facebooker-micah 1.0.74

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