tenderlove-facebooker 1.0.16.20090319151701

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/COPYING.rdoc +19 -0
  3. data/Manifest.txt +123 -0
  4. data/README.rdoc +103 -0
  5. data/Rakefile +86 -0
  6. data/TODO.rdoc +4 -0
  7. data/examples/desktop_login.rb +14 -0
  8. data/facebooker.gemspec +38 -0
  9. data/generators/facebook/facebook_generator.rb +14 -0
  10. data/generators/facebook/templates/config/facebooker.yml +46 -0
  11. data/generators/facebook/templates/public/javascripts/facebooker.js +99 -0
  12. data/generators/facebook_controller/USAGE +33 -0
  13. data/generators/facebook_controller/facebook_controller_generator.rb +40 -0
  14. data/generators/facebook_controller/templates/controller.rb +7 -0
  15. data/generators/facebook_controller/templates/functional_test.rb +11 -0
  16. data/generators/facebook_controller/templates/helper.rb +2 -0
  17. data/generators/facebook_controller/templates/view.fbml.erb +2 -0
  18. data/generators/facebook_controller/templates/view.html.erb +2 -0
  19. data/generators/facebook_publisher/facebook_publisher_generator.rb +14 -0
  20. data/generators/facebook_publisher/templates/create_facebook_templates.rb +15 -0
  21. data/generators/facebook_publisher/templates/publisher.rb +3 -0
  22. data/generators/facebook_scaffold/USAGE +27 -0
  23. data/generators/facebook_scaffold/facebook_scaffold_generator.rb +118 -0
  24. data/generators/facebook_scaffold/templates/controller.rb +93 -0
  25. data/generators/facebook_scaffold/templates/facebook_style.css +2579 -0
  26. data/generators/facebook_scaffold/templates/functional_test.rb +89 -0
  27. data/generators/facebook_scaffold/templates/helper.rb +2 -0
  28. data/generators/facebook_scaffold/templates/layout.fbml.erb +6 -0
  29. data/generators/facebook_scaffold/templates/layout.html.erb +17 -0
  30. data/generators/facebook_scaffold/templates/style.css +74 -0
  31. data/generators/facebook_scaffold/templates/view_edit.fbml.erb +13 -0
  32. data/generators/facebook_scaffold/templates/view_edit.html.erb +18 -0
  33. data/generators/facebook_scaffold/templates/view_index.fbml.erb +24 -0
  34. data/generators/facebook_scaffold/templates/view_index.html.erb +24 -0
  35. data/generators/facebook_scaffold/templates/view_new.fbml.erb +12 -0
  36. data/generators/facebook_scaffold/templates/view_new.html.erb +17 -0
  37. data/generators/facebook_scaffold/templates/view_show.fbml.erb +10 -0
  38. data/generators/facebook_scaffold/templates/view_show.html.erb +10 -0
  39. data/generators/publisher/publisher_generator.rb +14 -0
  40. data/generators/xd_receiver/templates/xd_receiver.html +10 -0
  41. data/generators/xd_receiver/xd_receiver_generator.rb +9 -0
  42. data/init.rb +72 -0
  43. data/install.rb +12 -0
  44. data/lib/facebooker/adapters/adapter_base.rb +87 -0
  45. data/lib/facebooker/adapters/bebo_adapter.rb +75 -0
  46. data/lib/facebooker/adapters/facebook_adapter.rb +52 -0
  47. data/lib/facebooker/admin.rb +42 -0
  48. data/lib/facebooker/batch_request.rb +44 -0
  49. data/lib/facebooker/data.rb +57 -0
  50. data/lib/facebooker/feed.rb +78 -0
  51. data/lib/facebooker/logging.rb +51 -0
  52. data/lib/facebooker/mobile.rb +20 -0
  53. data/lib/facebooker/mock/service.rb +50 -0
  54. data/lib/facebooker/mock/session.rb +18 -0
  55. data/lib/facebooker/model.rb +135 -0
  56. data/lib/facebooker/models/affiliation.rb +10 -0
  57. data/lib/facebooker/models/album.rb +11 -0
  58. data/lib/facebooker/models/applicationproperties.rb +39 -0
  59. data/lib/facebooker/models/applicationrestrictions.rb +10 -0
  60. data/lib/facebooker/models/cookie.rb +10 -0
  61. data/lib/facebooker/models/education_info.rb +11 -0
  62. data/lib/facebooker/models/event.rb +28 -0
  63. data/lib/facebooker/models/friend_list.rb +16 -0
  64. data/lib/facebooker/models/group.rb +36 -0
  65. data/lib/facebooker/models/info_item.rb +10 -0
  66. data/lib/facebooker/models/info_section.rb +10 -0
  67. data/lib/facebooker/models/location.rb +8 -0
  68. data/lib/facebooker/models/notifications.rb +17 -0
  69. data/lib/facebooker/models/page.rb +27 -0
  70. data/lib/facebooker/models/photo.rb +12 -0
  71. data/lib/facebooker/models/tag.rb +12 -0
  72. data/lib/facebooker/models/user.rb +426 -0
  73. data/lib/facebooker/models/video.rb +9 -0
  74. data/lib/facebooker/models/work_info.rb +9 -0
  75. data/lib/facebooker/parser.rb +589 -0
  76. data/lib/facebooker/rails/controller.rb +304 -0
  77. data/lib/facebooker/rails/cucumber/world.rb +46 -0
  78. data/lib/facebooker/rails/cucumber.rb +28 -0
  79. data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
  80. data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
  81. data/lib/facebooker/rails/facebook_request_fix.rb +24 -0
  82. data/lib/facebooker/rails/facebook_session_handling.rb +69 -0
  83. data/lib/facebooker/rails/facebook_url_helper.rb +192 -0
  84. data/lib/facebooker/rails/facebook_url_rewriting.rb +39 -0
  85. data/lib/facebooker/rails/helpers/fb_connect.rb +89 -0
  86. data/lib/facebooker/rails/helpers.rb +762 -0
  87. data/lib/facebooker/rails/integration_session.rb +38 -0
  88. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  89. data/lib/facebooker/rails/publisher.rb +526 -0
  90. data/lib/facebooker/rails/routing.rb +49 -0
  91. data/lib/facebooker/rails/test_helpers.rb +68 -0
  92. data/lib/facebooker/rails/utilities.rb +22 -0
  93. data/lib/facebooker/server_cache.rb +24 -0
  94. data/lib/facebooker/service.rb +94 -0
  95. data/lib/facebooker/session.rb +584 -0
  96. data/lib/facebooker/version.rb +9 -0
  97. data/lib/facebooker.rb +174 -0
  98. data/lib/net/http_multipart_post.rb +123 -0
  99. data/lib/tasks/facebooker.rake +18 -0
  100. data/lib/tasks/tunnel.rake +46 -0
  101. data/rails/init.rb +1 -0
  102. data/setup.rb +1585 -0
  103. data/templates/layout.erb +24 -0
  104. data/test/facebooker/adapters_test.rb +96 -0
  105. data/test/facebooker/admin_test.rb +102 -0
  106. data/test/facebooker/batch_request_test.rb +83 -0
  107. data/test/facebooker/data_test.rb +86 -0
  108. data/test/facebooker/logging_test.rb +43 -0
  109. data/test/facebooker/mobile_test.rb +45 -0
  110. data/test/facebooker/model_test.rb +123 -0
  111. data/test/facebooker/models/event_test.rb +15 -0
  112. data/test/facebooker/models/user_test.rb +295 -0
  113. data/test/facebooker/rails/publisher_test.rb +452 -0
  114. data/test/facebooker/rails_integration_test.rb +1312 -0
  115. data/test/facebooker/server_cache_test.rb +44 -0
  116. data/test/facebooker/session_test.rb +614 -0
  117. data/test/facebooker_test.rb +925 -0
  118. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  119. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  120. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  121. data/test/net/http_multipart_post_test.rb +52 -0
  122. data/test/rails_test_helper.rb +11 -0
  123. data/test/test_helper.rb +66 -0
  124. metadata +217 -0
