facebooker 1.0.18 → 1.0.29

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 (41) hide show
  1. data/{History.txt → CHANGELOG.rdoc} +0 -0
  2. data/{COPYING → COPYING.rdoc} +0 -0
  3. data/{README.txt → README.rdoc} +11 -4
  4. data/Rakefile +18 -15
  5. data/{TODO.txt → TODO.rdoc} +0 -0
  6. data/generators/facebook/templates/config/facebooker.yml +3 -0
  7. data/init.rb +12 -61
  8. data/lib/facebooker.rb +22 -15
  9. data/lib/facebooker/adapters/adapter_base.rb +3 -0
  10. data/lib/facebooker/logging.rb +1 -1
  11. data/lib/facebooker/model.rb +6 -4
  12. data/lib/facebooker/models/user.rb +39 -4
  13. data/lib/facebooker/parser.rb +14 -0
  14. data/lib/facebooker/rails/controller.rb +34 -10
  15. data/lib/facebooker/rails/extensions/action_controller.rb +48 -0
  16. data/lib/facebooker/rails/extensions/rack_setup.rb +2 -0
  17. data/lib/facebooker/rails/extensions/routing.rb +15 -0
  18. data/lib/facebooker/rails/facebook_url_helper.rb +3 -3
  19. data/lib/facebooker/rails/facebook_url_rewriting.rb +18 -5
  20. data/lib/facebooker/rails/helpers.rb +19 -2
  21. data/lib/facebooker/rails/helpers/fb_connect.rb +20 -10
  22. data/lib/facebooker/rails/publisher.rb +9 -5
  23. data/lib/facebooker/service.rb +1 -2
  24. data/lib/facebooker/session.rb +13 -1
  25. data/lib/facebooker/version.rb +1 -1
  26. data/lib/rack/facebook.rb +77 -0
  27. data/lib/tasks/tunnel.rake +3 -3
  28. data/test/facebooker/logging_test.rb +2 -2
  29. data/test/facebooker/models/user_test.rb +39 -3
  30. data/test/facebooker/rails/publisher_test.rb +19 -3
  31. data/test/facebooker/rails_integration_test.rb +52 -6
  32. data/test/rack/facebook_test.rb +62 -0
  33. data/test/rails_test_helper.rb +2 -0
  34. metadata +21 -27
  35. data/CHANGELOG.txt +0 -0
  36. data/Manifest.txt +0 -127
  37. data/README +0 -46
  38. data/lib/facebooker/models/user.rb.orig +0 -396
  39. data/lib/facebooker/models/user.rb.rej +0 -17
  40. data/lib/facebooker/session.rb.orig +0 -564
  41. data/lib/facebooker/session.rb.rej +0 -29
@@ -0,0 +1,48 @@
1
+ module ::ActionController
2
+ class Base
3
+ def self.inherited_with_facebooker(subclass)
4
+ inherited_without_facebooker(subclass)
5
+ if subclass.to_s == "ApplicationController"
6
+ subclass.send(:include,Facebooker::Rails::Controller)
7
+ subclass.helper Facebooker::Rails::Helpers
8
+ end
9
+ end
10
+ class << self
11
+ alias_method_chain :inherited, :facebooker
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ # When making get requests, Facebook sends fb_sig parameters both in the query string
18
+ # and also in the post body. We want to ignore the query string ones because they are one
19
+ # request out of date
20
+ # We only do thise when there are POST parameters so that IFrame linkage still works
21
+ if Rails.version < '2.3'
22
+ class ActionController::AbstractRequest
23
+ def query_parameters_with_facebooker
24
+ if request_parameters.blank?
25
+ query_parameters_without_facebooker
26
+ else
27
+ (query_parameters_without_facebooker||{}).reject {|key,value| key.to_s =~ /^fb_sig/}
28
+ end
29
+ end
30
+
31
+ alias_method_chain :query_parameters, :facebooker
32
+ end
33
+ else
34
+ class ActionController::Request
35
+ def query_parameters_with_facebooker
36
+ if request_parameters.blank?
37
+ query_parameters_without_facebooker
38
+ else
39
+ (query_parameters_without_facebooker||{}).reject {|key,value| key.to_s =~ /^fb_sig/}
40
+ end
41
+ end
42
+
43
+ alias_method_chain :query_parameters, :facebooker
44
+ end
45
+ end
46
+
47
+ Mime::Type.register_alias "text/html", :fbml
48
+ Mime::Type.register_alias "text/javascript", :fbjs
@@ -0,0 +1,2 @@
1
+ require 'rack/facebook'
2
+ ActionController::Dispatcher.middleware.insert_after 'ActionController::RewindableInput',Rack::Facebook, Facebooker.secret_key
@@ -0,0 +1,15 @@
1
+ class ActionController::Routing::Route
2
+ def recognition_conditions_with_facebooker
3
+ defaults = recognition_conditions_without_facebooker
4
+ defaults << " env[:canvas] == conditions[:canvas] " if conditions[:canvas]
5
+ defaults
6
+ end
7
+ alias_method_chain :recognition_conditions, :facebooker
8
+ end
9
+
10
+ # We turn off route optimization to make named routes use our code for figuring out if they should go to the session
11
+ ActionController::Base::optimise_named_routes = false
12
+
13
+ # pull :canvas=> into env in routing to allow for conditions
14
+ ActionController::Routing::RouteSet.send :include, Facebooker::Rails::Routing::RouteSetExtensions
15
+ ActionController::Routing::RouteSet::Mapper.send :include, Facebooker::Rails::Routing::MapperExtensions
@@ -50,7 +50,7 @@ module ActionView
50
50
  module UrlHelper
