facebooker 1.0.18 → 1.0.29

Sign up to get free protection for your applications and to get access to all the features.
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."