fbooker 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +15 -0
- data/CHANGELOG.rdoc +24 -0
- data/COPYING.rdoc +19 -0
- data/Manifest.txt +144 -0
- data/README.rdoc +113 -0
- data/Rakefile +94 -0
- data/TODO.rdoc +4 -0
- data/examples/desktop_login.rb +14 -0
- data/facebooker.gemspec +42 -0
- data/generators/facebook/facebook_generator.rb +14 -0
- data/generators/facebook/templates/config/facebooker.yml +49 -0
- data/generators/facebook/templates/public/javascripts/facebooker.js +332 -0
- data/generators/facebook_controller/USAGE +33 -0
- data/generators/facebook_controller/facebook_controller_generator.rb +40 -0
- data/generators/facebook_controller/templates/controller.rb +7 -0
- data/generators/facebook_controller/templates/functional_test.rb +11 -0
- data/generators/facebook_controller/templates/helper.rb +2 -0
- data/generators/facebook_controller/templates/view.fbml.erb +2 -0
- data/generators/facebook_controller/templates/view.html.erb +2 -0
- data/generators/facebook_publisher/facebook_publisher_generator.rb +14 -0
- data/generators/facebook_publisher/templates/create_facebook_templates.rb +15 -0
- data/generators/facebook_publisher/templates/publisher.rb +3 -0
- data/generators/facebook_scaffold/USAGE +27 -0
- data/generators/facebook_scaffold/facebook_scaffold_generator.rb +118 -0
- data/generators/facebook_scaffold/templates/controller.rb +93 -0
- data/generators/facebook_scaffold/templates/facebook_style.css +2579 -0
- data/generators/facebook_scaffold/templates/functional_test.rb +89 -0
- data/generators/facebook_scaffold/templates/helper.rb +2 -0
- data/generators/facebook_scaffold/templates/layout.fbml.erb +6 -0
- data/generators/facebook_scaffold/templates/layout.html.erb +17 -0
- data/generators/facebook_scaffold/templates/style.css +74 -0
- data/generators/facebook_scaffold/templates/view_edit.fbml.erb +13 -0
- data/generators/facebook_scaffold/templates/view_edit.html.erb +18 -0
- data/generators/facebook_scaffold/templates/view_index.fbml.erb +24 -0
- data/generators/facebook_scaffold/templates/view_index.html.erb +24 -0
- data/generators/facebook_scaffold/templates/view_new.fbml.erb +12 -0
- data/generators/facebook_scaffold/templates/view_new.html.erb +17 -0
- data/generators/facebook_scaffold/templates/view_show.fbml.erb +10 -0
- data/generators/facebook_scaffold/templates/view_show.html.erb +10 -0
- data/generators/publisher/publisher_generator.rb +14 -0
- data/generators/xd_receiver/templates/xd_receiver.html +10 -0
- data/generators/xd_receiver/templates/xd_receiver_ssl.html +10 -0
- data/generators/xd_receiver/xd_receiver_generator.rb +10 -0
- data/init.rb +26 -0
- data/install.rb +12 -0
- data/lib/facebooker.rb +252 -0
- data/lib/facebooker/adapters/adapter_base.rb +91 -0
- data/lib/facebooker/adapters/bebo_adapter.rb +77 -0
- data/lib/facebooker/adapters/facebook_adapter.rb +60 -0
- data/lib/facebooker/admin.rb +42 -0
- data/lib/facebooker/application.rb +13 -0
- data/lib/facebooker/batch_request.rb +45 -0
- data/lib/facebooker/data.rb +57 -0
- data/lib/facebooker/feed.rb +78 -0
- data/lib/facebooker/logging.rb +44 -0
- data/lib/facebooker/mobile.rb +20 -0
- data/lib/facebooker/mock/service.rb +50 -0
- data/lib/facebooker/mock/session.rb +18 -0
- data/lib/facebooker/model.rb +139 -0
- data/lib/facebooker/models/affiliation.rb +10 -0
- data/lib/facebooker/models/album.rb +11 -0
- data/lib/facebooker/models/applicationproperties.rb +39 -0
- data/lib/facebooker/models/applicationrestrictions.rb +10 -0
- data/lib/facebooker/models/cookie.rb +10 -0
- data/lib/facebooker/models/education_info.rb +11 -0
- data/lib/facebooker/models/event.rb +28 -0
- data/lib/facebooker/models/family_relative_info.rb +7 -0
- data/lib/facebooker/models/friend_list.rb +16 -0
- data/lib/facebooker/models/group.rb +36 -0
- data/lib/facebooker/models/info_item.rb +10 -0
- data/lib/facebooker/models/info_section.rb +10 -0
- data/lib/facebooker/models/location.rb +8 -0
- data/lib/facebooker/models/notifications.rb +17 -0
- data/lib/facebooker/models/page.rb +44 -0
- data/lib/facebooker/models/photo.rb +19 -0
- data/lib/facebooker/models/tag.rb +12 -0
- data/lib/facebooker/models/user.rb +552 -0
- data/lib/facebooker/models/video.rb +9 -0
- data/lib/facebooker/models/work_info.rb +10 -0
- data/lib/facebooker/parser.rb +717 -0
- data/lib/facebooker/rails/backwards_compatible_param_checks.rb +31 -0
- data/lib/facebooker/rails/controller.rb +353 -0
- data/lib/facebooker/rails/cucumber.rb +28 -0
- data/lib/facebooker/rails/cucumber/world.rb +40 -0
- data/lib/facebooker/rails/extensions/action_controller.rb +48 -0
- data/lib/facebooker/rails/extensions/rack_setup.rb +6 -0
- data/lib/facebooker/rails/extensions/routing.rb +15 -0
- data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
- data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
- data/lib/facebooker/rails/facebook_request_fix.rb +28 -0
- data/lib/facebooker/rails/facebook_request_fix_2-3.rb +31 -0
- data/lib/facebooker/rails/facebook_session_handling.rb +68 -0
- data/lib/facebooker/rails/facebook_url_helper.rb +192 -0
- data/lib/facebooker/rails/facebook_url_rewriting.rb +60 -0
- data/lib/facebooker/rails/helpers.rb +870 -0
- data/lib/facebooker/rails/helpers/fb_connect.rb +122 -0
- data/lib/facebooker/rails/integration_session.rb +38 -0
- data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
- data/lib/facebooker/rails/publisher.rb +595 -0
- data/lib/facebooker/rails/routing.rb +49 -0
- data/lib/facebooker/rails/test_helpers.rb +68 -0
- data/lib/facebooker/rails/utilities.rb +22 -0
- data/lib/facebooker/server_cache.rb +24 -0
- data/lib/facebooker/service.rb +102 -0
- data/lib/facebooker/service/base_service.rb +19 -0
- data/lib/facebooker/service/curl_service.rb +44 -0
- data/lib/facebooker/service/net_http_service.rb +12 -0
- data/lib/facebooker/service/typhoeus_multi_service.rb +27 -0
- data/lib/facebooker/service/typhoeus_service.rb +17 -0
- data/lib/facebooker/session.rb +726 -0
- data/lib/facebooker/version.rb +9 -0
- data/lib/net/http_multipart_post.rb +123 -0
- data/lib/rack/facebook.rb +89 -0
- data/lib/tasks/facebooker.rake +18 -0
- data/lib/tasks/tunnel.rake +46 -0
- data/rails/init.rb +1 -0
- data/setup.rb +1585 -0
- data/templates/layout.erb +24 -0
- data/test/facebooker/adapters_test.rb +191 -0
- data/test/facebooker/admin_test.rb +102 -0
- data/test/facebooker/application_test.rb +41 -0
- data/test/facebooker/batch_request_test.rb +83 -0
- data/test/facebooker/data_test.rb +86 -0
- data/test/facebooker/logging_test.rb +43 -0
- data/test/facebooker/mobile_test.rb +45 -0
- data/test/facebooker/model_test.rb +133 -0
- data/test/facebooker/models/event_test.rb +15 -0
- data/test/facebooker/models/page_test.rb +46 -0
- data/test/facebooker/models/photo_test.rb +16 -0
- data/test/facebooker/models/user_test.rb +397 -0
- data/test/facebooker/rails/facebook_request_fix_2-3_test.rb +25 -0
- data/test/facebooker/rails/facebook_url_rewriting_test.rb +76 -0
- data/test/facebooker/rails/integration_session_test.rb +13 -0
- data/test/facebooker/rails/publisher_test.rb +519 -0
- data/test/facebooker/rails_integration_test.rb +1511 -0
- data/test/facebooker/server_cache_test.rb +44 -0
- data/test/facebooker/service_test.rb +58 -0
- data/test/facebooker/session_test.rb +883 -0
- data/test/facebooker_test.rb +951 -0
- data/test/net/http_multipart_post_test.rb +52 -0
- data/test/rack/facebook_test.rb +73 -0
- metadata +235 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Facebooker
|
|
2
|
+
module Rails
|
|
3
|
+
module Routing
|
|
4
|
+
module RouteSetExtensions
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.alias_method_chain :extract_request_environment, :facebooker
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def extract_request_environment_with_facebooker(request)
|
|
10
|
+
env = extract_request_environment_without_facebooker(request)
|
|
11
|
+
env.merge :canvas => (request.parameters[:fb_sig_in_canvas]=="1")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
module MapperExtensions
|
|
15
|
+
|
|
16
|
+
# Generates pseudo-resource routes. Since everything is a POST, routes can't be identified
|
|
17
|
+
# using HTTP verbs. Therefore, the action is appended to the beginning of each named route,
|
|
18
|
+
# except for index.
|
|
19
|
+
#
|
|
20
|
+
# Example:
|
|
21
|
+
# map.facebook_resources :profiles
|
|
22
|
+
#
|
|
23
|
+
# Generates the following routes:
|
|
24
|
+
#
|
|
25
|
+
# new_profile POST /profiles/new {:controller=>"profiles", :action=>"new"}
|
|
26
|
+
# profiles POST /profiles/index {:controller=>"profiles", :action=>"index"}
|
|
27
|
+
# show_profile POST /profiles/:id/show {:controller=>"profiles", :action=>"show"}
|
|
28
|
+
# create_profile POST /profiles/create {:controller=>"profiles", :action=>"create"}
|
|
29
|
+
# edit_profile POST /profiles/:id/edit {:controller=>"profiles", :action=>"edit"}
|
|
30
|
+
# update_profile POST /profiles/:id/update {:controller=>"profiles", :action=>"update"}
|
|
31
|
+
# destroy_profile POST /profiles/:id/destroy {:controller=>"profiles", :action=>"destroy"}
|
|
32
|
+
#
|
|
33
|
+
def facebook_resources(name_sym)
|
|
34
|
+
name = name_sym.to_s
|
|
35
|
+
|
|
36
|
+
with_options :controller => name, :conditions => { :method => :post } do |map|
|
|
37
|
+
map.named_route("new_#{name.singularize}", "#{name}/new", :action => 'new')
|
|
38
|
+
map.named_route(name, "#{name}/index", :action => 'index')
|
|
39
|
+
map.named_route("show_#{name.singularize}", "#{name}/:id/show", :action => 'show', :id => /\d+/)
|
|
40
|
+
map.named_route("create_#{name.singularize}", "#{name}/create", :action => 'create')
|
|
41
|
+
map.named_route("edit_#{name.singularize}", "#{name}/:id/edit", :action => 'edit', :id => /\d+/)
|
|
42
|
+
map.named_route("update_#{name.singularize}", "#{name}/:id/update", :action => 'update', :id => /\d+/)
|
|
43
|
+
map.named_route("destroy_#{name.singularize}", "#{name}/:id/destroy", :action => 'destroy', :id => /\d+/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Facebooker
|
|
2
|
+
module Rails
|
|
3
|
+
module TestHelpers
|
|
4
|
+
def assert_facebook_redirect_to(url)
|
|
5
|
+
assert_response :success
|
|
6
|
+
assert_not_nil facebook_redirect_url
|
|
7
|
+
assert_equal url, facebook_redirect_url
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def follow_facebook_redirect!
|
|
11
|
+
facebook_post facebook_redirect_url
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def facebook_get(path,params={})
|
|
15
|
+
facebook_verb(:get,path,params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def facebook_post(path,params={})
|
|
19
|
+
facebook_verb(:post,path,params)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def facebook_put(path,params={})
|
|
23
|
+
facebook_verb(:put,path,params)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def facebook_delete(path,params={})
|
|
27
|
+
facebook_verb(:delete,path,params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def facebook_verb(verb,path, params={})
|
|
31
|
+
send verb, path, facebook_params(params).reverse_merge(:canvas => true)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def facebook_params(params = {})
|
|
35
|
+
params = default_facebook_parameters.with_indifferent_access.merge(params || {})
|
|
36
|
+
sig = generate_signature params
|
|
37
|
+
params.merge(:fb_sig => sig)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def default_facebook_parameters
|
|
43
|
+
{
|
|
44
|
+
:fb_sig_added => "1",
|
|
45
|
+
:fb_sig_session_key => "facebook_session_key",
|
|
46
|
+
:fb_sig_user => "1234",
|
|
47
|
+
:fb_sig_expires => "0",
|
|
48
|
+
:fb_sig_in_canvas => "1",
|
|
49
|
+
:fb_sig_time => Time.now.to_f
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def facebook_redirect_url
|
|
54
|
+
match = @response.body.match(/<fb:redirect url="([^"]+)"/)
|
|
55
|
+
match.nil? ? nil : match.captures[0]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def generate_signature(params)
|
|
59
|
+
facebook_params = params.select { |param,_| param =~ /^fb_sig_/ }.map do |param, value|
|
|
60
|
+
[param.sub(/^fb_sig_/, ''), value].join('=')
|
|
61
|
+
end
|
|
62
|
+
Digest::MD5.hexdigest([facebook_params.sort.join, Facebooker::Session.secret_key].join)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Facebooker
|
|
2
|
+
module Rails
|
|
3
|
+
class Utilities
|
|
4
|
+
class << self
|
|
5
|
+
def refresh_all_images(session)
|
|
6
|
+
Dir.glob(File.join(RAILS_ROOT,"public","images","*.{png,jpg,gif}")).each do |img|
|
|
7
|
+
refresh_image(session,img)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def refresh_image(session,full_path)
|
|
12
|
+
basename=File.basename(full_path)
|
|
13
|
+
base_path=ActionController::Base.asset_host
|
|
14
|
+
base_path += "/" unless base_path.ends_with?("/")
|
|
15
|
+
image_path=base_path+"images/#{basename}"
|
|
16
|
+
puts "refreshing: #{image_path}"
|
|
17
|
+
session.server_cache.refresh_img_src(image_path)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Facebooker
|
|
2
|
+
class ServerCache
|
|
3
|
+
def initialize(session)
|
|
4
|
+
@session = session
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# Stores an FBML reference on the server for use
|
|
9
|
+
# across multiple users in FBML
|
|
10
|
+
def set_ref_handle(handle_name, fbml_source)
|
|
11
|
+
(@session.post 'facebook.fbml.setRefHandle', {:handle => handle_name, :fbml => fbml_source},false) == '1'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Fetches and re-caches the content stored at the given URL, for use in a fb:ref FBML tag.
|
|
16
|
+
def refresh_ref_url(url)
|
|
17
|
+
(@session.post 'facebook.fbml.refreshRefUrl', {:url => url},false) == '1'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def refresh_img_src(url)
|
|
21
|
+
(@session.post 'facebook.fbml.refreshImgSrc', {:url => url},false) == '1'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'facebooker/parser'
|
|
2
|
+
module Facebooker
|
|
3
|
+
class Service
|
|
4
|
+
def initialize(api_base, api_path, api_key)
|
|
5
|
+
@api_base = api_base
|
|
6
|
+
@api_path = api_path
|
|
7
|
+
@api_key = api_key
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.active_service
|
|
11
|
+
unless @active_service
|
|
12
|
+
if Facebooker.use_curl?
|
|
13
|
+
@active_service = Facebooker::Service::CurlService.new
|
|
14
|
+
else
|
|
15
|
+
@active_service = Facebooker::Service::NetHttpService.new
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
@active_service
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.active_service=(new_service)
|
|
22
|
+
@active_service = new_service
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.with_service(service)
|
|
26
|
+
old_service = active_service
|
|
27
|
+
self.active_service = service
|
|
28
|
+
begin
|
|
29
|
+
yield
|
|
30
|
+
ensure
|
|
31
|
+
self.active_service = old_service
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Process all calls to Facebook in th block asynchronously
|
|
37
|
+
# nil will be returned from all calls and no results will be parsed. This is mostly useful
|
|
38
|
+
# for sending large numbers of notifications or sending a lot of profile updates
|
|
39
|
+
#
|
|
40
|
+
# for example:
|
|
41
|
+
# User.find_in_batches(:batch_size => 200) do |users|
|
|
42
|
+
# Faceboooker::Service.with_async do
|
|
43
|
+
# users.each {|u| u.facebook_session.send_notification(...)}
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# Note: You shouldn't make more than about 200 api calls in a with_async block
|
|
48
|
+
# or you might exhaust all filehandles.
|
|
49
|
+
#
|
|
50
|
+
# This functionality require the typhoeus gem
|
|
51
|
+
#
|
|
52
|
+
def self.with_async(&proc)
|
|
53
|
+
block_with_process = Proc.new { proc.call ; process_async}
|
|
54
|
+
with_service(Facebooker::Service::TyphoeusMultiService.new,&block_with_process)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.process_async
|
|
58
|
+
active_service.process
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# TODO: support ssl
|
|
63
|
+
def post(params)
|
|
64
|
+
attempt = 0
|
|
65
|
+
if active_service.parse_results?
|
|
66
|
+
Parser.parse(params[:method], post_form(url,params) )
|
|
67
|
+
else
|
|
68
|
+
post_form(url,params)
|
|
69
|
+
end
|
|
70
|
+
rescue Errno::ECONNRESET, EOFError
|
|
71
|
+
if attempt == 0
|
|
72
|
+
attempt += 1
|
|
73
|
+
retry
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def post_form(url,params)
|
|
78
|
+
active_service.post_form(url,params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def post_multipart_form(url,params)
|
|
82
|
+
active_service.post_multipart_form(url,params)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def active_service
|
|
86
|
+
self.class.active_service
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def post_file(params)
|
|
90
|
+
service_url = url(params.delete(:base))
|
|
91
|
+
result = post_multipart_form(service_url, params)
|
|
92
|
+
Parser.parse(params[:method], result)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
def url(base = nil)
|
|
97
|
+
base ||= @api_base
|
|
98
|
+
URI.parse('http://'+ base + @api_path)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Facebooker::Service::BaseService
|
|
2
|
+
def parse_results?
|
|
3
|
+
true
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def post_params(params)
|
|
7
|
+
post_params = {}
|
|
8
|
+
params.each do |k,v|
|
|
9
|
+
k = k.to_s unless k.is_a?(String)
|
|
10
|
+
if Array === v || Hash === v
|
|
11
|
+
post_params[k] = Facebooker.json_encode(v)
|
|
12
|
+
else
|
|
13
|
+
post_params[k] = v
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
post_params
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'curb'
|
|
2
|
+
Facebooker.use_curl = true
|
|
3
|
+
class Facebooker::Service::CurlService < Facebooker::Service::BaseService
|
|
4
|
+
def post_form(url,params,multipart=false)
|
|
5
|
+
curl = Curl::Easy.new(url.to_s) do |c|
|
|
6
|
+
c.multipart_form_post = multipart
|
|
7
|
+
c.timeout = Facebooker.timeout
|
|
8
|
+
end
|
|
9
|
+
curl.http_post(*to_curb_params(params))
|
|
10
|
+
curl.body_str
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def post_multipart_form(url,params)
|
|
14
|
+
post_form(url,params,true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Net::HTTP::MultipartPostFile
|
|
18
|
+
def multipart_post_file?(object)
|
|
19
|
+
object.respond_to?(:content_type) &&
|
|
20
|
+
object.respond_to?(:data) &&
|
|
21
|
+
object.respond_to?(:filename)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_curb_params(params)
|
|
25
|
+
parray = []
|
|
26
|
+
params.each_pair do |k,v|
|
|
27
|
+
if multipart_post_file?(v)
|
|
28
|
+
# Curl doesn't like blank field names
|
|
29
|
+
field = Curl::PostField.file((k.blank? ? 'xxx' : k.to_s), nil, File.basename(v.filename))
|
|
30
|
+
field.content_type = v.content_type
|
|
31
|
+
field.content = v.data
|
|
32
|
+
parray << field
|
|
33
|
+
else
|
|
34
|
+
parray << Curl::PostField.content(
|
|
35
|
+
k.to_s,
|
|
36
|
+
(Array === v || Hash===v) ? Facebooker.json_encode(v) : v.to_s
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
parray
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
class Facebooker::Service::NetHttpService <Facebooker::Service::BaseService
|
|
3
|
+
def post_form(url,params)
|
|
4
|
+
Net::HTTP.post_form(url, post_params(params))
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def post_multipart_form(url,params)
|
|
8
|
+
Net::HTTP.post_multipart_form(url, params)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Facebooker::Service::TyphoeusMultiService < Facebooker::Service::TyphoeusService
|
|
2
|
+
|
|
3
|
+
def initialize
|
|
4
|
+
@result_objects = []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def parse_results?
|
|
8
|
+
false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#define a method that returns nothing
|
|
12
|
+
define_remote_method :async_post, :on_success => lambda {|r| puts "."}
|
|
13
|
+
|
|
14
|
+
def perform_post(url,params)
|
|
15
|
+
add_result(self.class.async_post(:base_uri=>url,:params=>params))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_result(obj)
|
|
19
|
+
@result_objects << obj
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def process
|
|
23
|
+
# we need to access all objects to make sure the proxy has made the request
|
|
24
|
+
@result_objects.each(&:nil?)
|
|
25
|
+
@result_objects = []
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'typhoeus'
|
|
2
|
+
class Facebooker::Service::TyphoeusService < Facebooker::Service::BaseService
|
|
3
|
+
include Typhoeus
|
|
4
|
+
def post_form(url,params)
|
|
5
|
+
perform_post(url.to_s,post_params(params))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def perform_post(url,params)
|
|
9
|
+
self.class.post(url,:params=>post_params)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def post_multipart_form(url,params)
|
|
13
|
+
raise "Multipart not supported on Typhoeus"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
|
|
3
|
+
module Facebooker
|
|
4
|
+
#
|
|
5
|
+
# Raised when trying to perform an operation on a user
|
|
6
|
+
# other than the logged in user (if that's unallowed)
|
|
7
|
+
class NonSessionUser < StandardError; end
|
|
8
|
+
class Session
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Raised when a facebook session has expired. This
|
|
12
|
+
# happens when the timeout is reached, or when the
|
|
13
|
+
# user logs out of facebook
|
|
14
|
+
# can be handled with:
|
|
15
|
+
# rescue_from Facebooker::Session::SessionExpired, :with => :some_method_name
|
|
16
|
+
class SessionExpired < StandardError; end
|
|
17
|
+
|
|
18
|
+
class UnknownError < StandardError; end
|
|
19
|
+
class ServiceUnavailable < StandardError; end
|
|
20
|
+
class MaxRequestsDepleted < StandardError; end
|
|
21
|
+
class HostNotAllowed < StandardError; end
|
|
22
|
+
class MissingOrInvalidParameter < StandardError; end
|
|
23
|
+
class InvalidAPIKey < StandardError; end
|
|
24
|
+
class SessionExpired < StandardError; end
|
|
25
|
+
class CallOutOfOrder < StandardError; end
|
|
26
|
+
class IncorrectSignature < StandardError; end
|
|
27
|
+
class SignatureTooOld < StandardError; end
|
|
28
|
+
class TooManyUserCalls < StandardError; end
|
|
29
|
+
class TooManyUserActionCalls < StandardError; end
|
|
30
|
+
class InvalidFeedTitleLink < StandardError; end
|
|
31
|
+
class InvalidFeedTitleLength < StandardError; end
|
|
32
|
+
class InvalidFeedTitleName < StandardError; end
|
|
33
|
+
class BlankFeedTitle < StandardError; end
|
|
34
|
+
class FeedBodyLengthTooLong < StandardError; end
|
|
35
|
+
class InvalidFeedPhotoSource < StandardError; end
|
|
36
|
+
class InvalidFeedPhotoLink < StandardError; end
|
|
37
|
+
class TemplateDataMissingRequiredTokens < StandardError; end
|
|
38
|
+
class FeedMarkupInvalid < StandardError; end
|
|
39
|
+
class FeedTitleDataInvalid < StandardError; end
|
|
40
|
+
class FeedTitleTemplateInvalid < StandardError; end
|
|
41
|
+
class FeedBodyDataInvalid < StandardError; end
|
|
42
|
+
class FeedBodyTemplateInvalid < StandardError; end
|
|
43
|
+
class FeedPhotosNotRetrieved < StandardError; end
|
|
44
|
+
class FeedTargetIdsInvalid < StandardError; end
|
|
45
|
+
class TemplateBundleInvalid < StandardError; end
|
|
46
|
+
class ConfigurationMissing < StandardError; end
|
|
47
|
+
class FQLParseError < StandardError; end
|
|
48
|
+
class FQLFieldDoesNotExist < StandardError; end
|
|
49
|
+
class FQLTableDoesNotExist < StandardError; end
|
|
50
|
+
class FQLStatementNotIndexable < StandardError; end
|
|
51
|
+
class FQLFunctionDoesNotExist < StandardError; end
|
|
52
|
+
class FQLWrongNumberArgumentsPassedToFunction < StandardError; end
|
|
53
|
+
class PermissionError < StandardError; end
|
|
54
|
+
class InvalidAlbumId < StandardError; end
|
|
55
|
+
class AlbumIsFull < StandardError; end
|
|
56
|
+
class MissingOrInvalidImageFile < StandardError; end
|
|
57
|
+
class TooManyUnapprovedPhotosPending < StandardError; end
|
|
58
|
+
class ExtendedPermissionRequired < StandardError; end
|
|
59
|
+
class InvalidFriendList < StandardError; end
|
|
60
|
+
class UserUnRegistrationFailed < StandardError
|
|
61
|
+
attr_accessor :failed_users
|
|
62
|
+
end
|
|
63
|
+
class UserRegistrationFailed < StandardError
|
|
64
|
+
attr_accessor :failed_users
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
API_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "api.new.facebook.com" : "api.facebook.com"
|
|
68
|
+
API_PATH_REST = "/restserver.php"
|
|
69
|
+
WWW_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "www.new.facebook.com" : "www.facebook.com"
|
|
70
|
+
WWW_PATH_LOGIN = "/login.php"
|
|
71
|
+
WWW_PATH_ADD = "/add.php"
|
|
72
|
+
WWW_PATH_INSTALL = "/install.php"
|
|
73
|
+
|
|
74
|
+
attr_writer :auth_token
|
|
75
|
+
attr_reader :session_key
|
|
76
|
+
attr_reader :secret_from_session
|
|
77
|
+
|
|
78
|
+
def self.create(api_key=nil, secret_key=nil)
|
|
79
|
+
api_key ||= self.api_key
|
|
80
|
+
secret_key ||= self.secret_key
|
|
81
|
+
raise ArgumentError unless !api_key.nil? && !secret_key.nil?
|
|
82
|
+
new(api_key, secret_key)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.api_key
|
|
86
|
+
extract_key_from_environment(:api) || extract_key_from_configuration_file(:api) rescue report_inability_to_find_key(:api)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.secret_key
|
|
90
|
+
extract_key_from_environment(:secret) || extract_key_from_configuration_file(:secret) rescue report_inability_to_find_key(:secret)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.current
|
|
94
|
+
Thread.current['facebook_session']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.current=(session)
|
|
98
|
+
Thread.current['facebook_session'] = session
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def login_url(options={})
|
|
102
|
+
options = default_login_url_options.merge(options)
|
|
103
|
+
"#{Facebooker.login_url_base}#{login_url_optional_parameters(options)}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def install_url(options={})
|
|
107
|
+
"#{Facebooker.install_url_base}#{install_url_optional_parameters(options)}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The url to get user to approve extended permissions
|
|
111
|
+
# http://wiki.developers.facebook.com/index.php/Extended_permission
|
|
112
|
+
#
|
|
113
|
+
# permissions:
|
|
114
|
+
# * email
|
|
115
|
+
# * offline_access
|
|
116
|
+
# * status_update
|
|
117
|
+
# * photo_upload
|
|
118
|
+
# * video_upload
|
|
119
|
+
# * create_listing
|
|
120
|
+
# * create_event
|
|
121
|
+
# * rsvp_event
|
|
122
|
+
# * sms
|
|
123
|
+
def permission_url(permission,options={})
|
|
124
|
+
options = default_login_url_options.merge(options)
|
|
125
|
+
options = add_next_parameters(options)
|
|
126
|
+
options << "&ext_perm=#{permission}"
|
|
127
|
+
"#{Facebooker.permission_url_base}#{options.join}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def connect_permission_url(permission,options={})
|
|
131
|
+
options = default_login_url_options.merge(options)
|
|
132
|
+
options = add_next_parameters(options)
|
|
133
|
+
options << "&ext_perm=#{permission}"
|
|
134
|
+
"#{Facebooker.connect_permission_url_base}#{options.join}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def install_url_optional_parameters(options)
|
|
138
|
+
optional_parameters = []
|
|
139
|
+
optional_parameters += add_next_parameters(options)
|
|
140
|
+
optional_parameters.join
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def add_next_parameters(options)
|
|
144
|
+
opts = []
|
|
145
|
+
opts << "&next=#{CGI.escape(options[:next])}" if options[:next]
|
|
146
|
+
opts << "&next_cancel=#{CGI.escape(options[:next_cancel])}" if options[:next_cancel]
|
|
147
|
+
opts
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def login_url_optional_parameters(options)
|
|
151
|
+
# It is important that unused options are omitted as stuff like &canvas=false will still display the canvas.
|
|
152
|
+
optional_parameters = []
|
|
153
|
+
optional_parameters += add_next_parameters(options)
|
|
154
|
+
optional_parameters << "&skipcookie=true" if options[:skip_cookie]
|
|
155
|
+
optional_parameters << "&hide_checkbox=true" if options[:hide_checkbox]
|
|
156
|
+
optional_parameters << "&canvas=true" if options[:canvas]
|
|
157
|
+
optional_parameters << "&fbconnect=true" if options[:fbconnect]
|
|
158
|
+
optional_parameters << "&req_perms=#{options[:req_perms]}" if options[:req_perms]
|
|
159
|
+
optional_parameters.join
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def default_login_url_options
|
|
163
|
+
{}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def initialize(api_key, secret_key)
|
|
167
|
+
@api_key = api_key
|
|
168
|
+
@secret_key = secret_key
|
|
169
|
+
@batch_request = nil
|
|
170
|
+
@session_key = nil
|
|
171
|
+
@uid = nil
|
|
172
|
+
@auth_token = nil
|
|
173
|
+
@secret_from_session = nil
|
|
174
|
+
@expires = nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def secret_for_method(method_name)
|
|
178
|
+
@secret_key
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def auth_token
|
|
182
|
+
@auth_token ||= post 'facebook.auth.createToken'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def infinite?
|
|
186
|
+
@expires == 0
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def expired?
|
|
190
|
+
@expires.nil? || (!infinite? && Time.at(@expires) <= Time.now)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def secured?
|
|
194
|
+
!@session_key.nil? && !expired?
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def secure!(args = {})
|
|
198
|
+
response = post 'facebook.auth.getSession', :auth_token => auth_token, :generate_session_secret => args[:generate_session_secret] ? "1" : "0"
|
|
199
|
+
secure_with!(response['session_key'], response['uid'], response['expires'], response['secret'])
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def secure_with_session_secret!
|
|
203
|
+
self.secure!(:generate_session_secret => true)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def secure_with!(session_key, uid = nil, expires = nil, secret_from_session = nil)
|
|
207
|
+
@session_key = session_key
|
|
208
|
+
@uid = uid ? Integer(uid) : post('facebook.users.getLoggedInUser', :session_key => session_key)
|
|
209
|
+
@expires = Integer(expires)
|
|
210
|
+
@secret_from_session = secret_from_session
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def fql_build_object(type, hash)
|
|
214
|
+
case type
|
|
215
|
+
when 'user'
|
|
216
|
+
user = User.new
|
|
217
|
+
user.session = self
|
|
218
|
+
user.populate_from_hash!(hash)
|
|
219
|
+
user
|
|
220
|
+
when 'photo'
|
|
221
|
+
Photo.from_hash(hash)
|
|
222
|
+
when 'album'
|
|
223
|
+
Album.from_hash(hash)
|
|
224
|
+
when 'page'
|
|
225
|
+
Page.from_hash(hash)
|
|
226
|
+
when 'page_admin'
|
|
227
|
+
Page.from_hash(hash)
|
|
228
|
+
when 'group'
|
|
229
|
+
Group.from_hash(hash)
|
|
230
|
+
when 'event'
|
|
231
|
+
Event.from_hash(hash)
|
|
232
|
+
when 'event_member'
|
|
233
|
+
Event::Attendance.from_hash(hash)
|
|
234
|
+
else
|
|
235
|
+
hash
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def fql_query(query, format = 'XML')
|
|
240
|
+
post('facebook.fql.query', :query => query, :format => format) do |response|
|
|
241
|
+
type = response.shift
|
|
242
|
+
return [] if type.nil?
|
|
243
|
+
response.shift.map do |hash|
|
|
244
|
+
fql_build_object(type, hash)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def fql_multiquery(queries, format = 'XML')
|
|
250
|
+
results = {}
|
|
251
|
+
post('facebook.fql.multiquery', :queries => queries.to_json, :format => format) do |responses|
|
|
252
|
+
responses.each do |response|
|
|
253
|
+
name = response.shift
|
|
254
|
+
response = response.shift
|
|
255
|
+
type = response.shift
|
|
256
|
+
value = []
|
|
257
|
+
unless type.nil?
|
|
258
|
+
value = response.shift.map do |hash|
|
|
259
|
+
fql_build_object(type, hash)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
results[name] = value
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
results
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def user
|
|
269
|
+
@user ||= User.new(uid, self)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
#
|
|
273
|
+
# This one has so many parameters, a Hash seemed cleaner than a long param list. Options can be:
|
|
274
|
+
# :uid => Filter by events associated with a user with this uid
|
|
275
|
+
# :eids => Filter by this list of event ids. This is a comma-separated list of eids.
|
|
276
|
+
# :start_time => Filter with this UTC as lower bound. A missing or zero parameter indicates no lower bound. (Time or Integer)
|
|
277
|
+
# :end_time => Filter with this UTC as upper bound. A missing or zero parameter indicates no upper bound. (Time or Integer)
|
|
278
|
+
# :rsvp_status => Filter by this RSVP status.
|
|
279
|
+
def events(options = {})
|
|
280
|
+
@events ||= {}
|
|
281
|
+
@events[options.to_s] ||= post('facebook.events.get', options) do |response|
|
|
282
|
+
response.map do |hash|
|
|
283
|
+
Event.from_hash(hash)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Creates an event with the event_info hash and an optional Net::HTTP::MultipartPostFile for the event picture.
|
|
289
|
+
# If ActiveSupport::TimeWithZone is installed (it's in Rails > 2.1), and start_time or end_time are given as
|
|
290
|
+
# ActiveSupport::TimeWithZone, then they will be assumed to represent local time for the event. They will automatically be
|
|
291
|
+
# converted to the expected timezone for Facebook, which is PST or PDT depending on when the event occurs.
|
|
292
|
+
# Returns the eid of the newly created event
|
|
293
|
+
# http://wiki.developers.facebook.com/index.php/Events.create
|
|
294
|
+
def create_event(event_info, multipart_post_file = nil)
|
|
295
|
+
if defined?(ActiveSupport::TimeWithZone) && defined?(ActiveSupport::TimeZone)
|
|
296
|
+
# Facebook expects all event local times to be in Pacific Time, so we need to take the actual local time and
|
|
297
|
+
# send it to Facebook as if it were Pacific Time converted to Unix epoch timestamp. Very confusing...
|
|
298
|
+
facebook_time = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
|
|
299
|
+
|
|
300
|
+
start_time = event_info.delete(:start_time) || event_info.delete('start_time')
|
|
301
|
+
if start_time && start_time.is_a?(ActiveSupport::TimeWithZone)
|
|
302
|
+
event_info['start_time'] = facebook_time.parse(start_time.strftime("%Y-%m-%d %H:%M:%S")).to_i
|
|
303
|
+
else
|
|
304
|
+
event_info['start_time'] = start_time
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
end_time = event_info.delete(:end_time) || event_info.delete('end_time')
|
|
308
|
+
if end_time && end_time.is_a?(ActiveSupport::TimeWithZone)
|
|
309
|
+
event_info['end_time'] = facebook_time.parse(end_time.strftime("%Y-%m-%d %H:%M:%S")).to_i
|
|
310
|
+
else
|
|
311
|
+
event_info['end_time'] = end_time
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
post_file('facebook.events.create', :event_info => event_info.to_json, nil => multipart_post_file)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Cancel an event
|
|
319
|
+
# http://wiki.developers.facebook.com/index.php/Events.cancel
|
|
320
|
+
# E.g:
|
|
321
|
+
# @session.cancel_event('100321123', :cancel_message => "It's raining...")
|
|
322
|
+
# # => Returns true if all went well
|
|
323
|
+
def cancel_event(eid, options = {})
|
|
324
|
+
result = post('facebook.events.cancel', options.merge(:eid => eid))
|
|
325
|
+
result == '1' ? true : false
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def event_members(eid)
|
|
329
|
+
@members ||= {}
|
|
330
|
+
@members[eid] ||= post('facebook.events.getMembers', :eid => eid) do |response|
|
|
331
|
+
response.map do |attendee_hash|
|
|
332
|
+
Event::Attendance.from_hash(attendee_hash)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def users_standard(user_ids, fields=[])
|
|
338
|
+
post("facebook.users.getStandardInfo",:uids=>user_ids.join(","),:fields=>User.standard_fields(fields)) do |users|
|
|
339
|
+
users.map { |u| User.new(u)}
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def users(user_ids, fields=[])
|
|
344
|
+
post("facebook.users.getInfo",:uids=>user_ids.join(","),:fields=>User.user_fields(fields)) do |users|
|
|
345
|
+
users.map { |u| User.new(u)}
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def pages(options = {})
|
|
350
|
+
raise ArgumentError, 'fields option is mandatory' unless options.has_key?(:fields)
|
|
351
|
+
@pages ||= {}
|
|
352
|
+
@pages[options] ||= post('facebook.pages.getInfo', options) do |response|
|
|
353
|
+
response.map do |hash|
|
|
354
|
+
Page.from_hash(hash)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Takes page_id and uid, returns true if uid is a fan of the page_id
|
|
360
|
+
def is_fan(page_id, uid)
|
|
361
|
+
puts "Deprecated. Use Page#user_is_fan? instead"
|
|
362
|
+
Page.new(page_id).user_is_fan?(uid)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
#
|
|
367
|
+
# Returns a proxy object for handling calls to Facebook cached items
|
|
368
|
+
# such as images and FBML ref handles
|
|
369
|
+
def server_cache
|
|
370
|
+
Facebooker::ServerCache.new(self)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
#
|
|
374
|
+
# Returns a proxy object for handling calls to the Facebook Data API
|
|
375
|
+
def data
|
|
376
|
+
Facebooker::Data.new(self)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def admin
|
|
380
|
+
Facebooker::Admin.new(self)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def application
|
|
384
|
+
Facebooker::Application.new(self)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def mobile
|
|
388
|
+
Facebooker::Mobile.new(self)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
#
|
|
392
|
+
# Given an array like:
|
|
393
|
+
# [[userid, otheruserid], [yetanotherid, andanotherid]]
|
|
394
|
+
# returns a Hash indicating friendship of those pairs:
|
|
395
|
+
# {[userid, otheruserid] => true, [yetanotherid, andanotherid] => false}
|
|
396
|
+
# if one of the Hash values is nil, it means the facebook platform's answer is "I don't know"
|
|
397
|
+
def check_friendship(array_of_pairs_of_users)
|
|
398
|
+
uids1 = []
|
|
399
|
+
uids2 = []
|
|
400
|
+
array_of_pairs_of_users.each do |pair|
|
|
401
|
+
uids1 << pair.first
|
|
402
|
+
uids2 << pair.last
|
|
403
|
+
end
|
|
404
|
+
post('facebook.friends.areFriends', :uids1 => uids1.join(','), :uids2 => uids2.join(','))
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def get_photos(pids = nil, subj_id = nil, aid = nil)
|
|
408
|
+
if [subj_id, pids, aid].all? {|arg| arg.nil?}
|
|
409
|
+
raise ArgumentError, "Can't get a photo without a picture, album or subject ID"
|
|
410
|
+
end
|
|
411
|
+
# We have to normalize params orherwise FB complain about signature
|
|
412
|
+
params = {:pids => pids, :subj_id => subj_id, :aid => aid}.delete_if { |k,v| v.nil? }
|
|
413
|
+
@photos = post('facebook.photos.get', params ) do |response|
|
|
414
|
+
response.map do |hash|
|
|
415
|
+
Photo.from_hash(hash)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def get_albums(aids)
|
|
421
|
+
@albums = post('facebook.photos.getAlbums', :aids => aids) do |response|
|
|
422
|
+
response.map do |hash|
|
|
423
|
+
Album.from_hash(hash)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def get_tags(pids)
|
|
429
|
+
@tags = post('facebook.photos.getTags', :pids => pids) do |response|
|
|
430
|
+
response.map do |hash|
|
|
431
|
+
Tag.from_hash(hash)
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def add_tags(pid, x, y, tag_uid = nil, tag_text = nil )
|
|
437
|
+
if [tag_uid, tag_text].all? {|arg| arg.nil?}
|
|
438
|
+
raise ArgumentError, "Must enter a name or string for this tag"
|
|
439
|
+
end
|
|
440
|
+
@tags = post('facebook.photos.addTag', :pid => pid, :tag_uid => tag_uid, :tag_text => tag_text, :x => x, :y => y )
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def send_notification(user_ids, fbml, email_fbml = nil)
|
|
444
|
+
params = {:notification => fbml, :to_ids => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(',')}
|
|
445
|
+
if email_fbml
|
|
446
|
+
params[:email] = email_fbml
|
|
447
|
+
end
|
|
448
|
+
params[:type]="user_to_user"
|
|
449
|
+
# if there is no uid, this is an announcement
|
|
450
|
+
unless uid?
|
|
451
|
+
params[:type]="app_to_user"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
post 'facebook.notifications.send', params,uid?
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
##
|
|
458
|
+
# Register a template bundle with Facebook.
|
|
459
|
+
# returns the template id to use to send using this template
|
|
460
|
+
def register_template_bundle(one_line_story_templates,short_story_templates=nil,full_story_template=nil, action_links=nil)
|
|
461
|
+
templates = ensure_array(one_line_story_templates)
|
|
462
|
+
parameters = {:one_line_story_templates => templates.to_json}
|
|
463
|
+
|
|
464
|
+
unless action_links.blank?
|
|
465
|
+
parameters[:action_links] = action_links.to_json
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
unless short_story_templates.blank?
|
|
469
|
+
templates = ensure_array(short_story_templates)
|
|
470
|
+
parameters[:short_story_templates] = templates.to_json
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
unless full_story_template.blank?
|
|
474
|
+
parameters[:full_story_template] = full_story_template.to_json
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
post("facebook.feed.registerTemplateBundle", parameters, false)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
##
|
|
481
|
+
# Deactivate a template bundle with Facebook.
|
|
482
|
+
# Returns true if a bundle with the specified id is active and owned by this app.
|
|
483
|
+
# Useful to avoid exceeding the 100 templates/app limit.
|
|
484
|
+
def deactivate_template_bundle_by_id(template_bundle_id)
|
|
485
|
+
post("facebook.feed.deactivateTemplateBundleByID", {:template_bundle_id => template_bundle_id.to_s}, false)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def active_template_bundles
|
|
489
|
+
post("facebook.feed.getRegisteredTemplateBundles",{},false)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
##
|
|
493
|
+
# publish a previously rendered template bundle
|
|
494
|
+
# see http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
|
|
495
|
+
#
|
|
496
|
+
def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil,story_size=nil)
|
|
497
|
+
parameters={:template_bundle_id=>bundle_id,:template_data=>data.to_json}
|
|
498
|
+
parameters[:target_ids] = target_ids unless target_ids.blank?
|
|
499
|
+
parameters[:body_general] = body_general unless body_general.blank?
|
|
500
|
+
parameters[:story_size] = story_size unless story_size.nil?
|
|
501
|
+
post("facebook.feed.publishUserAction", parameters)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
##
|
|
506
|
+
# Send email to as many as 100 users at a time
|
|
507
|
+
def send_email(user_ids, subject, text, fbml = nil)
|
|
508
|
+
user_ids = Array(user_ids)
|
|
509
|
+
params = {:fbml => fbml, :recipients => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(','), :text => text, :subject => subject}
|
|
510
|
+
post 'facebook.notifications.sendEmail', params, false
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Only serialize the bare minimum to recreate the session.
|
|
514
|
+
def marshal_load(variables)#:nodoc:
|
|
515
|
+
fields_to_serialize.each_with_index{|field, index| instance_variable_set_value(field, variables[index])}
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Only serialize the bare minimum to recreate the session.
|
|
519
|
+
def marshal_dump#:nodoc:
|
|
520
|
+
fields_to_serialize.map{|field| instance_variable_value(field)}
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Only serialize the bare minimum to recreate the session.
|
|
524
|
+
def to_yaml( opts = {} )
|
|
525
|
+
YAML::quick_emit(self.object_id, opts) do |out|
|
|
526
|
+
out.map(taguri) do |map|
|
|
527
|
+
fields_to_serialize.each do |field|
|
|
528
|
+
map.add(field, instance_variable_value(field))
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def instance_variable_set_value(field, value)
|
|
535
|
+
self.instance_variable_set("@#{field}", value)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def instance_variable_value(field)
|
|
539
|
+
self.instance_variable_get("@#{field}")
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def fields_to_serialize
|
|
543
|
+
%w(session_key uid expires secret_from_session auth_token api_key secret_key)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
class Desktop < Session
|
|
547
|
+
def login_url
|
|
548
|
+
super + "&auth_token=#{auth_token}"
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def secret_for_method(method_name)
|
|
552
|
+
secret = auth_request_methods.include?(method_name) ? super : @secret_from_session
|
|
553
|
+
secret
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def post(method, params = {},use_session=false)
|
|
557
|
+
if method == 'facebook.profile.getFBML' || method == 'facebook.profile.setFBML'
|
|
558
|
+
raise NonSessionUser.new("User #{@uid} is not the logged in user.") unless @uid == params[:uid]
|
|
559
|
+
end
|
|
560
|
+
super
|
|
561
|
+
end
|
|
562
|
+
private
|
|
563
|
+
def auth_request_methods
|
|
564
|
+
['facebook.auth.getSession', 'facebook.auth.createToken']
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def batch_request?
|
|
569
|
+
@batch_request
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def add_to_batch(req,&proc)
|
|
573
|
+
batch_request = BatchRequest.new(req,proc)
|
|
574
|
+
Thread.current[:facebooker_current_batch_queue]<<batch_request
|
|
575
|
+
batch_request
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Submit the enclosed requests for this session inside a batch
|
|
579
|
+
#
|
|
580
|
+
# All requests will be sent to Facebook at the end of the block
|
|
581
|
+
# each method inside the block will return a proxy object
|
|
582
|
+
# attempting to access the proxy before the end of the block will yield an exception
|
|
583
|
+
#
|
|
584
|
+
# For Example:
|
|
585
|
+
#
|
|
586
|
+
# facebook_session.batch do
|
|
587
|
+
# @send_result = facebook_session.send_notification([12451752],"Woohoo")
|
|
588
|
+
# @albums = facebook_session.user.albums
|
|
589
|
+
# end
|
|
590
|
+
# puts @albums.first.inspect
|
|
591
|
+
#
|
|
592
|
+
# is valid, however
|
|
593
|
+
#
|
|
594
|
+
# facebook_session.batch do
|
|
595
|
+
# @send_result = facebook_session.send_notification([12451752],"Woohoo")
|
|
596
|
+
# @albums = facebook_session.user.albums
|
|
597
|
+
# puts @albums.first.inspect
|
|
598
|
+
# end
|
|
599
|
+
#
|
|
600
|
+
# will raise Facebooker::BatchRequest::UnexecutedRequest
|
|
601
|
+
#
|
|
602
|
+
# If an exception is raised while processing the result, that exception will be
|
|
603
|
+
# re-raised on the next access to that object or when exception_raised? is called
|
|
604
|
+
#
|
|
605
|
+
# for example, if the send_notification resulted in TooManyUserCalls being raised,
|
|
606
|
+
# calling
|
|
607
|
+
# @send_result.exception_raised?
|
|
608
|
+
# would re-raise that exception
|
|
609
|
+
# if there was an error retrieving the albums, it would be re-raised when
|
|
610
|
+
# @albums.first
|
|
611
|
+
# is called
|
|
612
|
+
#
|
|
613
|
+
def batch(serial_only=false)
|
|
614
|
+
@batch_request=true
|
|
615
|
+
Thread.current[:facebooker_current_batch_queue]=[]
|
|
616
|
+
yield
|
|
617
|
+
# Set the batch request to false so that post will execute the batch job
|
|
618
|
+
@batch_request=false
|
|
619
|
+
BatchRun.current_batch=Thread.current[:facebooker_current_batch_queue]
|
|
620
|
+
post("facebook.batch.run",:method_feed=>BatchRun.current_batch.map{|q| q.uri}.to_json,:serial_only=>serial_only.to_s)
|
|
621
|
+
ensure
|
|
622
|
+
@batch_request=false
|
|
623
|
+
BatchRun.current_batch=nil
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def post_without_logging(method, params = {}, use_session_key = true, &proc)
|
|
627
|
+
add_facebook_params(params, method)
|
|
628
|
+
use_session_key && @session_key && params[:session_key] ||= @session_key
|
|
629
|
+
final_params=params.merge(:sig => signature_for(params))
|
|
630
|
+
if batch_request?
|
|
631
|
+
add_to_batch(final_params,&proc)
|
|
632
|
+
else
|
|
633
|
+
result = service.post(final_params)
|
|
634
|
+
result = yield result if block_given?
|
|
635
|
+
result
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def post(method, params = {}, use_session_key = true, &proc)
|
|
640
|
+
if batch_request? or Facebooker::Logging.skip_api_logging
|
|
641
|
+
post_without_logging(method, params, use_session_key, &proc)
|
|
642
|
+
else
|
|
643
|
+
Logging.log_fb_api(method, params) do
|
|
644
|
+
post_without_logging(method, params, use_session_key, &proc)
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def post_file(method, params = {})
|
|
650
|
+
base = params.delete(:base)
|
|
651
|
+
Logging.log_fb_api(method, params) do
|
|
652
|
+
add_facebook_params(params, method)
|
|
653
|
+
@session_key && params[:session_key] ||= @session_key unless params[:uid]
|
|
654
|
+
service.post_file(params.merge(:base => base, :sig => signature_for(params.reject{|key, value| key.nil?})))
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@configuration_file_path = nil
|
|
660
|
+
|
|
661
|
+
def self.configuration_file_path
|
|
662
|
+
@configuration_file_path || File.expand_path("~/.facebookerrc")
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def self.configuration_file_path=(path)
|
|
666
|
+
@configuration_file_path = path
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
private
|
|
670
|
+
def add_facebook_params(hash, method)
|
|
671
|
+
hash[:method] = method
|
|
672
|
+
hash[:api_key] = @api_key
|
|
673
|
+
hash[:call_id] = Time.now.to_f.to_s unless method == 'facebook.auth.getSession'
|
|
674
|
+
hash[:v] = "1.0"
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# This ultimately delgates to the adapter
|
|
678
|
+
def self.extract_key_from_environment(key_name)
|
|
679
|
+
Facebooker.send(key_name.to_s + "_key") rescue nil
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def self.extract_key_from_configuration_file(key_name)
|
|
683
|
+
read_configuration_file[key_name]
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def self.report_inability_to_find_key(key_name)
|
|
687
|
+
raise ConfigurationMissing, "Could not find configuration information for #{key_name}"
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def self.read_configuration_file
|
|
691
|
+
eval(File.read(configuration_file_path))
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def service
|
|
695
|
+
@service ||= Service.new(Facebooker.api_server_base, Facebooker.api_rest_path, @api_key)
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def uid
|
|
699
|
+
@uid || (secure!; @uid)
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def uid?
|
|
703
|
+
!! @uid
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def signature_for(params)
|
|
707
|
+
raw_string = params.inject([]) do |collection, pair|
|
|
708
|
+
collection << pair.map { |x|
|
|
709
|
+
Array === x ? Facebooker.json_encode(x) : x
|
|
710
|
+
}.join("=")
|
|
711
|
+
collection
|
|
712
|
+
end.sort.join
|
|
713
|
+
Digest::MD5.hexdigest([raw_string, secret_for_method(params[:method])].join)
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def ensure_array(value)
|
|
717
|
+
value.is_a?(Array) ? value : [value]
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
class CanvasSession < Session
|
|
722
|
+
def default_login_url_options
|
|
723
|
+
{:canvas => true}
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|