51
51
  # Alters one and only one line of the Rails button_to. See below.
52
52
  def button_to_with_facebooker(name, options={}, html_options = {})
53
- if !request_comes_from_facebook?
53
+ if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook?
54
54
  button_to_without_facebooker(name,options,html_options)
55
55
  else
56
56
  html_options = html_options.stringify_keys
@@ -125,7 +125,7 @@ module ActionView
125
125
  # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?"})
126
126
  # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?", :color=>"pink"})
127
127
  def confirm_javascript_function_with_facebooker(confirm, fun = nil)
128
- if !request_comes_from_facebook?
128
+ if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook?
129
129
  confirm_javascript_function_without_facebooker(confirm)
130
130
  else
131
131
  if(confirm.is_a?(Hash))
@@ -157,7 +157,7 @@ module ActionView
157
157
  # Dynamically creates a form for link_to with method. Calls confirm_javascript_function if and
158
158
  # only if (confirm && method) for link_to
159
159
  def method_javascript_function_with_facebooker(method, url = '', href = nil, confirm = nil)
160
- if !request_comes_from_facebook?
160
+ if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook?
161
161
  method_javascript_function_without_facebooker(method,url,href)
162
162
  else
163
163
  action = (href && url.size > 0) ? "'#{url}'" : 'a.getHref()'
@@ -1,8 +1,16 @@
1
1
  module ::ActionController
2
- class AbstractRequest
3
- def relative_url_root
4
- Facebooker.path_prefix
5
- end
2
+ if Rails.version < '2.3'
3
+ class AbstractRequest
4
+ def relative_url_root
5
+ Facebooker.path_prefix
6
+ end
7
+ end
8
+ else
9
+ class Request
10
+ def relative_url_root
11
+ Facebooker.path_prefix
12
+ end
13
+ end
6
14
  end
7
15
 
8
16
  class Base
@@ -19,7 +27,12 @@ module ::ActionController
19
27
  def link_to_canvas?(params, options)
20
28
  option_override = options[:canvas]
21
29
  return false if option_override == false # important to check for false. nil should use default behavior
22
- option_override || @request.parameters["fb_sig_in_canvas"] == "1" || @request.parameters[:fb_sig_in_canvas] == "1"
30
+ option_override || (can_safely_access_request_parameters? && (@request.parameters["fb_sig_in_canvas"] == "1" || @request.parameters[:fb_sig_in_canvas] == "1" ))
31
+ end
32
+
33
+ #rails blindly tries to merge things that may be nil into the parameters. Make sure this won't break
34
+ def can_safely_access_request_parameters?
35
+ @request.request_parameters
23
36
  end
24
37
 
25
38
  def rewrite_url_with_facebooker(*args)
@@ -44,6 +44,13 @@ module Facebooker
44
44
 
45
45
  FB_DIALOG_BUTTON_VALID_OPTION_KEYS = [:close_dialog, :href, :form_id, :clickrewriteurl, :clickrewriteid, :clickrewriteform]
46
46
 
47
+ def fb_show_feed_dialog(action, user_message = "", prompt = "", callback = nil)
48
+ update_page do |page|
49
+ page.call "Facebook.showFeedDialog",action.template_id,action.data,action.body_general,action.target_ids,callback,prompt,user_message
50
+ end
51
+ end
52
+
53
+
47
54
  # Create an fb:request-form without a selector
48
55
  #
49
56
  # The block passed to this tag is used as the content of the form
@@ -282,15 +289,25 @@ module Facebooker
282
289
 
283
290
  # Render an <fb:profile-pic> for the specified user.
284
291
  #
