simianarmy-facebooker 1.0.40

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