facebooker 1.0.13 → 1.0.18
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/Manifest.txt +29 -16
- data/Rakefile +9 -2
- data/generators/facebook/templates/public/javascripts/facebooker.js +1 -8
- data/init.rb +3 -1
- data/lib/facebooker.rb +4 -2
- data/lib/facebooker/adapters/facebook_adapter.rb +4 -0
- data/lib/facebooker/admin.rb +2 -2
- 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 +3 -3
- data/lib/facebooker/models/applicationrestrictions.rb +10 -0
- data/lib/facebooker/models/user.rb +35 -3
- data/lib/facebooker/models/user.rb.orig +396 -0
- data/lib/facebooker/models/user.rb.rej +17 -0
- data/lib/facebooker/models/video.rb +9 -0
- data/lib/facebooker/parser.rb +22 -1
- data/lib/facebooker/rails/controller.rb +34 -12
- data/lib/facebooker/rails/cucumber.rb +28 -0
- data/lib/facebooker/rails/cucumber/world.rb +46 -0
- data/lib/facebooker/rails/facebook_pretty_errors.rb +19 -11
- data/lib/facebooker/rails/helpers.rb +203 -122
- data/lib/facebooker/rails/helpers/fb_connect.rb +56 -5
- data/lib/facebooker/rails/integration_session.rb +38 -0
- data/lib/facebooker/rails/publisher.rb +15 -2
- data/lib/facebooker/rails/test_helpers.rb +11 -26
- data/lib/facebooker/service.rb +6 -3
- data/lib/facebooker/session.rb +24 -2
- data/lib/facebooker/session.rb.orig +564 -0
- data/lib/facebooker/session.rb.rej +29 -0
- data/lib/facebooker/version.rb +1 -1
- data/lib/tasks/tunnel.rake +2 -2
- data/test/{adapters_test.rb → facebooker/adapters_test.rb} +2 -4
- data/test/{facebook_admin_test.rb → facebooker/admin_test.rb} +2 -2
- data/test/{batch_request_test.rb → facebooker/batch_request_test.rb} +3 -2
- data/test/{facebook_data_test.rb → facebooker/data_test.rb} +2 -2
- data/test/{logging_test.rb → facebooker/logging_test.rb} +3 -3
- data/test/facebooker/mobile_test.rb +45 -0
- data/test/{model_test.rb → facebooker/model_test.rb} +36 -4
- data/test/{event_test.rb → facebooker/models/event_test.rb} +2 -2
- data/test/{user_test.rb → facebooker/models/user_test.rb} +10 -5
- data/test/{publisher_test.rb → facebooker/rails/publisher_test.rb} +12 -18
- data/test/{rails_integration_test.rb → facebooker/rails_integration_test.rb} +347 -272
- data/test/{facebook_cache_test.rb → facebooker/server_cache_test.rb} +1 -1
- data/test/{session_test.rb → facebooker/session_test.rb} +3 -2
- data/test/facebooker_test.rb +19 -1
- data/test/{http_multipart_post_test.rb → net/http_multipart_post_test.rb} +2 -4
- data/test/rails_test_helper.rb +11 -0
- data/test/test_helper.rb +8 -2
- metadata +45 -32
- data/lib/facebooker/rails/facebook_asset_path.rb +0 -18
@@ -11,27 +11,78 @@ module Facebooker
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def init_fb_connect(*required_features)
|
14
|
+
def init_fb_connect(*required_features,&proc)
|
15
|
+
additions = ""
|
16
|
+
if block_given?
|
17
|
+
additions = capture(&proc)
|
18
|
+
end
|
15
19
|
init_string = "FB.Facebook.init('#{Facebooker.api_key}','/xd_receiver.html');"
|
16
20
|
unless required_features.blank?
|
17
21
|
init_string = <<-FBML
|
18
22
|
Element.observe(window,'load', function() {
|
19
23
|
FB_RequireFeatures(#{required_features.to_json}, function() {
|
20
24
|
#{init_string}
|
25
|
+
#{additions}
|
21
26
|
});
|
22
27
|
});
|
23
28
|
FBML
|
24
29
|
end
|
25
|
-
|
30
|
+
if block_given?
|
31
|
+
concat javascript_tag(init_string)
|
32
|
+
else
|
33
|
+
javascript_tag init_string
|
34
|
+
end
|
26
35
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
|
37
|
+
# Render an <fb:login-button> element
|
38
|
+
#
|
39
|
+
# ==== Examples
|
40
|
+
#
|
41
|
+
# <%= fb_login_button%>
|
42
|
+
# => <fb:login-button></fb:login-button>
|
43
|
+
#
|
44
|
+
# Specifying a javascript callback
|
45
|
+
#
|
46
|
+
# <%= fb_login_button 'update_something();'%>
|
47
|
+
# => <fb:login-button onlogin='update_something();'></fb:login-button>
|
48
|
+
#
|
49
|
+
# Adding options <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:login-button
|
50
|
+
#
|
51
|
+
# <%= fb_login_button 'update_something();', :size => :small, :background => :dark%>
|
52
|
+
# => <fb:login-button background='dark' onlogin='update_something();' size='small'></fb:login-button>
|
53
|
+
#
|
54
|
+
def fb_login_button(*args)
|
55
|
+
|
56
|
+
callback = args.first
|
57
|
+
options = args.second || {}
|
58
|
+
options.merge!(:onlogin=>callback)if callback
|
59
|
+
|
60
|
+
content_tag("fb:login-button",nil, options)
|
61
|
+
end
|
62
|
+
def fb_login_and_redirect(url)
|
63
|
+
js = update_page do |page|
|
64
|
+
page.redirect_to url
|
65
|
+
end
|
66
|
+
content_tag("fb:login-button",nil,:onlogin=>js)
|
30
67
|
end
|
31
68
|
|
32
69
|
def fb_unconnected_friends_count
|
33
70
|
content_tag "fb:unconnected-friends-count",nil
|
34
71
|
end
|
72
|
+
|
73
|
+
def fb_logout_link(text,url)
|
74
|
+
js = update_page do |page|
|
75
|
+
page.call "FB.Connect.logoutAndRedirect",url
|
76
|
+
end
|
77
|
+
link_to_function text, js
|
78
|
+
end
|
79
|
+
|
80
|
+
def fb_user_action(action)
|
81
|
+
update_page do |page|
|
82
|
+
page.call "FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,"FB.RequireConnect.promptConnect"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
35
86
|
end
|
36
87
|
end
|
37
88
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'action_controller/integration'
|
2
|
+
|
3
|
+
class Facebooker::Rails::IntegrationSession < ActionController::Integration::Session
|
4
|
+
include Facebooker::Rails::TestHelpers
|
5
|
+
attr_accessor :default_request_params, :canvas
|
6
|
+
|
7
|
+
def process(method, path, parameters = nil, headers = nil)
|
8
|
+
if canvas
|
9
|
+
parameters = facebook_params(@default_request_params.merge(parameters || {}))
|
10
|
+
end
|
11
|
+
super method, path, parameters, headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset!
|
15
|
+
self.default_request_params = {:fb_sig_in_canvas => '1'}.with_indifferent_access
|
16
|
+
self.canvas = true
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(path, parameters = nil, headers = nil)
|
21
|
+
if canvas
|
22
|
+
post path, (parameters || {}).merge('fb_sig_request_method' => 'GET'), headers
|
23
|
+
else
|
24
|
+
super path, parameters, headers
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
%w(put delete).each do |method|
|
29
|
+
define_method method do |*args|
|
30
|
+
if canvas
|
31
|
+
path, parameters, headers = *args
|
32
|
+
post path, (parameters || {}).merge('_method' => method.upcase), headers
|
33
|
+
else
|
34
|
+
super *args
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -312,10 +312,23 @@ module Facebooker
|
|
312
312
|
end
|
313
313
|
end
|
314
314
|
|
315
|
-
|
316
|
-
|
315
|
+
# work around the fact that facebook cares about the order of the keys in the hash
|
316
|
+
class ImageHolder
|
317
|
+
attr_accessor :src,:href
|
318
|
+
def initialize(src,href)
|
319
|
+
self.src=src
|
320
|
+
self.href=href
|
321
|
+
end
|
322
|
+
|
323
|
+
def to_json(*args)
|
324
|
+
"{\"src\":#{src.to_json}, \"href\":#{href.to_json}}"
|
325
|
+
end
|
317
326
|
end
|
318
327
|
|
328
|
+
def image(src,target)
|
329
|
+
ImageHolder.new(image_path(src),target.respond_to?(:to_str) ? target : url_for(target))
|
330
|
+
end
|
331
|
+
|
319
332
|
def action_link(text,target)
|
320
333
|
{:text=>text, :href=>target}
|
321
334
|
end
|
@@ -22,25 +22,22 @@ module Facebooker
|
|
22
22
|
def facebook_put(path,params={})
|
23
23
|
facebook_verb(:put,path,params)
|
24
24
|
end
|
25
|
+
|
25
26
|
def facebook_delete(path,params={})
|
26
27
|
facebook_verb(:delete,path,params)
|
27
28
|
end
|
28
29
|
|
29
30
|
def facebook_verb(verb,path, params={})
|
30
|
-
|
31
|
-
params.merge!(:fb_sig => generate_signature(facebook_params(params).stringify_keys))
|
32
|
-
|
33
|
-
params = params.update(:canvas => true).update(params)
|
34
|
-
send verb, path, params
|
31
|
+
send verb, path, facebook_params(params).reverse_merge(:canvas => true)
|
35
32
|
end
|
36
33
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
params.merge(:fb_sig =>
|
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)
|
41
38
|
end
|
42
39
|
|
43
|
-
|
40
|
+
private
|
44
41
|
|
45
42
|
def default_facebook_parameters
|
46
43
|
{
|
@@ -53,28 +50,16 @@ module Facebooker
|
|
53
50
|
}
|
54
51
|
end
|
55
52
|
|
56
|
-
def facebook_params(params)
|
57
|
-
params.inject({}) do |fb_params, pair|
|
58
|
-
unless pair.first.to_s.match(/^fb_sig_/).nil?
|
59
|
-
fb_params[pair.first] = pair.last
|
60
|
-
end
|
61
|
-
fb_params
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
53
|
def facebook_redirect_url
|
66
54
|
match = @response.body.match(/<fb:redirect url="([^"]+)"/)
|
67
55
|
match.nil? ? nil : match.captures[0]
|
68
56
|
end
|
69
57
|
|
70
|
-
def generate_signature(
|
71
|
-
|
72
|
-
|
73
|
-
collection
|
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('=')
|
74
61
|
end
|
75
|
-
|
76
|
-
raw_string = facebook_sig_params.map{ |*args| args.join('=') }.sort.join
|
77
|
-
Digest::MD5.hexdigest([raw_string, Facebooker::Session.secret_key].join)
|
62
|
+
Digest::MD5.hexdigest([facebook_params.sort.join, Facebooker::Session.secret_key].join)
|
78
63
|
end
|
79
64
|
|
80
65
|
end
|
data/lib/facebooker/service.rb
CHANGED
@@ -58,12 +58,15 @@ module Facebooker
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def post_file(params)
|
61
|
-
|
61
|
+
service_url = url(params.delete(:base))
|
62
|
+
result = post_multipart_form(service_url, params)
|
63
|
+
Parser.parse(params[:method], result)
|
62
64
|
end
|
63
65
|
|
64
66
|
private
|
65
|
-
def url
|
66
|
-
|
67
|
+
def url(base = nil)
|
68
|
+
base ||= @api_base
|
69
|
+
URI.parse('http://'+ base + @api_path)
|
67
70
|
end
|
68
71
|
|
69
72
|
# Net::HTTP::MultipartPostFile
|
data/lib/facebooker/session.rb
CHANGED
@@ -94,6 +94,19 @@ module Facebooker
|
|
94
94
|
"#{Facebooker.install_url_base(@api_key)}#{install_url_optional_parameters(options)}"
|
95
95
|
end
|
96
96
|
|
97
|
+
# The url to get user to approve extended permissions
|
98
|
+
# http://wiki.developers.facebook.com/index.php/Extended_permission
|
99
|
+
#
|
100
|
+
# permissions:
|
101
|
+
# * email
|
102
|
+
# * offline_access
|
103
|
+
# * status_update
|
104
|
+
# * photo_upload
|
105
|
+
# * video_upload
|
106
|
+
# * create_listing
|
107
|
+
# * create_event
|
108
|
+
# * rsvp_event
|
109
|
+
# * sms
|
97
110
|
def permission_url(permission,options={})
|
98
111
|
options = default_login_url_options.merge(options)
|
99
112
|
"http://#{Facebooker.www_server_base_url}/authorize.php?api_key=#{@api_key}&v=1.0&ext_perm=#{permission}#{install_url_optional_parameters(options)}"
|
@@ -176,6 +189,10 @@ module Facebooker
|
|
176
189
|
user
|
177
190
|
when 'photo'
|
178
191
|
Photo.from_hash(hash)
|
192
|
+
when 'page'
|
193
|
+
Page.from_hash(hash)
|
194
|
+
when 'page_admin'
|
195
|
+
Page.from_hash(hash)
|
179
196
|
when 'event_member'
|
180
197
|
Event::Attendance.from_hash(hash)
|
181
198
|
else
|
@@ -256,6 +273,10 @@ module Facebooker
|
|
256
273
|
Facebooker::Admin.new(self)
|
257
274
|
end
|
258
275
|
|
276
|
+
def mobile
|
277
|
+
Facebooker::Mobile.new(self)
|
278
|
+
end
|
279
|
+
|
259
280
|
#
|
260
281
|
# Given an array like:
|
261
282
|
# [[userid, otheruserid], [yetanotherid, andanotherid]]
|
@@ -492,10 +513,11 @@ module Facebooker
|
|
492
513
|
end
|
493
514
|
|
494
515
|
def post_file(method, params = {})
|
516
|
+
base = params.delete(:base)
|
495
517
|
Logging.log_fb_api(method, params) do
|
496
518
|
add_facebook_params(params, method)
|
497
|
-
@session_key && params[:session_key] ||= @session_key
|
498
|
-
service.post_file(params.merge(:sig => signature_for(params.reject{|key, value| key.nil?})))
|
519
|
+
@session_key && params[:session_key] ||= @session_key unless params[:uid]
|
520
|
+
service.post_file(params.merge(:base => base, :sig => signature_for(params.reject{|key, value| key.nil?})))
|
499
521
|
end
|
500
522
|
end
|
501
523
|
|
@@ -0,0 +1,564 @@
|
|
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
|
+
class SessionExpired < StandardError; end
|
10
|
+
class UnknownError < StandardError; end
|
11
|
+
class ServiceUnavailable < StandardError; end
|
12
|
+
class MaxRequestsDepleted < StandardError; end
|
13
|
+
class HostNotAllowed < StandardError; end
|
14
|
+
class MissingOrInvalidParameter < StandardError; end
|
15
|
+
class InvalidAPIKey < StandardError; end
|
16
|
+
class SessionExpired < StandardError; end
|
17
|
+
class CallOutOfOrder < StandardError; end
|
18
|
+
class IncorrectSignature < StandardError; end
|
19
|
+
class SignatureTooOld < StandardError; end
|
20
|
+
class TooManyUserCalls < StandardError; end
|
21
|
+
class TooManyUserActionCalls < StandardError; end
|
22
|
+
class InvalidFeedTitleLink < StandardError; end
|
23
|
+
class InvalidFeedTitleLength < StandardError; end
|
24
|
+
class InvalidFeedTitleName < StandardError; end
|
25
|
+
class BlankFeedTitle < StandardError; end
|
26
|
+
class FeedBodyLengthTooLong < StandardError; end
|
27
|
+
class InvalidFeedPhotoSource < StandardError; end
|
28
|
+
class InvalidFeedPhotoLink < StandardError; end
|
29
|
+
class TemplateDataMissingRequiredTokens < StandardError; end
|
30
|
+
class FeedMarkupInvalid < StandardError; end
|
31
|
+
class FeedTitleDataInvalid < StandardError; end
|
32
|
+
class FeedTitleTemplateInvalid < StandardError; end
|
33
|
+
class FeedBodyDataInvalid < StandardError; end
|
34
|
+
class FeedBodyTemplateInvalid < StandardError; end
|
35
|
+
class FeedPhotosNotRetrieved < StandardError; end
|
36
|
+
class FeedTargetIdsInvalid < StandardError; end
|
37
|
+
class TemplateBundleInvalid < StandardError; end
|
38
|
+
class ConfigurationMissing < StandardError; end
|
39
|
+
class FQLParseError < StandardError; end
|
40
|
+
class FQLFieldDoesNotExist < StandardError; end
|
41
|
+
class FQLTableDoesNotExist < StandardError; end
|
42
|
+
class FQLStatementNotIndexable < StandardError; end
|
43
|
+
class FQLFunctionDoesNotExist < StandardError; end
|
44
|
+
class FQLWrongNumberArgumentsPassedToFunction < StandardError; end
|
45
|
+
class InvalidAlbumId < StandardError; end
|
46
|
+
class AlbumIsFull < StandardError; end
|
47
|
+
class MissingOrInvalidImageFile < StandardError; end
|
48
|
+
class TooManyUnapprovedPhotosPending < StandardError; end
|
49
|
+
class ExtendedPermissionRequired < StandardError; end
|
50
|
+
class InvalidFriendList < StandardError; end
|
51
|
+
class UserRegistrationFailed < StandardError
|
52
|
+
attr_accessor :failed_users
|
53
|
+
end
|
54
|
+
|
55
|
+
API_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "api.new.facebook.com" : "api.facebook.com"
|
56
|
+
API_PATH_REST = "/restserver.php"
|
57
|
+
WWW_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "www.new.facebook.com" : "www.facebook.com"
|
58
|
+
WWW_PATH_LOGIN = "/login.php"
|
59
|
+
WWW_PATH_ADD = "/add.php"
|
60
|
+
WWW_PATH_INSTALL = "/install.php"
|
61
|
+
|
62
|
+
attr_writer :auth_token
|
63
|
+
attr_reader :session_key
|
64
|
+
|
65
|
+
def self.create(api_key=nil, secret_key=nil)
|
66
|
+
api_key ||= self.api_key
|
67
|
+
secret_key ||= self.secret_key
|
68
|
+
raise ArgumentError unless !api_key.nil? && !secret_key.nil?
|
69
|
+
new(api_key, secret_key)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.api_key
|
73
|
+
extract_key_from_environment(:api) || extract_key_from_configuration_file(:api) rescue report_inability_to_find_key(:api)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.secret_key
|
77
|
+
extract_key_from_environment(:secret) || extract_key_from_configuration_file(:secret) rescue report_inability_to_find_key(:secret)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.current
|
81
|
+
@current_session
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.current=(session)
|
85
|
+
@current_session=session
|
86
|
+
end
|
87
|
+
|
88
|
+
def login_url(options={})
|
89
|
+
options = default_login_url_options.merge(options)
|
90
|
+
"#{Facebooker.login_url_base(@api_key)}#{login_url_optional_parameters(options)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def install_url(options={})
|
94
|
+
"#{Facebooker.install_url_base(@api_key)}#{install_url_optional_parameters(options)}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def permission_url(permission,options={})
|
98
|
+
options = default_login_url_options.merge(options)
|
99
|
+
"http://#{Facebooker.www_server_base_url}/authorize.php?api_key=#{@api_key}&v=1.0&ext_perm=#{permission}#{install_url_optional_parameters(options)}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def install_url_optional_parameters(options)
|
103
|
+
optional_parameters = []
|
104
|
+
optional_parameters += add_next_parameters(options)
|
105
|
+
optional_parameters.join
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_next_parameters(options)
|
109
|
+
opts = []
|
110
|
+
opts << "&next=#{CGI.escape(options[:next])}" if options[:next]
|
111
|
+
opts << "&next_cancel=#{CGI.escape(options[:next_cancel])}" if options[:next_cancel]
|
112
|
+
opts
|
113
|
+
end
|
114
|
+
|
115
|
+
def login_url_optional_parameters(options)
|
116
|
+
# It is important that unused options are omitted as stuff like &canvas=false will still display the canvas.
|
117
|
+
optional_parameters = []
|
118
|
+
optional_parameters += add_next_parameters(options)
|
119
|
+
optional_parameters << "&skipcookie=true" if options[:skip_cookie]
|
120
|
+
optional_parameters << "&hide_checkbox=true" if options[:hide_checkbox]
|
121
|
+
optional_parameters << "&canvas=true" if options[:canvas]
|
122
|
+
optional_parameters.join
|
123
|
+
end
|
124
|
+
|
125
|
+
def default_login_url_options
|
126
|
+
{}
|
127
|
+
end
|
128
|
+
|
129
|
+
def initialize(api_key, secret_key)
|
130
|
+
@api_key = api_key
|
131
|
+
@secret_key = secret_key
|
132
|
+
end
|
133
|
+
|
134
|
+
def secret_for_method(method_name)
|
135
|
+
@secret_key
|
136
|
+
end
|
137
|
+
|
138
|
+
def auth_token
|
139
|
+
@auth_token ||= post 'facebook.auth.createToken'
|
140
|
+
end
|
141
|
+
|
142
|
+
def infinite?
|
143
|
+
@expires == 0
|
144
|
+
end
|
145
|
+
|
146
|
+
def expired?
|
147
|
+
@expires.nil? || (!infinite? && Time.at(@expires) <= Time.now)
|
148
|
+
end
|
149
|
+
|
150
|
+
def secured?
|
151
|
+
!@session_key.nil? && !expired?
|
152
|
+
end
|
153
|
+
|
154
|
+
def secure!
|
155
|
+
response = post 'facebook.auth.getSession', :auth_token => auth_token
|
156
|
+
secure_with!(response['session_key'], response['uid'], response['expires'], response['secret'])
|
157
|
+
end
|
158
|
+
|
159
|
+
def secure_with!(session_key, uid = nil, expires = nil, secret_from_session = nil)
|
160
|
+
@session_key = session_key
|
161
|
+
@uid = uid ? Integer(uid) : post('facebook.users.getLoggedInUser', :session_key => session_key)
|
162
|
+
@expires = Integer(expires)
|
163
|
+
@secret_from_session = secret_from_session
|
164
|
+
end
|
165
|
+
|
166
|
+
def fql_query(query, format = 'XML')
|
167
|
+
post('facebook.fql.query', :query => query, :format => format) do |response|
|
168
|
+
type = response.shift
|
169
|
+
return [] if type.nil?
|
170
|
+
response.shift.map do |hash|
|
171
|
+
case type
|
172
|
+
when 'user'
|
173
|
+
user = User.new
|
174
|
+
user.session = self
|
175
|
+
user.populate_from_hash!(hash)
|
176
|
+
user
|
177
|
+
when 'photo'
|
178
|
+
Photo.from_hash(hash)
|
179
|
+
when 'event_member'
|
180
|
+
Event::Attendance.from_hash(hash)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def user
|
187
|
+
@user ||= User.new(uid, self)
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# This one has so many parameters, a Hash seemed cleaner than a long param list. Options can be:
|
192
|
+
# :uid => Filter by events associated with a user with this uid
|
193
|
+
# :eids => Filter by this list of event ids. This is a comma-separated list of eids.
|
194
|
+
# :start_time => Filter with this UTC as lower bound. A missing or zero parameter indicates no lower bound. (Time or Integer)
|
195
|
+
# :end_time => Filter with this UTC as upper bound. A missing or zero parameter indicates no upper bound. (Time or Integer)
|
196
|
+
# :rsvp_status => Filter by this RSVP status.
|
197
|
+
def events(options = {})
|
198
|
+
@events ||= post('facebook.events.get', options) do |response|
|
199
|
+
response.map do |hash|
|
200
|
+
Event.from_hash(hash)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def event_members(eid)
|
206
|
+
@members ||= post('facebook.events.getMembers', :eid => eid) do |response|
|
207
|
+
response.map do |attendee_hash|
|
208
|
+
Event::Attendance.from_hash(attendee_hash)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def users_standard(user_ids, fields=[])
|
214
|
+
post("facebook.users.getStandardInfo",:uids=>user_ids.join(","),:fields=>User.user_fields(fields)) do |users|
|
215
|
+
users.map { |u| User.new(u)}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def users(user_ids, fields=[])
|
220
|
+
post("facebook.users.getInfo",:uids=>user_ids.join(","),:fields=>User.standard_fields(fields)) do |users|
|
221
|
+
users.map { |u| User.new(u)}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def pages(options = {})
|
226
|
+
raise ArgumentError, 'fields option is mandatory' unless options.has_key?(:fields)
|
227
|
+
@pages ||= {}
|
228
|
+
@pages[options] ||= post('facebook.pages.getInfo', options) do |response|
|
229
|
+
response.map do |hash|
|
230
|
+
Page.from_hash(hash)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# Returns a proxy object for handling calls to Facebook cached items
|
237
|
+
# such as images and FBML ref handles
|
238
|
+
def server_cache
|
239
|
+
Facebooker::ServerCache.new(self)
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# Returns a proxy object for handling calls to the Facebook Data API
|
244
|
+
def data
|
245
|
+
Facebooker::Data.new(self)
|
246
|
+
end
|
247
|
+
|
248
|
+
def admin
|
249
|
+
Facebooker::Admin.new(self)
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# Given an array like:
|
254
|
+
# [[userid, otheruserid], [yetanotherid, andanotherid]]
|
255
|
+
# returns a Hash indicating friendship of those pairs:
|
256
|
+
# {[userid, otheruserid] => true, [yetanotherid, andanotherid] => false}
|
257
|
+
# if one of the Hash values is nil, it means the facebook platform's answer is "I don't know"
|
258
|
+
def check_friendship(array_of_pairs_of_users)
|
259
|
+
uids1 = []
|
260
|
+
uids2 = []
|
261
|
+
array_of_pairs_of_users.each do |pair|
|
262
|
+
uids1 << pair.first
|
263
|
+
uids2 << pair.last
|
264
|
+
end
|
265
|
+
post('facebook.friends.areFriends', :uids1 => uids1.join(','), :uids2 => uids2.join(','))
|
266
|
+
end
|
267
|
+
|
268
|
+
def get_photos(pids = nil, subj_id = nil, aid = nil)
|
269
|
+
if [subj_id, pids, aid].all? {|arg| arg.nil?}
|
270
|
+
raise ArgumentError, "Can't get a photo without a picture, album or subject ID"
|
271
|
+
end
|
272
|
+
@photos = post('facebook.photos.get', :subj_id => subj_id, :pids => pids, :aid => aid ) do |response|
|
273
|
+
response.map do |hash|
|
274
|
+
Photo.from_hash(hash)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_albums(aids)
|
280
|
+
@albums = post('facebook.photos.getAlbums', :aids => aids) do |response|
|
281
|
+
response.map do |hash|
|
282
|
+
Album.from_hash(hash)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def get_tags(pids)
|
288
|
+
@tags = post('facebook.photos.getTags', :pids => pids) do |response|
|
289
|
+
response.map do |hash|
|
290
|
+
Tag.from_hash(hash)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def add_tags(pid, x, y, tag_uid = nil, tag_text = nil )
|
296
|
+
if [tag_uid, tag_text].all? {|arg| arg.nil?}
|
297
|
+
raise ArgumentError, "Must enter a name or string for this tag"
|
298
|
+
end
|
299
|
+
@tags = post('facebook.photos.addTag', :pid => pid, :tag_uid => tag_uid, :tag_text => tag_text, :x => x, :y => y )
|
300
|
+
end
|
301
|
+
|
302
|
+
def send_notification(user_ids, fbml, email_fbml = nil)
|
303
|
+
params = {:notification => fbml, :to_ids => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(',')}
|
304
|
+
if email_fbml
|
305
|
+
params[:email] = email_fbml
|
306
|
+
end
|
307
|
+
params[:type]="user_to_user"
|
308
|
+
# if there is no uid, this is an announcement
|
309
|
+
unless uid?
|
310
|
+
params[:type]="app_to_user"
|
311
|
+
end
|
312
|
+
|
313
|
+
post 'facebook.notifications.send', params,uid?
|
314
|
+
end
|
315
|
+
|
316
|
+
##
|
317
|
+
# Register a template bundle with Facebook.
|
318
|
+
# returns the template id to use to send using this template
|
319
|
+
def register_template_bundle(one_line_story_templates,short_story_templates=nil,full_story_template=nil, action_links=nil)
|
320
|
+
if !one_line_story_templates.is_a?(Array)
|
321
|
+
one_line_story_templates = [one_line_story_templates]
|
322
|
+
end
|
323
|
+
parameters = {:one_line_story_templates=>one_line_story_templates.to_json}
|
324
|
+
|
325
|
+
if !action_links.blank?
|
326
|
+
parameters[:action_links] = action_links.to_json
|
327
|
+
end
|
328
|
+
|
329
|
+
if !short_story_templates.blank?
|
330
|
+
short_story_templates = [short_story_templates] unless short_story_templates.is_a?(Array)
|
331
|
+
parameters[:short_story_templates]= short_story_templates.to_json
|
332
|
+
end
|
333
|
+
|
334
|
+
if !full_story_template.blank?
|
335
|
+
parameters[:full_story_template]= full_story_template.to_json
|
336
|
+
end
|
337
|
+
post("facebook.feed.registerTemplateBundle", parameters,false)
|
338
|
+
end
|
339
|
+
|
340
|
+
##
|
341
|
+
# publish a previously rendered template bundle
|
342
|
+
# see http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
|
343
|
+
#
|
344
|
+
def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil)
|
345
|
+
parameters={:template_bundle_id=>bundle_id,:template_data=>data.to_json}
|
346
|
+
parameters[:target_ids] = target_ids unless target_ids.blank?
|
347
|
+
parameters[:body_general] = body_general unless body_general.blank?
|
348
|
+
post("facebook.feed.publishUserAction", parameters)
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
##
|
353
|
+
# Send email to as many as 100 users at a time
|
354
|
+
def send_email(user_ids, subject, text, fbml = nil)
|
355
|
+
user_ids = Array(user_ids)
|
356
|
+
params = {:fbml => fbml, :recipients => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(','), :text => text, :subject => subject}
|
357
|
+
post 'facebook.notifications.sendEmail', params
|
358
|
+
end
|
359
|
+
|
360
|
+
# Only serialize the bare minimum to recreate the session.
|
361
|
+
def marshal_load(variables)#:nodoc:
|
362
|
+
fields_to_serialize.each_with_index{|field, index| instance_variable_set_value(field, variables[index])}
|
363
|
+
end
|
364
|
+
|
365
|
+
# Only serialize the bare minimum to recreate the session.
|
366
|
+
def marshal_dump#:nodoc:
|
367
|
+
fields_to_serialize.map{|field| instance_variable_value(field)}
|
368
|
+
end
|
369
|
+
|
370
|
+
# Only serialize the bare minimum to recreate the session.
|
371
|
+
def to_yaml( opts = {} )
|
372
|
+
YAML::quick_emit(self.object_id, opts) do |out|
|
373
|
+
out.map(taguri) do |map|
|
374
|
+
fields_to_serialize.each do |field|
|
375
|
+
map.add(field, instance_variable_value(field))
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def instance_variable_set_value(field, value)
|
382
|
+
self.instance_variable_set("@#{field}", value)
|
383
|
+
end
|
384
|
+
|
385
|
+
def instance_variable_value(field)
|
386
|
+
self.instance_variable_get("@#{field}")
|
387
|
+
end
|
388
|
+
|
389
|
+
def fields_to_serialize
|
390
|
+
%w(session_key uid expires secret_from_session auth_token api_key secret_key)
|
391
|
+
end
|
392
|
+
|
393
|
+
class Desktop < Session
|
394
|
+
def login_url
|
395
|
+
super + "&auth_token=#{auth_token}"
|
396
|
+
end
|
397
|
+
|
398
|
+
def secret_for_method(method_name)
|
399
|
+
secret = auth_request_methods.include?(method_name) ? super : @secret_from_session
|
400
|
+
secret
|
401
|
+
end
|
402
|
+
|
403
|
+
def post(method, params = {},use_session=false)
|
404
|
+
if method == 'facebook.profile.getFBML' || method == 'facebook.profile.setFBML'
|
405
|
+
raise NonSessionUser.new("User #{@uid} is not the logged in user.") unless @uid == params[:uid]
|
406
|
+
end
|
407
|
+
super
|
408
|
+
end
|
409
|
+
private
|
410
|
+
def auth_request_methods
|
411
|
+
['facebook.auth.getSession', 'facebook.auth.createToken']
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def batch_request?
|
416
|
+
@batch_request
|
417
|
+
end
|
418
|
+
|
419
|
+
def add_to_batch(req,&proc)
|
420
|
+
batch_request = BatchRequest.new(req,proc)
|
421
|
+
Thread.current[:facebooker_current_batch_queue]<<batch_request
|
422
|
+
batch_request
|
423
|
+
end
|
424
|
+
|
425
|
+
# Submit the enclosed requests for this session inside a batch
|
426
|
+
#
|
427
|
+
# All requests will be sent to Facebook at the end of the block
|
428
|
+
# each method inside the block will return a proxy object
|
429
|
+
# attempting to access the proxy before the end of the block will yield an exception
|
430
|
+
#
|
431
|
+
# For Example:
|
432
|
+
#
|
433
|
+
# facebook_session.batch do
|
434
|
+
# @send_result = facebook_session.send_notification([12451752],"Woohoo")
|
435
|
+
# @albums = facebook_session.user.albums
|
436
|
+
# end
|
437
|
+
# puts @albums.first.inspect
|
438
|
+
#
|
439
|
+
# is valid, however
|
440
|
+
#
|
441
|
+
# facebook_session.batch do
|
442
|
+
# @send_result = facebook_session.send_notification([12451752],"Woohoo")
|
443
|
+
# @albums = facebook_session.user.albums
|
444
|
+
# puts @albums.first.inspect
|
445
|
+
# end
|
446
|
+
#
|
447
|
+
# will raise Facebooker::BatchRequest::UnexecutedRequest
|
448
|
+
#
|
449
|
+
# If an exception is raised while processing the result, that exception will be
|
450
|
+
# re-raised on the next access to that object or when exception_raised? is called
|
451
|
+
#
|
452
|
+
# for example, if the send_notification resulted in TooManyUserCalls being raised,
|
453
|
+
# calling
|
454
|
+
# @send_result.exception_raised?
|
455
|
+
# would re-raise that exception
|
456
|
+
# if there was an error retrieving the albums, it would be re-raised when
|
457
|
+
# @albums.first
|
458
|
+
# is called
|
459
|
+
#
|
460
|
+
def batch(serial_only=false)
|
461
|
+
@batch_request=true
|
462
|
+
Thread.current[:facebooker_current_batch_queue]=[]
|
463
|
+
yield
|
464
|
+
# Set the batch request to false so that post will execute the batch job
|
465
|
+
@batch_request=false
|
466
|
+
BatchRun.current_batch=Thread.current[:facebooker_current_batch_queue]
|
467
|
+
post("facebook.batch.run",:method_feed=>BatchRun.current_batch.map{|q| q.uri}.to_json,:serial_only=>serial_only.to_s)
|
468
|
+
ensure
|
469
|
+
@batch_request=false
|
470
|
+
BatchRun.current_batch=nil
|
471
|
+
end
|
472
|
+
|
473
|
+
def post_without_logging(method, params = {}, use_session_key = true, &proc)
|
474
|
+
add_facebook_params(params, method)
|
475
|
+
use_session_key && @session_key && params[:session_key] ||= @session_key
|
476
|
+
final_params=params.merge(:sig => signature_for(params))
|
477
|
+
if batch_request?
|
478
|
+
add_to_batch(final_params,&proc)
|
479
|
+
else
|
480
|
+
result = service.post(final_params)
|
481
|
+
result = yield result if block_given?
|
482
|
+
result
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def post(method, params = {}, use_session_key = true, &proc)
|
487
|
+
if batch_request?
|
488
|
+
post_without_logging(method, params, use_session_key, &proc)
|
489
|
+
else
|
490
|
+
Logging.log_fb_api(method, params) do
|
491
|
+
post_without_logging(method, params, use_session_key, &proc)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
def post_file(method, params = {})
|
497
|
+
Logging.log_fb_api(method, params) do
|
498
|
+
add_facebook_params(params, method)
|
499
|
+
@session_key && params[:session_key] ||= @session_key
|
500
|
+
service.post_file(params.merge(:sig => signature_for(params.reject{|key, value| key.nil?})))
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
def self.configuration_file_path
|
506
|
+
@configuration_file_path || File.expand_path("~/.facebookerrc")
|
507
|
+
end
|
508
|
+
|
509
|
+
def self.configuration_file_path=(path)
|
510
|
+
@configuration_file_path = path
|
511
|
+
end
|
512
|
+
|
513
|
+
private
|
514
|
+
def add_facebook_params(hash, method)
|
515
|
+
hash[:method] = method
|
516
|
+
hash[:api_key] = @api_key
|
517
|
+
hash[:call_id] = Time.now.to_f.to_s unless method == 'facebook.auth.getSession'
|
518
|
+
hash[:v] = "1.0"
|
519
|
+
end
|
520
|
+
|
521
|
+
# This ultimately delgates to the adapter
|
522
|
+
def self.extract_key_from_environment(key_name)
|
523
|
+
Facebooker.send(key_name.to_s + "_key") rescue nil
|
524
|
+
end
|
525
|
+
|
526
|
+
def self.extract_key_from_configuration_file(key_name)
|
527
|
+
read_configuration_file[key_name]
|
528
|
+
end
|
529
|
+
|
530
|
+
def self.report_inability_to_find_key(key_name)
|
531
|
+
raise ConfigurationMissing, "Could not find configuration information for #{key_name}"
|
532
|
+
end
|
533
|
+
|
534
|
+
def self.read_configuration_file
|
535
|
+
eval(File.read(configuration_file_path))
|
536
|
+
end
|
537
|
+
|
538
|
+
def service
|
539
|
+
@service ||= Service.new(Facebooker.api_server_base, Facebooker.api_rest_path, @api_key)
|
540
|
+
end
|
541
|
+
|
542
|
+
def uid
|
543
|
+
@uid || (secure!; @uid)
|
544
|
+
end
|
545
|
+
|
546
|
+
def uid?
|
547
|
+
! @uid.nil?
|
548
|
+
end
|
549
|
+
|
550
|
+
def signature_for(params)
|
551
|
+
raw_string = params.inject([]) do |collection, pair|
|
552
|
+
collection << pair.join("=")
|
553
|
+
collection
|
554
|
+
end.sort.join
|
555
|
+
Digest::MD5.hexdigest([raw_string, secret_for_method(params[:method])].join)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
class CanvasSession < Session
|
560
|
+
def default_login_url_options
|
561
|
+
{:canvas => true}
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|