285
- # You can optionally specify the size using the :size=> option.
292
+ # You can optionally specify the size using the :size=> option. Valid
293
+ # sizes are :thumb, :small, :normal and :square. Or, you can specify
294
+ # width and height settings instead, just like an img tag.
295
+ #
296
+ # You can optionally specify whether or not to include the facebook icon
297
+ # overlay using the :facebook_logo => true option. Default is false.
286
298
  #
287
- # Valid sizes are :thumb, :small, :normal and :square
288
299
  def fb_profile_pic(user, options={})
289
300
  options = options.dup
290
301
  validate_fb_profile_pic_size(options)
302
+ options.transform_keys!(FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM)
303
+ options.assert_valid_keys(FB_PROFILE_PIC_VALID_OPTION_KEYS)
291
304
  options.merge!(:uid => cast_to_facebook_id(user))
292
305
  content_tag("fb:profile-pic", nil,stringify_vals(options))
293
306
  end
307
+
308
+ FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM = {:facebook_logo => 'facebook-logo'}
309
+ FB_PROFILE_PIC_VALID_OPTION_KEYS = [:size, :linked, 'facebook-logo', :width, :height]
310
+
294
311
 
295
312
  # Render an fb:photo tag.
296
313
  # photo is either a Facebooker::Photo or an id of a Facebook photo or an object that responds to photo_id.
@@ -16,10 +16,19 @@ module Facebooker
16
16
  if block_given?
17
17
  additions = capture(&proc)
18
18
  end
19
+
20
+ options = {:js => :prototype}
21
+ if required_features.last.is_a?(Hash)
22
+ options.merge!(required_features.pop.symbolize_keys)
23
+ end
24
+
19
25
  init_string = "FB.Facebook.init('#{Facebooker.api_key}','/xd_receiver.html');"
20
26
  unless required_features.blank?
21
27
  init_string = <<-FBML
22
- Element.observe(window,'load', function() {
28
+ #{case options[:js]
29
+ when :jquery then "$(document).ready("
30
+ else "Element.observe(window,'load',"
31
+ end} function() {
23
32
  FB_RequireFeatures(#{required_features.to_json}, function() {
24
33
  #{init_string}
25
34
  #{additions}
@@ -27,8 +36,8 @@ module Facebooker
27
36
  });
28
37
  FBML
29
38
  end
30
- if block_given?
31
- concat javascript_tag(init_string)
39
+ if block_given? && block_is_within_action_view?(proc)
40
+ concat(javascript_tag(init_string), proc.binding)
32
41
  else
33
42
  javascript_tag init_string
34
43
  end
@@ -54,32 +63,33 @@ module Facebooker
54
63
  def fb_login_button(*args)
55
64
 
56
65
  callback = args.first
57
- options = args.second || {}
66
+ options = args[1] || {}
58
67
  options.merge!(:onlogin=>callback)if callback
59
68
 
60
69
  content_tag("fb:login-button",nil, options)
61
70
  end
62
- def fb_login_and_redirect(url)
71
+
72
+ def fb_login_and_redirect(url, options = {})
63
73
  js = update_page do |page|
64
74
  page.redirect_to url
65
75
  end
66
- content_tag("fb:login-button",nil,:onlogin=>js)
76
+ content_tag("fb:login-button",nil,options.merge(:onlogin=>js))
67
77
  end
68
78
 
69
79
  def fb_unconnected_friends_count
70
80
  content_tag "fb:unconnected-friends-count",nil
71
81
  end
72
82
 
73
- def fb_logout_link(text,url)
83
+ def fb_logout_link(text,url,*args)
74
84
  js = update_page do |page|
75
85
  page.call "FB.Connect.logoutAndRedirect",url
76
86
  end
77
- link_to_function text, js
87
+ link_to_function text, js, *args
78
88
  end
79
89
 
80
- def fb_user_action(action)
90
+ def fb_user_action(action, user_message = "", prompt = "", callback = nil)
81
91
  update_page do |page|
82
- page.call "FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,"FB.RequireConnect.promptConnect"
92
+ page.call "FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,page.literal("FB.RequireConnect.promptConnect"),callback,prompt,user_message
83
93
  end
84
94
  end
85
95
 
@@ -224,12 +224,12 @@ module Facebooker
224
224
  attr_accessor :template_id
225
225
  attr_accessor :template_name
226
226
  attr_accessor :story_size
227
+
227
228
  def target_ids=(val)
228
229
  @target_ids = val.is_a?(Array) ? val.join(",") : val
229
230
  end
230
231
  def data_hash
231
- default_data = story_size.nil? ? {} : {:story_size=>story_size}
232
- default_data.merge(data||{})
232
+ data||{}
233
233
  end
234
234
  end
235
235
 
@@ -377,7 +377,7 @@ module Facebooker
377
377
  when Ref
378
378
  Facebooker::Session.create.server_cache.set_ref_handle(_body.handle,_body.fbml)
379
379
  when UserAction
380
- @from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general)
380
+ @from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general,_body.story_size)
381
381
  else
