fbooker 1.0.53

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