corey-facebooker 1.0.28.1

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