382
382
  raise UnspecifiedBodyType.new("You must specify a valid send_as")
383
383
  end
@@ -404,7 +404,10 @@ module Facebooker
404
404
  #only do this on Rails 2.1
405
405
  if ActionController::Base.respond_to?(:append_view_path)
406
406
  # only add the view path once
407
- ActionController::Base.append_view_path(controller_root) unless ActionController::Base.view_paths.include?(controller_root)
407
+ unless ActionController::Base.view_paths.include?(controller_root)
408
+ ActionController::Base.append_view_path(controller_root)
409
+ ActionController::Base.append_view_path(controller_root+"/..")
410
+ end
408
411
  end
409
412
  returning ActionView::Base.new([template_root,controller_root], assigns, self) do |template|
410
413
  template.controller=self
@@ -427,6 +430,7 @@ module Facebooker
427
430
  include ActionView::Helpers::FormHelper
428
431
  include ActionView::Helpers::FormTagHelper
429
432
  include ActionView::Helpers::AssetTagHelper
433
+ include ActionView::Helpers::NumberHelper
430
434
  include Facebooker::Rails::Helpers
431
435
 
432
436
  #define this for the publisher views
@@ -471,7 +475,7 @@ module Facebooker
471
475
  case publisher._body
472
476
  when UserAction
473
477
  publisher._body.template_name = method
474
- publisher._body.template_id = FacebookTemplate.bundle_id_for_class_and_method(self,method)
478
+ publisher._body.template_id ||= FacebookTemplate.bundle_id_for_class_and_method(self,method)
475
479
  end
476
480
 
477
481
  should_send ? publisher.send_message(method) : publisher._body
@@ -2,7 +2,6 @@ begin
2
2
  require 'curb'
3
3
  Facebooker.use_curl = true
4
4
  rescue LoadError
5
- $stderr.puts "Curb not found. Using Net::HTTP."
6
5
  require 'net/http'
7
6
  end
8
7
  require 'facebooker/parser'
@@ -92,4 +91,4 @@ module Facebooker
92
91
  parray
93
92
  end
94
93
  end
95
- end
94
+ end
@@ -6,7 +6,15 @@ module Facebooker
6
6
  # other than the logged in user (if that's unallowed)
7
7
  class NonSessionUser < StandardError; end
8
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
9
16
  class SessionExpired < StandardError; end
17
+
10
18
  class UnknownError < StandardError; end
11
19
  class ServiceUnavailable < StandardError; end
12
20
  class MaxRequestsDepleted < StandardError; end
@@ -48,6 +56,9 @@ module Facebooker
48
56
  class TooManyUnapprovedPhotosPending < StandardError; end
49
57
  class ExtendedPermissionRequired < StandardError; end
50
58
  class InvalidFriendList < StandardError; end
59
+ class UserUnRegistrationFailed < StandardError
60
+ attr_accessor :failed_users
61
+ end
51
62
  class UserRegistrationFailed < StandardError
52
63
  attr_accessor :failed_users
53
64
  end
@@ -360,10 +371,11 @@ module Facebooker
360
371
  # publish a previously rendered template bundle
361
372
  # see http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
362
373
  #
363
- def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil)
374
+ def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil,story_size=nil)
364
375
  parameters={:template_bundle_id=>bundle_id,:template_data=>data.to_json}
365
376
  parameters[:target_ids] = target_ids unless target_ids.blank?
366
377
  parameters[:body_general] = body_general unless body_general.blank?
378
+ parameters[:story_size] = story_size unless story_size.nil?
367
379
  post("facebook.feed.publishUserAction", parameters)
368
380
  end
369
381
 