@@ -0,0 +1,584 @@
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
+ Thread.current['facebook_session']
82
+ end
83
+
84
+ def self.current=(session)
85
+ Thread.current['facebook_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
+ # 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
110
+ def permission_url(permission,options={})
111
+ options = default_login_url_options.merge(options)
112
+ "http://#{Facebooker.www_server_base_url}/authorize.php?api_key=#{@api_key}&v=1.0&ext_perm=#{permission}#{install_url_optional_parameters(options)}"
113
+ end
114
+
115
+ def install_url_optional_parameters(options)
116
+ optional_parameters = []
117
+ optional_parameters += add_next_parameters(options)
118
+ optional_parameters.join
119
+ end
120
+
121
+ def add_next_parameters(options)
122
+ opts = []
123
+ opts << "&next=#{CGI.escape(options[:next])}" if options[:next]
124
+ opts << "&next_cancel=#{CGI.escape(options[:next_cancel])}" if options[:next_cancel]
125
+ opts
126
+ end
127
+
128
+ def login_url_optional_parameters(options)
129
+ # It is important that unused options are omitted as stuff like &canvas=false will still display the canvas.
130
+ optional_parameters = []
131
+ optional_parameters += add_next_parameters(options)
132
+ optional_parameters << "&skipcookie=true" if options[:skip_cookie]
133
+ optional_parameters << "&hide_checkbox=true" if options[:hide_checkbox]
134
+ optional_parameters << "&canvas=true" if options[:canvas]
135
+ optional_parameters.join
136
+ end
137
+
138
+ def default_login_url_options
139
+ {}
140
+ end
141
+
142
+ def initialize(api_key, secret_key)
143
+ @api_key = api_key
144
+ @secret_key = secret_key
145
+ end
146
+
147
+ def secret_for_method(method_name)
148
+ @secret_key
149
+ end
150
+
151
+ def auth_token
152
+ @auth_token ||= post 'facebook.auth.createToken'
153
+ end
154
+
155
+ def infinite?
156
+ @expires == 0
157
+ end
158
+
159
+ def expired?
160
+ @expires.nil? || (!infinite? && Time.at(@expires) <= Time.now)
161
+ end
162
+
163
+ def secured?
164
+ !@session_key.nil? && !expired?
165
+ end
166
+
167
+ def secure!
168
+ response = post 'facebook.auth.getSession', :auth_token => auth_token
169
+ secure_with!(response['session_key'], response['uid'], response['expires'], response['secret'])
170
+ end
171
+
172
+ def secure_with!(session_key, uid = nil, expires = nil, secret_from_session = nil)
173
+ @session_key = session_key
174
+ @uid = uid ? Integer(uid) : post('facebook.users.getLoggedInUser', :session_key => session_key)
175
+ @expires = Integer(expires)
176
+ @secret_from_session = secret_from_session
177
+ end
178
+
179
+ def fql_query(query, format = 'XML')
180
+ post('facebook.fql.query', :query => query, :format => format) do |response|
181
+ type = response.shift
182
+ return [] if type.nil?
183
+ response.shift.map do |hash|
184
+ case type
185
+ when 'user'
186
+ user = User.new
187
+ user.session = self
188
+ user.populate_from_hash!(hash)
189
+ user
190
+ when 'photo'
191
+ Photo.from_hash(hash)
192
+ when 'page'
193
+ Page.from_hash(hash)
194
+ when 'page_admin'
195
+ Page.from_hash(hash)
196
+ when 'event_member'
197
+ Event::Attendance.from_hash(hash)
198
+ else
199
+ hash
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ def user
206
+ @user ||= User.new(uid, self)
207
+ end
208
+
209
+ #
210
+ # This one has so many parameters, a Hash seemed cleaner than a long param list. Options can be:
211
+ # :uid => Filter by events associated with a user with this uid
212
+ # :eids => Filter by this list of event ids. This is a comma-separated list of eids.
213
+ # :start_time => Filter with this UTC as lower bound. A missing or zero parameter indicates no lower bound. (Time or Integer)
214
+ # :end_time => Filter with this UTC as upper bound. A missing or zero parameter indicates no upper bound. (Time or Integer)
215
+ # :rsvp_status => Filter by this RSVP status.
216
+ def events(options = {})
217
+ @events ||= post('facebook.events.get', options) do |response|
218
+ response.map do |hash|
219
+ Event.from_hash(hash)
220
+ end
221
+ end
222
+ end
223
+
224
+ def event_members(eid)
225
+ @members ||= post('facebook.events.getMembers', :eid => eid) do |response|
226
+ response.map do |attendee_hash|
227
+ Event::Attendance.from_hash(attendee_hash)
228
+ end
229
+ end
230
+ end
231
+
232
+ def users_standard(user_ids, fields=[])
233
+ post("facebook.users.getStandardInfo",:uids=>user_ids.join(","),:fields=>User.standard_fields(fields)) do |users|
234
+ users.map { |u| User.new(u)}
235
+ end
236
+ end
237
+
238
+ def users(user_ids, fields=[])
239
+ post("facebook.users.getInfo",:uids=>user_ids.join(","),:fields=>User.user_fields(fields)) do |users|
240
+ users.map { |u| User.new(u)}
241
+ end
242
+ end
243
+
244
+ def pages(options = {})
245
+ raise ArgumentError, 'fields option is mandatory' unless options.has_key?(:fields)
246
+ @pages ||= {}
247
+ @pages[options] ||= post('facebook.pages.getInfo', options) do |response|
248
+ response.map do |hash|
249
+ Page.from_hash(hash)
250
+ end
251
+ end
252
+ end
253
+
254
+ # Takes page_id and uid, returns true if uid is a fan of the page_id
255
+ def is_fan(page_id, uid)
256
+ post('facebook.pages.isFan', :page_id=>page_id, :uid=>uid)
257
+ end
258
+
259
+ #
260
+ # Returns a proxy object for handling calls to Facebook cached items
261
+ # such as images and FBML ref handles
262
+ def server_cache
263
+ Facebooker::ServerCache.new(self)
264
+ end
265
+
266
+ #
267
+ # Returns a proxy object for handling calls to the Facebook Data API
268
+ def data
269
+ Facebooker::Data.new(self)
270
+ end
271
+
272
+ def admin
273
+ Facebooker::Admin.new(self)
274
+ end
275
+
276
+ def mobile
277
+ Facebooker::Mobile.new(self)
278
+ end
279
+
280
+ #
281
+ # Given an array like:
282
+ # [[userid, otheruserid], [yetanotherid, andanotherid]]
283
+ # returns a Hash indicating friendship of those pairs:
284
+ # {[userid, otheruserid] => true, [yetanotherid, andanotherid] => false}
285
+ # if one of the Hash values is nil, it means the facebook platform's answer is "I don't know"
286
+ def check_friendship(array_of_pairs_of_users)
287
+ uids1 = []
288
+ uids2 = []
289
+ array_of_pairs_of_users.each do |pair|
290
+ uids1 << pair.first
291
+ uids2 << pair.last
292
+ end
293
+ post('facebook.friends.areFriends', :uids1 => uids1.join(','), :uids2 => uids2.join(','))
294
+ end
295
+
296
+ def get_photos(pids = nil, subj_id = nil, aid = nil)
297
+ if [subj_id, pids, aid].all? {|arg| arg.nil?}
298
+ raise ArgumentError, "Can't get a photo without a picture, album or subject ID"
299
+ end
300
+ @photos = post('facebook.photos.get', :subj_id => subj_id, :pids => pids, :aid => aid ) do |response|
301
+ response.map do |hash|
302
+ Photo.from_hash(hash)
303
+ end
304
+ end
305
+ end
306
+
307
+ def get_albums(aids)
308
+ @albums = post('facebook.photos.getAlbums', :aids => aids) do |response|
309
+ response.map do |hash|
310
+ Album.from_hash(hash)
311
+ end
312
+ end
313
+ end
314
+
315
+ def get_tags(pids)
316
+ @tags = post('facebook.photos.getTags', :pids => pids) do |response|
317
+ response.map do |hash|
318
+ Tag.from_hash(hash)
319
+ end
320
+ end
321
+ end
322
+
323
+ def add_tags(pid, x, y, tag_uid = nil, tag_text = nil )
324
+ if [tag_uid, tag_text].all? {|arg| arg.nil?}
325
+ raise ArgumentError, "Must enter a name or string for this tag"
326
+ end
327
+ @tags = post('facebook.photos.addTag', :pid => pid, :tag_uid => tag_uid, :tag_text => tag_text, :x => x, :y => y )
328
+ end
329
+
330
+ def send_notification(user_ids, fbml, email_fbml = nil)
331
+ params = {:notification => fbml, :to_ids => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(',')}
332
+ if email_fbml
333
+ params[:email] = email_fbml
334
+ end
335
+ params[:type]="user_to_user"
336
+ # if there is no uid, this is an announcement
337
+ unless uid?
338
+ params[:type]="app_to_user"
339
+ end
340
+
341
+ post 'facebook.notifications.send', params,uid?
342
+ end
343
+
344
+ ##
345
+ # Register a template bundle with Facebook.
346
+ # returns the template id to use to send using this template
347
+ def register_template_bundle(one_line_story_templates,short_story_templates=nil,full_story_template=nil, action_links=nil)
348
+ parameters = {:one_line_story_templates => Array(one_line_story_templates).to_json}
349
+
350
+ parameters[:action_links] = action_links.to_json unless action_links.blank?
351
+
352
+ parameters[:short_story_templates] = Array(short_story_templates).to_json unless short_story_templates.blank?
353
+
354
+ parameters[:full_story_template] = full_story_template.to_json unless full_story_template.blank?
355
+
356
+ post("facebook.feed.registerTemplateBundle", parameters, false)
357
+ end
358
+
359
+ ##
360
+ # publish a previously rendered template bundle
361
+ # see http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
362
+ #
363
+ def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil)
364
+ parameters={:template_bundle_id=>bundle_id,:template_data=>data.to_json}
365
+ parameters[:target_ids] = target_ids unless target_ids.blank?
366
+ parameters[:body_general] = body_general unless body_general.blank?
367
+ post("facebook.feed.publishUserAction", parameters)
368
+ end
369
+
370
+
371
+ ##
372
+ # Send email to as many as 100 users at a time
373
+ def send_email(user_ids, subject, text, fbml = nil)
374
+ user_ids = Array(user_ids)
375
+ params = {:fbml => fbml, :recipients => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(','), :text => text, :subject => subject}
376
+ post 'facebook.notifications.sendEmail', params
377
+ end
378
+
379
+ # Only serialize the bare minimum to recreate the session.
380
+ def marshal_load(variables)#:nodoc:
381
+ fields_to_serialize.each_with_index{|field, index| instance_variable_set_value(field, variables[index])}
382
+ end
383
+
384
+ # Only serialize the bare minimum to recreate the session.
385
+ def marshal_dump#:nodoc:
386
+ fields_to_serialize.map{|field| instance_variable_value(field)}
387
+ end
388
+
389
+ # Only serialize the bare minimum to recreate the session.
390
+ def to_yaml( opts = {} )
391
+ YAML::quick_emit(self.object_id, opts) do |out|
392
+ out.map(taguri) do |map|
393
+ fields_to_serialize.each do |field|
394
+ map.add(field, instance_variable_value(field))
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ def instance_variable_set_value(field, value)
401
+ self.instance_variable_set("@#{field}", value)
402
+ end
403
+
404
+ def instance_variable_value(field)
405
+ self.instance_variable_get("@#{field}")
406
+ end
407
+
408
+ def fields_to_serialize
409
+ %w(session_key uid expires secret_from_session auth_token api_key secret_key)
410
+ end
411
+
412
+ class Desktop < Session
413
+ def login_url
414
+ super + "&auth_token=#{auth_token}"
415
+ end
416
+
417
+ def secret_for_method(method_name)
418
+ secret = auth_request_methods.include?(method_name) ? super : @secret_from_session
419
+ secret
420
+ end
421
+
422
+ def post(method, params = {},use_session=false)
423
+ if method == 'facebook.profile.getFBML' || method == 'facebook.profile.setFBML'
424
+ raise NonSessionUser.new("User #{@uid} is not the logged in user.") unless @uid == params[:uid]
425
+ end
426
+ super
427
+ end
428
+ private
429
+ def auth_request_methods
430
+ ['facebook.auth.getSession', 'facebook.auth.createToken']
431
+ end
432
+ end
433
+
434
+ def batch_request?
435
+ @batch_request
436
+ end
437
+
438
+ def add_to_batch(req,&proc)
439
+ batch_request = BatchRequest.new(req,proc)
440
+ Thread.current[:facebooker_current_batch_queue]<<batch_request
441
+ batch_request
442
+ end
443
+
444
+ # Submit the enclosed requests for this session inside a batch
445
+ #
446
+ # All requests will be sent to Facebook at the end of the block
447
+ # each method inside the block will return a proxy object
448
+ # attempting to access the proxy before the end of the block will yield an exception
449
+ #
450
+ # For Example:
451
+ #
452
+ # facebook_session.batch do
453
+ # @send_result = facebook_session.send_notification([12451752],"Woohoo")
454
+ # @albums = facebook_session.user.albums
455
+ # end
456
+ # puts @albums.first.inspect
457
+ #
458
+ # is valid, however
459
+ #
460
+ # facebook_session.batch do
461
+ # @send_result = facebook_session.send_notification([12451752],"Woohoo")
462
+ # @albums = facebook_session.user.albums
463
+ # puts @albums.first.inspect
464
+ # end
465
+ #
466
+ # will raise Facebooker::BatchRequest::UnexecutedRequest
467
+ #
468
+ # If an exception is raised while processing the result, that exception will be
469
+ # re-raised on the next access to that object or when exception_raised? is called
470
+ #
471
+ # for example, if the send_notification resulted in TooManyUserCalls being raised,
472
+ # calling
473
+ # @send_result.exception_raised?
474
+ # would re-raise that exception
475
+ # if there was an error retrieving the albums, it would be re-raised when
476
+ # @albums.first
477
+ # is called
478
+ #
479
+ def batch(serial_only=false)
480
+ @batch_request=true
481
+ Thread.current[:facebooker_current_batch_queue]=[]
482
+ yield
483
+ # Set the batch request to false so that post will execute the batch job
484
+ @batch_request=false
485
+ BatchRun.current_batch=Thread.current[:facebooker_current_batch_queue]
486
+ post("facebook.batch.run",:method_feed=>BatchRun.current_batch.map{|q| q.uri}.to_json,:serial_only=>serial_only.to_s)
487
+ ensure
488
+ @batch_request=false
489
+ BatchRun.current_batch=nil
490
+ end
491
+
492
+ def post_without_logging(method, params = {}, use_session_key = true, &proc)
493
+ add_facebook_params(params, method)
494
+ use_session_key && @session_key && params[:session_key] ||= @session_key
495
+ final_params=params.merge(:sig => signature_for(params))
496
+ if batch_request?
497
+ add_to_batch(final_params,&proc)
498
+ else
499
+ result = service.post(final_params)
500
+ result = yield result if block_given?
501
+ result
502
+ end
503
+ end
504
+
505
+ def post(method, params = {}, use_session_key = true, &proc)
506
+ if batch_request?
507
+ post_without_logging(method, params, use_session_key, &proc)
508
+ else
509
+ Logging.log_fb_api(method, params) do
510
+ post_without_logging(method, params, use_session_key, &proc)
511
+ end
512
+ end
513
+ end
514
+
515
+ def post_file(method, params = {})
516
+ base = params.delete(:base)
517
+ Logging.log_fb_api(method, params) do
518
+ add_facebook_params(params, method)
519
+ @session_key && params[:session_key] ||= @session_key
520
+ service.post_file(params.merge(:base => base, :sig => signature_for(params.reject{|key, value| key.nil?})))
521
+ end
522
+ end
523
+
524
+
525
+ def self.configuration_file_path
526
+ @configuration_file_path || File.expand_path("~/.facebookerrc")
527
+ end
528
+
529
+ def self.configuration_file_path=(path)
530
+ @configuration_file_path = path
531
+ end
532
+
533
+ private
534
+ def add_facebook_params(hash, method)
535
+ hash[:method] = method
536
+ hash[:api_key] = @api_key
537
+ hash[:call_id] = Time.now.to_f.to_s unless method == 'facebook.auth.getSession'
538
+ hash[:v] = "1.0"
539
+ end
540
+
541
+ # This ultimately delgates to the adapter
542
+ def self.extract_key_from_environment(key_name)
543
+ Facebooker.send(key_name.to_s + "_key") rescue nil
544
+ end
545
+
546
+ def self.extract_key_from_configuration_file(key_name)
547
+ read_configuration_file[key_name]
548
+ end
549
+
550
+ def self.report_inability_to_find_key(key_name)
551
+ raise ConfigurationMissing, "Could not find configuration information for #{key_name}"
552
+ end
553
+
554
+ def self.read_configuration_file
555
+ eval(File.read(configuration_file_path))
556
+ end
557
+
558
+ def service
559
+ @service ||= Service.new(Facebooker.api_server_base, Facebooker.api_rest_path, @api_key)
560
+ end
561
+
562
+ def uid
563
+ @uid || (secure!; @uid)
564
+ end
565
+
566
+ def uid?
567
+ !! @uid
568
+ end
569
+
570
+ def signature_for(params)
571
+ raw_string = params.inject([]) do |collection, pair|
572
+ collection << pair.join("=")
573
+ collection
574
+ end.sort.join
575
+ Digest::MD5.hexdigest([raw_string, secret_for_method(params[:method])].join)
576
+ end
577
+ end
578
+
579
+ class CanvasSession < Session
580
+ def default_login_url_options
581
+ {:canvas => true}
582
+ end
583
+ end
584
+ end
@@ -0,0 +1,9 @@
1
+ module Facebooker #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 16
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end