@@ -2,7 +2,7 @@ module Facebooker #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 0
5
- TINY = 18
5
+ TINY = 29
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,77 @@
1
+ module Rack
2
+ # This Rack middleware checks the signature of Facebook params, and
3
+ # converts them to Ruby objects when appropiate. Also, it converts
4
+ # the request method from the Facebook POST to the original HTTP
5
+ # method used by the client.
6
+ #
7
+ # If the signature is wrong, it returns a "400 Invalid Facebook Signature".
8
+ #
9
+ # Optionally, it can take a block that receives the Rack environment
10
+ # and returns a value that evaluates to true when we want the middleware to
11
+ # be executed for the specific request.
12
+ #
13
+ # == Usage
14
+ #
15
+ # In your config.ru:
16
+ #
17
+ # require 'rack/facebook'
18
+ # use Rack::Facebook, "my_facebook_secret_key"
19
+ #
20
+ # Using a block condition:
21
+ #
22
+ # use Rack::Facebook, "my_facebook_secret_key" do |env|
23
+ # env['REQUEST_URI'] =~ /^\/facebook_only/
24
+ # end
25
+ #
26
+ class Facebook
27
+ def initialize(app, secret_key, &condition)
28
+ @app = app
29
+ @secret_key = secret_key
30
+ @condition = condition
31
+ end
32
+
33
+ def call(env)
34
+ if @condition.nil? || @condition.call(env)
35
+ request = Rack::Request.new(env)
36
+ fb_params = extract_fb_sig_params(request.POST)
37
+ unless fb_params.empty?
38
+ unless signature_is_valid?(fb_params, request.POST['fb_sig'])
39
+ return Rack::Response.new(["Invalid Facebook signature"], 400).finish
40
+ end
41
+ env['REQUEST_METHOD'] = fb_params["request_method"] if fb_params["request_method"]
42
+ convert_parameters!(request.POST)
43
+ end
44
+ end
45
+ @app.call(env)
46
+ end
47
+
48
+ private
49
+
50
+ def extract_fb_sig_params(params)
51
+ params.inject({}) do |collection, (param, value)|
52
+ collection[param.sub(/^fb_sig_/, '')] = value if param[0,7] == 'fb_sig_'
53
+ collection
54
+ end
55
+ end
56
+
57
+ def signature_is_valid?(fb_params, actual_sig)
58
+ raw_string = fb_params.map{ |*args| args.join('=') }.sort.join
59
+ expected_signature = Digest::MD5.hexdigest([raw_string, @secret_key].join)
60
+ actual_sig == expected_signature
61
+ end
62
+
63
+ def convert_parameters!(params)
64
+
65
+ params.each do |key, value|
66
+ case key
67
+ when 'fb_sig_added', 'fb_sig_in_canvas', 'fb_sig_in_new_facebook', 'fb_sig_position_fix', 'fb_sig_is_ajax'
68
+ params[key] = value == "1"
69
+ when 'fb_sig_expires', 'fb_sig_profile_update_time', 'fb_sig_time'
70
+ params[key] = value == "0" ? nil : Time.at(value.to_f)
71
+ when 'fb_sig_friends'
72
+ params[key] = value.split(',')
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -19,8 +19,7 @@ namespace :facebooker do
19
19
  # Adapted from Evan Weaver: http://blog.evanweaver.com/articles/2007/07/13/developing-a-facebook-app-locally/
20
20
  desc "Check if reverse tunnel is running"
21
21
  task :status => [ :environment, :config ] do
22
- if `ssh #{@public_host} -l #{@public_host_username} netstat -an |
23
- egrep "tcp.*:#{@public_port}.*LISTEN" | wc`.to_i > 0
22
+ if `ssh #{@public_host} -l #{@public_host_username} netstat -an | egrep "tcp.*:#{@public_port}.*LISTEN" | wc`.to_i > 0
24
23
  puts "Seems ok"
25
24
  else
26
25
  puts "Down"
@@ -35,10 +34,11 @@ namespace :facebooker do
35
34
  @public_port = FACEBOOKER['tunnel']['public_port']
36
35
  @local_port = FACEBOOKER['tunnel']['local_port']
37
36
  @ssh_port = FACEBOOKER['tunnel']['ssh_port'] || 22
37
+ @server_alive_interval = FACEBOOKER['tunnel']['server_alive_interval'] || 0
38
38
  @notification = "Starting tunnel #{@public_host}:#{@public_port} to 0.0.0.0:#{@local_port}"
39
39
  @notification << " using SSH port #{@ssh_port}" unless @ssh_port == 22
40
40
  # "GatewayPorts yes" needs to be enabled in the remote's sshd config
41
- @ssh_command = "ssh -v -p #{@ssh_port} -nNT4 -R *:#{@public_port}:localhost:#{@local_port} #{@public_host_username}@#{@public_host}"
41
+ @ssh_command = %Q[ssh -v -p #{@ssh_port} -nNT4 -o "ServerAliveInterval #{@server_alive_interval}" -R *:#{@public_port}:localhost:#{@local_port} #{@public_host_username}@#{@public_host}]
42
42
  end
43
43
  end
44
44
  desc "Create a reverse ssh tunnel from a public server to a private development server."