facebooker-lite 1.0.67

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 (118) hide show
  1. data/.autotest +15 -0
  2. data/CHANGELOG.rdoc +24 -0
  3. data/COPYING.rdoc +19 -0
  4. data/Manifest.txt +114 -0
  5. data/README.rdoc +120 -0
  6. data/Rakefile +94 -0
  7. data/TODO.rdoc +4 -0
  8. data/facebooker.gemspec +42 -0
  9. data/generators/xd_receiver/templates/xd_receiver.html +10 -0
  10. data/generators/xd_receiver/templates/xd_receiver_ssl.html +10 -0
  11. data/generators/xd_receiver/xd_receiver_generator.rb +10 -0
  12. data/init.rb +25 -0
  13. data/install.rb +12 -0
  14. data/lib/facebooker.rb +261 -0
  15. data/lib/facebooker/adapters/adapter_base.rb +91 -0
  16. data/lib/facebooker/adapters/bebo_adapter.rb +77 -0
  17. data/lib/facebooker/adapters/facebook_adapter.rb +60 -0
  18. data/lib/facebooker/admin.rb +42 -0
  19. data/lib/facebooker/application.rb +37 -0
  20. data/lib/facebooker/attachment.rb +51 -0
  21. data/lib/facebooker/batch_request.rb +45 -0
  22. data/lib/facebooker/data.rb +57 -0
  23. data/lib/facebooker/feed.rb +78 -0
  24. data/lib/facebooker/logging.rb +44 -0
  25. data/lib/facebooker/mobile.rb +20 -0
  26. data/lib/facebooker/mock/service.rb +50 -0
  27. data/lib/facebooker/mock/session.rb +18 -0
  28. data/lib/facebooker/model.rb +139 -0
  29. data/lib/facebooker/models/affiliation.rb +10 -0
  30. data/lib/facebooker/models/album.rb +11 -0
  31. data/lib/facebooker/models/applicationproperties.rb +39 -0
  32. data/lib/facebooker/models/applicationrestrictions.rb +10 -0
  33. data/lib/facebooker/models/comment.rb +9 -0
  34. data/lib/facebooker/models/cookie.rb +10 -0
  35. data/lib/facebooker/models/education_info.rb +11 -0
  36. data/lib/facebooker/models/event.rb +28 -0
  37. data/lib/facebooker/models/family_relative_info.rb +7 -0
  38. data/lib/facebooker/models/friend_list.rb +16 -0
  39. data/lib/facebooker/models/group.rb +36 -0
  40. data/lib/facebooker/models/info_item.rb +10 -0
  41. data/lib/facebooker/models/info_section.rb +10 -0
  42. data/lib/facebooker/models/location.rb +8 -0
  43. data/lib/facebooker/models/message_thread.rb +89 -0
  44. data/lib/facebooker/models/notifications.rb +17 -0
  45. data/lib/facebooker/models/page.rb +46 -0
  46. data/lib/facebooker/models/photo.rb +19 -0
  47. data/lib/facebooker/models/tag.rb +12 -0
  48. data/lib/facebooker/models/user.rb +751 -0
  49. data/lib/facebooker/models/video.rb +9 -0
  50. data/lib/facebooker/models/work_info.rb +10 -0
  51. data/lib/facebooker/parser.rb +970 -0
  52. data/lib/facebooker/rails/backwards_compatible_param_checks.rb +31 -0
  53. data/lib/facebooker/rails/controller.rb +353 -0
  54. data/lib/facebooker/rails/extensions/action_controller.rb +47 -0
  55. data/lib/facebooker/rails/extensions/rack_setup.rb +16 -0
  56. data/lib/facebooker/rails/extensions/routing.rb +15 -0
  57. data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
  58. data/lib/facebooker/rails/facebook_request_fix.rb +28 -0
  59. data/lib/facebooker/rails/facebook_request_fix_2-3.rb +31 -0
  60. data/lib/facebooker/rails/facebook_session_handling.rb +68 -0
  61. data/lib/facebooker/rails/facebook_url_rewriting.rb +60 -0
  62. data/lib/facebooker/rails/helpers/fb_connect.rb +75 -0
  63. data/lib/facebooker/rails/integration_session.rb +38 -0
  64. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  65. data/lib/facebooker/rails/publisher.rb +608 -0
  66. data/lib/facebooker/rails/routing.rb +49 -0
  67. data/lib/facebooker/rails/test_helpers.rb +68 -0
  68. data/lib/facebooker/rails/utilities.rb +22 -0
  69. data/lib/facebooker/server_cache.rb +24 -0
  70. data/lib/facebooker/service.rb +103 -0
  71. data/lib/facebooker/service/base_service.rb +19 -0
  72. data/lib/facebooker/service/curl_service.rb +44 -0
  73. data/lib/facebooker/service/net_http_service.rb +12 -0
  74. data/lib/facebooker/service/typhoeus_multi_service.rb +27 -0
  75. data/lib/facebooker/service/typhoeus_service.rb +17 -0
  76. data/lib/facebooker/session.rb +786 -0
  77. data/lib/facebooker/stream_post.rb +19 -0
  78. data/lib/facebooker/version.rb +9 -0
  79. data/lib/net/http_multipart_post.rb +123 -0
  80. data/lib/rack/facebook.rb +89 -0
  81. data/lib/rack/facebook_session.rb +21 -0
  82. data/lib/tasks/facebooker.rake +19 -0
  83. data/lib/tasks/facebooker.rb +2 -0
  84. data/lib/tasks/tunnel.rake +46 -0
  85. data/rails/init.rb +1 -0
  86. data/setup.rb +1585 -0
  87. data/templates/layout.erb +24 -0
  88. data/test/facebooker/adapters_test.rb +191 -0
  89. data/test/facebooker/admin_test.rb +102 -0
  90. data/test/facebooker/application_test.rb +110 -0
  91. data/test/facebooker/attachment_test.rb +72 -0
  92. data/test/facebooker/batch_request_test.rb +83 -0
  93. data/test/facebooker/data_test.rb +86 -0
  94. data/test/facebooker/logging_test.rb +43 -0
  95. data/test/facebooker/mobile_test.rb +45 -0
  96. data/test/facebooker/model_test.rb +133 -0
  97. data/test/facebooker/models/event_test.rb +15 -0
  98. data/test/facebooker/models/page_test.rb +56 -0
  99. data/test/facebooker/models/photo_test.rb +16 -0
  100. data/test/facebooker/models/user_test.rb +1074 -0
  101. data/test/facebooker/rails/facebook_request_fix_2-3_test.rb +25 -0
  102. data/test/facebooker/rails/facebook_url_rewriting_test.rb +76 -0
  103. data/test/facebooker/rails/integration_session_test.rb +13 -0
  104. data/test/facebooker/rails/publisher_test.rb +538 -0
  105. data/test/facebooker/rails_integration_test.rb +1543 -0
  106. data/test/facebooker/server_cache_test.rb +44 -0
  107. data/test/facebooker/service_test.rb +58 -0
  108. data/test/facebooker/session_test.rb +883 -0
  109. data/test/facebooker_test.rb +1263 -0
  110. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  111. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  112. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  113. data/test/net/http_multipart_post_test.rb +52 -0
  114. data/test/rack/facebook_session_test.rb +34 -0
  115. data/test/rack/facebook_test.rb +73 -0
  116. data/test/rails_test_helper.rb +36 -0
  117. data/test/test_helper.rb +74 -0
  118. metadata +278 -0
@@ -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
@@ -0,0 +1,22 @@
1
+ class ActionController::Base
2
+ def rescues_path_with_facebooker(template_name)
3
+ t = "#{RAILS_ROOT}/vendor/plugins/facebooker/templates/#{template_name}.erb"
4
+ if pretty_facebook_errors? && File.exist?(t)
5
+ t
6
+ else
7
+ rescues_path_without_facebooker(template_name)
8
+ end
9
+ end
10
+ alias_method_chain :rescues_path, :facebooker
11
+
12
+ def response_code_for_rescue_with_facebooker(exception)
13
+ pretty_facebook_errors? ? 200 : response_code_for_rescue_without_facebooker(exception)
14
+ end
15
+ alias_method_chain :response_code_for_rescue, :facebooker
16
+
17
+
18
+ def pretty_facebook_errors?
19
+ Facebooker.facebooker_config['pretty_errors'] ||
20
+ (Facebooker.facebooker_config['pretty_errors'].nil? && RAILS_ENV=="development")
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ module ::ActionController
2
+ class AbstractRequest
3
+ include Facebooker::Rails::BackwardsCompatibleParamChecks
4
+
5
+ def request_method_with_facebooker
6
+ if parameters[:_method].blank?
7
+ if %w{GET HEAD}.include?(parameters[:fb_sig_request_method])
8
+ parameters[:_method] = parameters[:fb_sig_request_method]
9
+ end
10
+ end
11
+ request_method_without_facebooker
12
+ end
13
+
14
+ if new.methods.include?("request_method")
15
+ alias_method_chain :request_method, :facebooker
16
+ end
17
+
18
+ def xml_http_request_with_facebooker?
19
+ one_or_true(parameters["fb_sig_is_mockajax"]) ||
20
+ one_or_true(parameters["fb_sig_is_ajax"]) ||
21
+ xml_http_request_without_facebooker?
22
+ end
23
+ alias_method_chain :xml_http_request?, :facebooker
24
+ # we have to re-alias xhr? since it was pointing to the old method
25
+ alias :xhr? :xml_http_request?
26
+
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module ::ActionController
2
+
3
+ class Request
4
+
5
+ include Facebooker::Rails::BackwardsCompatibleParamChecks
6
+
7
+ def request_method_with_facebooker
8
+ if parameters[:_method].blank?
9
+ if %w{GET HEAD}.include?(parameters[:fb_sig_request_method])
10
+ parameters[:_method] = parameters[:fb_sig_request_method]
11
+ end
12
+ end
13
+ request_method_without_facebooker
14
+ end
15
+
16
+ if new({}).methods.include?("request_method")
17
+ alias_method_chain :request_method, :facebooker
18
+ end
19
+
20
+ def xml_http_request_with_facebooker?
21
+ one_or_true(parameters["fb_sig_is_mockajax"]) ||
22
+ one_or_true(parameters["fb_sig_is_ajax"]) ||
23
+ xml_http_request_without_facebooker?
24
+ end
25
+ alias_method_chain :xml_http_request?, :facebooker
26
+
27
+ # we have to re-alias xhr? since it was pointing to the old method
28
+ alias :xhr? :xml_http_request?
29
+
30
+ end
31
+ end
@@ -0,0 +1,68 @@
1
+ module ActionController
2
+ class CgiRequest
3
+ alias :initialize_aliased_by_facebooker :initialize
4
+
5
+ def initialize(cgi, session_options = {})
6
+ initialize_aliased_by_facebooker(cgi, session_options)
7
+ @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters))
8
+ end
9
+
10
+ DEFAULT_SESSION_OPTIONS[:cookie_only] = false
11
+ end
12
+ end
13
+
14
+ module ActionController
15
+ class RackRequest < AbstractRequest #:nodoc:
16
+ alias :initialize_aliased_by_facebooker :initialize
17
+
18
+ def initialize(cgi, session_options = {})
19
+ initialize_aliased_by_facebooker(cgi, session_options)
20
+ @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters))
21
+ end
22
+ end
23
+ end
24
+
25
+ class CGI
26
+ class Session
27
+ alias :initialize_aliased_by_facebooker :initialize
28
+ attr_reader :request, :initialization_options
29
+
30
+ def initialize(request, option={})
31
+ @request = request
32
+ @initialization_options = option
33
+ option['session_id'] ||= set_session_id
34
+ initialize_aliased_by_facebooker(request, option)
35
+ end
36
+
37
+ def set_session_id
38
+ if session_key_should_be_set_with_facebook_session_key?
39
+ request_parameters[facebook_session_key]
40
+ else
41
+ request_parameters[session_key]
42
+ end
43
+ end
44
+
45
+ def request_parameters
46
+ request.instance_variable_get("@request_params")
47
+ end
48
+
49
+ def session_key_should_be_set_with_facebook_session_key?
50
+ request_parameters[session_key].blank? && !request_parameters[facebook_session_key].blank?
51
+ end
52
+
53
+ def session_key
54
+ initialization_options['session_key'] || '_session_id'
55
+ end
56
+
57
+ def facebook_session_key
58
+ 'fb_sig_session_key'
59
+ end
60
+
61
+ alias :create_new_id_aliased_by_facebooker :create_new_id
62
+
63
+ def create_new_id
64
+ @new_session = true
65
+ @session_id || create_new_id_aliased_by_facebooker
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ module ::ActionController
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
14
+ end
15
+
16
+ class Base
17
+ class << self
18
+ alias :old_relative_url_root :relative_url_root
19
+ def relative_url_root
20
+ Facebooker.path_prefix
21
+ end
22
+ end
23
+ end
24
+
25
+ class UrlRewriter
26
+ include Facebooker::Rails::BackwardsCompatibleParamChecks
27
+
28
+ RESERVED_OPTIONS << :canvas
29
+
30
+ def link_to_new_canvas?
31
+ one_or_true @request.parameters["fb_sig_in_new_facebook"]
32
+ end
33
+
34
+ def link_to_canvas?(params, options)
35
+ option_override = options[:canvas]
36
+ return false if option_override == false # important to check for false. nil should use default behavior
37
+ option_override || (can_safely_access_request_parameters? && (one_or_true(@request.parameters["fb_sig_in_canvas"]) || one_or_true(@request.parameters[:fb_sig_in_canvas]) || one_or_true(@request.parameters["fb_sig_is_ajax"]) ))
38
+ end
39
+
40
+ #rails blindly tries to merge things that may be nil into the parameters. Make sure this won't break
41
+ def can_safely_access_request_parameters?
42
+ @request.request_parameters
43
+ end
44
+
45
+ def rewrite_url_with_facebooker(*args)
46
+ options = args.first.is_a?(Hash) ? args.first : args.last
47
+ is_link_to_canvas = link_to_canvas?(@request.request_parameters, options)
48
+ if is_link_to_canvas && !options.has_key?(:host)
49
+ options[:host] = Facebooker.canvas_server_base
50
+ end
51
+ options.delete(:canvas)
52
+ Facebooker.request_for_canvas(is_link_to_canvas) do
53
+ rewrite_url_without_facebooker(*args)
54
+ end
55
+ end
56
+
57
+ alias_method_chain :rewrite_url, :facebooker
58
+
59
+ end
60
+ end
@@ -0,0 +1,75 @@
1
+ module Facebooker
2
+ module Rails
3
+ module Helpers
4
+ module FbConnect
5
+ def fb_connect_javascript_tag(options = {})
6
+ # accept both Rails and Facebook locale formatting, i.e. "en-US" and "en_US".
7
+ lang = "/#{options[:lang].to_s.gsub('-', '_')}" if options[:lang]
8
+ # dont use the javascript_include_tag helper since it adds a .js at the end
9
+ if request.ssl?
10
+ "<script src=\"https://ssl.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
11
+ else
12
+ "<script src=\"http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
13
+ end
14
+ end
15
+
16
+ #
17
+ # For information on the :app_settings argument see http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Facebook.Init_2
18
+ # While it would be nice to treat :app_settings as a hash, some of the arguments do different things if they are a string vs a javascript function
19
+ # and Rails' Hash#to_json always quotes strings so there is no way to indicate when the value should be a javascript function.
20
+ # For this reason :app_settings needs to be a string that is valid JSON (including the {}'s).
21
+ #
22
+ def init_fb_connect(*required_features, &proc)
23
+ init_fb_connect_with_options({},*required_features, &proc)
24
+ end
25
+
26
+ def init_fb_connect_with_options(options = {},*required_features, &proc)
27
+ additions = ""
28
+ if block_given?
29
+ additions = capture(&proc)
30
+ end
31
+
32
+ # Yes, app_settings is set to a string of an empty JSON element. That's intentional.
33
+ options = options.merge({:app_settings => '{}'})
34
+
35
+ if required_features.last.is_a?(Hash)
36
+ options.merge!(required_features.pop.symbolize_keys)
37
+ end
38
+
39
+ if request.ssl?
40
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver_ssl.html', #{options[:app_settings]});"
41
+ else
42
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver.html', #{options[:app_settings]});"
43
+ end
44
+ unless required_features.blank?
45
+ init_string = <<-FBML
46
+ #{case options[:js]
47
+ when :jquery then "jQuery(document).ready("
48
+ when :dojo then "dojo.addOnLoad("
49
+ when :mootools then "window.addEvent('domready',"
50
+ else "Element.observe(window,'load',"
51
+ end} function() {
52
+ FB_RequireFeatures(#{required_features.to_json}, function() {
53
+ #{init_string}
54
+ #{additions}
55
+ });
56
+ });
57
+ FBML
58
+ end
59
+
60
+ # block_is_within_action_view? is rails 2.1.x and has been
61
+ # deprecated. rails >= 2.2.x uses block_called_from_erb?
62
+ block_tester = respond_to?(:block_is_within_action_view?) ?
63
+ :block_is_within_action_view? : :block_called_from_erb?
64
+
65
+ if block_given? && send(block_tester, proc)
66
+ versioned_concat(javascript_tag(init_string),proc.binding)
67
+ else
68
+ javascript_tag init_string
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,38 @@
1
+ require 'action_controller/integration'
2
+
3
+ class Facebooker::Rails::IntegrationSession < ActionController::Integration::Session
4
+ include Facebooker::Rails::TestHelpers
5
+ attr_accessor :default_request_params, :canvas
6
+
7
+ def process(method, path, parameters = nil, headers = nil)
8
+ if canvas
9
+ parameters = facebook_params(@default_request_params.merge(parameters || {}))
10
+ end
11
+ super method, path, parameters, headers
12
+ end
13
+
14
+ def reset!
15
+ self.default_request_params = {:fb_sig_in_canvas => '1', :fb_sig_api_key => Facebooker::Session.api_key}.with_indifferent_access
16
+ self.canvas = true
17
+ super
18
+ end
19
+
20
+ def get(path, parameters = nil, headers = nil)
21
+ if canvas
22
+ post path, (parameters || {}).merge('fb_sig_request_method' => 'GET'), headers
23
+ else
24
+ super path, parameters, headers
25
+ end
26
+ end
27
+
28
+ %w(put delete).each do |method|
29
+ define_method method do |*args|
30
+ if canvas
31
+ path, parameters, headers = *args
32
+ post path, (parameters || {}).merge('_method' => method.upcase), headers
33
+ else
34
+ super *args
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ module Facebooker
2
+ module Rails
3
+ module ProfilePublisherExtensions
4
+
5
+ ##
6
+ # returns true if Facebook is requesting the interface for a profile publisher
7
+ def wants_interface?
8
+ params[:method] == "publisher_getInterface"
9
+ end
10
+
11
+ ##
12
+ # render the interface for a publisher.
13
+ # fbml is the content in string form. Use render_to_string to get the content from a template
14
+ # publish_enabled controlls whether the post form is active by default. If it isn't, you'll need to use fbjs to activate it
15
+ # comment_enabled controls whether to include a comment box
16
+ def render_publisher_interface(fbml,publish_enabled=true,comment_enabled=false)
17
+ render :json=>{:content=>{:fbml=>fbml,:publishEnabled=>publish_enabled,:commentEnabled=>comment_enabled},
18
+ :method=>"publisher_getInterface"}
19
+ end
20
+
21
+ # render an error while publishing the template
22
+ # This can be used for validation errors
23
+ def render_publisher_error(title,body)
24
+ render :json=>{:errorCode=>1,:errorTitle=>title,:errorMessage=>body}.to_json
25
+ end
26
+
27
+ # render the response for a feed. This takes a user_action object like those returned from the Rails Publisher
28
+ # For instance, AttackPublisher.create_attack(@attack)
29
+ # The template must have been registered previously
30
+ def render_publisher_response(user_action)
31
+ render :json=>{:content=> {
32
+ :feed=>{
33
+ :template_id=>user_action.template_id,
34
+ :template_data=>user_action.data
35
+ }
36
+ },
37
+ :method=>"publisher_getFeedStory"
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,608 @@
1
+ module Facebooker
2
+ module Rails
3
+ # ActionMailer like module for publishing Facbook messages
4
+ #
5
+ # To use, create a subclass and define methods
6
+ # Each method should start by calling send_as to specify the type of message
7
+ # Valid options are :email and :notification, :user_action, :profile, :ref, :publish_stream
8
+ #
9
+ #
10
+ # Below is an example of each type
11
+ #
12
+ # class TestPublisher < Facebooker::Rails::Publisher
13
+ # # The new message templates are supported as well
14
+ # # First, create a method that contains your templates:
15
+ # # You may include multiple one line story templates and short story templates
16
+ # # but only one full story template
17
+ # # Your most specific template should be first
18
+ # #
19
+ # # Before using, you must register your template by calling register. For this example
20
+ # # You would call TestPublisher.register_publish_action
21
+ # # Registering the template will store the template id returned from Facebook in the
22
+ # # facebook_templates table that is created when you create your first publisher
23
+ # def publish_action_template
24
+ # one_line_story_template "{*actor*} did stuff with {*friend*}"
25
+ # one_line_story_template "{*actor*} did stuff"
26
+ # short_story_template "{*actor*} has a title {*friend*}", render(:partial=>"short_body")
27
+ # short_story_template "{*actor*} has a title", render(:partial=>"short_body")
28
+ # full_story_template "{*actor*} has a title {*friend*}", render(:partial=>"full_body")
29
+ # action_links action_link("My text {*template_var*}","{*link_url*}")
30
+ # end
31
+ #
32
+ # # To send a registered template, you need to create a method to set the data
33
+ # # The publisher will look up the template id from the facebook_templates table
34
+ # def publish_action(f)
35
+ # send_as :user_action
36
+ # from f
37
+ # story_size SHORT # or ONE_LINE or FULL
38
+ # data :friend=>"Mike"
39
+ # end
40
+ #
41
+ #
42
+ # # Provide a from user to send a general notification
43
+ # # if from is nil, this will send an announcement
44
+ # def notification(to,f)
45
+ # send_as :notification
46
+ # recipients to
47
+ # from f
48
+ # fbml "Not"
49
+ # end
50
+ #
51
+ # def email(to,f)
52
+ # send_as :email
53
+ # recipients to
54
+ # from f
55
+ # title "Email"
56
+ # fbml 'text'
57
+ # text fbml
58
+ # end
59
+ # # This will render the profile in /users/profile.fbml.erb
60
+ # # it will set @user to user_to_update in the template
61
+ # # The mobile profile will be rendered from the app/views/test_publisher/_mobile.erb
62
+ # # template
63
+ # def profile_update(user_to_update,user_with_session_to_use)
64
+ # send_as :profile
65
+ # from user_with_session_to_use
66
+ # recipients user_to_update
67
+ # profile render(:file=>"users/profile.fbml.erb",:assigns=>{:user=>user_to_update})
68
+ # profile_action "A string"
69
+ # mobile_profile render(:partial=>"mobile",:assigns=>{:user=>user_to_update})
70
+ # end
71
+ #
72
+ # # Update the given handle ref with the content from a
73
+ # # template
74
+ # def ref_update(user)
75
+ # send_as :ref
76
+ # from user
77
+ # fbml render(:file=>"users/profile",:assigns=>{:user=>user_to_update})
78
+ # handle "a_ref_handle"
79
+ # end
80
+ #
81
+ # # Publish a post into the stream on the user's Wall and News Feed.
82
+ # def publish_stream(user_with_session_to_use, user_to_update, params)
83
+ # send_as :publish_stream
84
+ # from user_with_session_to_use
85
+ # target user_to_update
86
+ # attachment params[:attachment]
87
+ # message params[:message]
88
+ # action_links params[:action_links]
89
+ # end
90
+ #
91
+ #
92
+ # To send a message, use ActionMailer like semantics
93
+ # TestPublisher.deliver_action(@user)
94
+ #
95
+ # For testing, you may want to create an instance of the underlying message without sending it
96
+ # TestPublisher.create_action(@user)
97
+ # will create and return an instance of Facebooker::Feeds::Action
98
+ #
99
+ # Publisher makes many helpers available, including the linking and asset helpers
100
+ class Publisher
101
+
102
+ #story sizes from the Facebooker API
103
+ ONE_LINE=1
104
+ SHORT=2
105
+ FULL=4
106
+
107
+ def initialize
108
+ @from = nil
109
+ @full_story_template = nil
110
+ @recipients = nil
111
+ @action_links = nil
112
+ @controller = PublisherController.new(self)
113
+ @action_links = nil
114
+ end
115
+
116
+ def self.default_url_options
117
+ {:host => Facebooker.canvas_server_base + Facebooker.facebook_path_prefix}
118
+ end
119
+
120
+ def default_url_options
121
+ self.class.default_url_options
122
+ end
123
+
124
+ # use facebook options everywhere
125
+ def request_comes_from_facebook?
126
+ true
127
+ end
128
+
129
+ class FacebookTemplate < ::ActiveRecord::Base
130
+ cattr_accessor :template_cache
131
+ self.template_cache = {}
132
+
133
+ def self.inspect(*args)
134
+ "FacebookTemplate"
135
+ end
136
+
137
+ def template_changed?(hash)
138
+ if respond_to?(:content_hash)
139
+ content_hash != hash
140
+ else
141
+ false
142
+ end
143
+ end
144
+
145
+ def deactivate
146
+ Facebooker::Session.create.deactivate_template_bundle_by_id(self.bundle_id)
147
+ return true
148
+ rescue Facebooker::Session::TemplateBundleInvalid => e
149
+ return false
150
+ end
151
+
152
+
153
+
154
+ class << self
155
+
156
+ def register(klass,method)
157
+ publisher = setup_publisher(klass,method)
158
+ template_id = Facebooker::Session.create.register_template_bundle(publisher.one_line_story_templates,publisher.short_story_templates,publisher.full_story_template,publisher.action_links)
159
+ template = find_or_initialize_by_template_name(template_name(klass,method))
160
+ template.deactivate if template.bundle_id # deactivate old templates to avoid exceeding templates/app limit
161
+ template.bundle_id = template_id
162
+ template.content_hash = hashed_content(klass,method) if template.respond_to?(:content_hash)
163
+ template.save!
164
+ cache(klass,method,template)
165
+ template
166
+ end
167
+
168
+ def for_class_and_method(klass,method)
169
+ find_cached(klass,method)
170
+ end
171
+ def bundle_id_for_class_and_method(klass,method)
172
+ for_class_and_method(klass,method).bundle_id
173
+ end
174
+
175
+ def cache(klass,method,template)
176
+ template_cache[template_name(klass,method)] = template
177
+ end
178
+
179
+ def clear_cache!
180
+ self.template_cache = {}
181
+ end
182
+
183
+ def find_cached(klass,method)
184
+ template_cache[template_name(klass,method)] || find_in_db(klass,method)
185
+ end
186
+
187
+ def find_in_db(klass,method)
188
+ template = find_by_template_name(template_name(klass,method))
189
+
190
+ if template.nil? || template.template_changed?(hashed_content(klass, method))
191
+ template = register(klass,method)
192
+ end
193
+ template
194
+ end
195
+
196
+ def setup_publisher(klass,method)
197
+ publisher = klass.new
198
+ publisher.send method + '_template'
199
+ publisher
200
+ end
201
+
202
+ def hashed_content(klass, method)
203
+ publisher = setup_publisher(klass,method)
204
+ # sort the Hash elements (in the short_story and full_story) before generating MD5
205
+ Digest::MD5.hexdigest [publisher.one_line_story_templates,
206
+ (publisher.short_story_templates and publisher.short_story_templates.collect{|ss| ss.to_a.sort_by{|e| e[0].to_s}}),
207
+ (publisher.full_story_template and publisher.full_story_template.to_a.sort_by{|e| e[0].to_s})
208
+ ].to_json
209
+ end
210
+
211
+ def template_name(klass,method)
212
+ "#{Facebooker.api_key}: #{klass.name}::#{method}"
213
+ end
214
+ end
215
+ end
216
+
217
+ class_inheritable_accessor :master_helper_module
218
+ attr_accessor :one_line_story_templates, :short_story_templates
219
+ attr_writer :action_links
220
+
221
+ cattr_accessor :skip_registry
222
+ self.skip_registry = false
223
+
224
+
225
+ class InvalidSender < StandardError; end
226
+ class UnknownBodyType < StandardError; end
227
+ class UnspecifiedBodyType < StandardError; end
228
+ class Email
229
+ attr_accessor :title
230
+ attr_accessor :text
231
+ attr_accessor :fbml
232
+ end
233
+
234
+ class Notification
235
+ attr_accessor :fbml
236
+ end
237
+
238
+ class Profile
239
+ attr_accessor :profile
240
+ attr_accessor :profile_action
241
+ attr_accessor :mobile_profile
242
+ attr_accessor :profile_main
243
+ end
244
+ class Ref
245
+ attr_accessor :handle
246
+ attr_accessor :fbml
247
+ end
248
+ class UserAction
249
+ attr_accessor :data
250
+ attr_reader :target_ids
251
+ attr_accessor :body_general
252
+ attr_accessor :template_id
253
+ attr_accessor :template_name
254
+ attr_accessor :story_size
255
+
256
+ def target_ids=(val)
257
+ @target_ids = val.is_a?(Array) ? val.join(",") : val
258
+ end
259
+
260
+ def data_hash
261
+ data||{}
262
+ end
263
+ end
264
+
265
+ class PublishStream
266
+ attr_accessor :target
267
+ attr_accessor :attachment
268
+ attr_accessor :action_links
269
+ attr_accessor :message
270
+ end
271
+
272
+ cattr_accessor :ignore_errors
273
+ attr_accessor :_body
274
+
275
+ def recipients(*args)
276
+ if args.size==0
277
+ @recipients
278
+ else
279
+ @recipients=args.first
280
+ end
281
+ end
282
+
283
+ def from(*args)
284
+ if args.size==0
285
+ @from
286
+ else
287
+ @from=args.first
288
+ end
289
+ end
290
+
291
+
292
+ def send_as(option)
293
+ self._body=case option
294
+ when :action
295
+ Facebooker::Feed::Action.new
296
+ when :story
297
+ Facebooker::Feed::Story.new
298
+ when :templatized_action
299
+ Facebooker::Feed::TemplatizedAction.new
300
+ when :notification
301
+ Notification.new
302
+ when :email
303
+ Email.new
304
+ when :profile
305
+ Profile.new
306
+ when :ref
307
+ Ref.new
308
+ when :user_action
309
+ UserAction.new
310
+ when :publish_stream
311
+ StreamPost.new
312
+ else
313
+ raise UnknownBodyType.new("Unknown type to publish")
314
+ end
315
+ end
316
+
317
+ def full_story_template(title=nil,body=nil,params={})
318
+ if title.nil?
319
+ @full_story_template
320
+ else
321
+ @full_story_template=params.merge(:template_title=>title, :template_body=>body)
322
+ end
323
+ end
324
+
325
+ def one_line_story_template(str)
326
+ @one_line_story_templates ||= []
327
+ @one_line_story_templates << str
328
+ end
329
+
330
+ def short_story_template(title,body,params={})
331
+ @short_story_templates ||= []
332
+ @short_story_templates << params.merge(:template_title=>title, :template_body=>body)
333
+ end
334
+
335
+ def action_links(*links)
336
+ if self._body and self._body.respond_to?(:action_links)
337
+ self._body.send(:action_links,*links)
338
+ end
339
+ if links.blank?
340
+ @action_links
341
+ else
342
+ @action_links = *links
343
+ end
344
+ end
345
+
346
+ def method_missing(name,*args)
347
+ if args.size==1 and self._body.respond_to?("#{name}=")
348
+ self._body.send("#{name}=",*args)
349
+ elsif self._body.respond_to?(name)
350
+ self._body.send(name,*args)
351
+ else
352
+ super
353
+ end
354
+ end
355
+
356
+ # work around the fact that facebook cares about the order of the keys in the hash
357
+ class ImageHolder
358
+ attr_accessor :src,:href
359
+ def initialize(src,href)
360
+ self.src=src
361
+ self.href=href
362
+ end
363
+
364
+ def ==(other)
365
+ self.src == other.src && self.href == other.href
366
+ end
367
+
368
+ def to_json(*args)
369
+ "{\"src\":#{src.to_json}, \"href\":#{href.to_json}}"
370
+ end
371
+ end
372
+
373
+ def image(src,target)
374
+ ImageHolder.new(image_path(src),target.respond_to?(:to_str) ? target : url_for(target))
375
+ end
376
+
377
+ def action_link(text,target)
378
+ {:text=>text, :href=>target}
379
+ end
380
+
381
+ def requires_from_user?(from,body)
382
+ ! (announcement_notification?(from,body) or ref_update?(body) or profile_update?(body))
383
+ end
384
+
385
+ def profile_update?(body)
386
+ body.is_a?(Profile)
387
+ end
388
+
389
+ def ref_update?(body)
390
+ body.is_a?(Ref)
391
+ end
392
+
393
+ def announcement_notification?(from,body)
394
+ from.nil? and body.is_a?(Notification)
395
+ end
396
+
397
+ def send_message(method)
398
+ @recipients = @recipients.is_a?(Array) ? @recipients : [@recipients]
399
+ if from.nil? and @recipients.size==1 and requires_from_user?(from,_body)
400
+ @from = @recipients.first
401
+ end
402
+ # notifications can
403
+ # omit the from address
404
+ raise InvalidSender.new("Sender must be a Facebooker::User") unless from.is_a?(Facebooker::User) || !requires_from_user?(from,_body)
405
+ case _body
406
+ when Facebooker::Feed::TemplatizedAction,Facebooker::Feed::Action
407
+ from.publish_action(_body)
408
+ when Facebooker::Feed::Story
409
+ @recipients.each {|r| r.publish_story(_body)}
410
+ when Notification
411
+ (from.nil? ? Facebooker::Session.create : from.session).send_notification(@recipients,_body.fbml)
412
+ when Email
413
+ from.session.send_email(@recipients,
414
+ _body.title,
415
+ _body.text,
416
+ _body.fbml)
417
+ when Profile
418
+ # If recipient and from aren't the same person, create a new user object using the
419
+ # userid from recipient and the session from from
420
+ @from = Facebooker::User.new(Facebooker::User.cast_to_facebook_id(@recipients.first),Facebooker::Session.create)
421
+ @from.set_profile_fbml(_body.profile, _body.mobile_profile, _body.profile_action, _body.profile_main)
422
+ when Ref
423
+ Facebooker::Session.create.server_cache.set_ref_handle(_body.handle,_body.fbml)
424
+ when UserAction
425
+ @from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general,_body.story_size)
426
+ when Facebooker::StreamPost
427
+ @from.publish_to(_body.target, {:attachment => _body.attachment, :action_links => @action_links, :message => _body.message })
428
+ else
429
+ raise UnspecifiedBodyType.new("You must specify a valid send_as")
430
+ end
431
+ end
432
+
433
+ # nodoc
434
+ # needed for actionview
435
+ def logger
436
+ RAILS_DEFAULT_LOGGER
437
+ end
438
+
439
+ # nodoc
440
+ # delegate to action view. Set up assigns and render
441
+ def render(opts)
442
+ opts = opts.dup
443
+ body = opts.delete(:assigns) || {}
444
+ initialize_template_class(body.dup.merge(:controller=>self)).render(opts)
445
+ end
446
+
447
+
448
+ def initialize_template_class(assigns)
449
+ template_root = "#{RAILS_ROOT}/app/views"
450
+ controller_root = File.join(template_root,self.class.controller_path)
451
+ #only do this on Rails 2.1
452
+ if ActionController::Base.respond_to?(:append_view_path)
453
+ # only add the view path once
454
+ unless ActionController::Base.view_paths.include?(controller_root)
455
+ ActionController::Base.append_view_path(controller_root)
456
+ ActionController::Base.append_view_path(controller_root+"/..")
457
+ end
458
+ view_paths = ActionController::Base.view_paths
459
+ else
460
+ view_paths = [template_root, controller_root]
461
+ end
462
+ returning ActionView::Base.new(view_paths, assigns, self) do |template|
463
+ template.controller=self
464
+ template.extend(self.class.master_helper_module)
465
+ def template.request_comes_from_facebook?
466
+ true
467
+ end
468
+ end
469
+ end
470
+
471
+
472
+ self.master_helper_module = Module.new
473
+ self.master_helper_module.module_eval do
474
+ # url_helper delegates to @controller,
475
+ # so we need to define that in the template
476
+ # we make it point to the publisher
477
+ include ActionView::Helpers::UrlHelper
478
+ include ActionView::Helpers::TextHelper
479
+ include ActionView::Helpers::TagHelper
480
+ include ActionView::Helpers::FormHelper
481
+ include ActionView::Helpers::FormTagHelper
482
+ include ActionView::Helpers::AssetTagHelper
483
+ include ActionView::Helpers::NumberHelper
484
+ include Facebooker::Rails::Helpers
485
+
486
+ #define this for the publisher views
487
+ def protect_against_forgery?
488
+ @paf ||= ActionController::Base.new.send(:protect_against_forgery?)
489
+ end
490
+
491
+ # url_for calls in publishers tend to want full paths
492
+ def url_for(options = {})
493
+ super(options.kind_of?(Hash) ? {:only_path => false}.update(options) : options)
494
+ end
495
+ end
496
+ ActionController::Routing::Routes.named_routes.install(self.master_helper_module)
497
+ include self.master_helper_module
498
+ class <<self
499
+
500
+ def register_all_templates_on_all_applications
501
+ Facebooker.with_all_applications do
502
+ puts "Registering templates for #{Facebooker.api_key}"
503
+ register_all_templates
504
+ end
505
+ end
506
+
507
+ def register_all_templates
508
+ all_templates = instance_methods.grep(/_template$/) - %w(short_story_template full_story_template one_line_story_template)
509
+ all_templates.each do |template|
510
+ template_name=template.sub(/_template$/,"")
511
+ puts "Registering #{template_name}"
512
+ send("register_"+template_name)
513
+ end
514
+ end
515
+
516
+ def unregister_inactive_templates
517
+ session = Facebooker::Session.create
518
+ active_template_ids = FacebookTemplate.all.map(&:bundle_id)
519
+ all_template_ids = session.active_template_bundles.map {|t| t["template_bundle_id"]}
520
+ (all_template_ids - active_template_ids).each do |template_bundle_id|
521
+ session.deactivate_template_bundle_by_id(template_bundle_id)
522
+ end
523
+ end
524
+
525
+ def respond_to?(method_symbol, include_private=false)
526
+ if match = /^(create|deliver|register)_([_a-z]\w*)/.match(method_symbol.to_s)
527
+ instance_methods.include?(match[2])
528
+ else
529
+ super(method_symbol, include_private)
530
+ end
531
+ end
532
+
533
+ def method_missing(name,*args)
534
+ should_send = false
535
+ method = ''
536
+ if md = /^create_(.*)$/.match(name.to_s)
537
+ method = md[1]
538
+ elsif md = /^deliver_(.*)$/.match(name.to_s)
539
+ method = md[1]
540
+ should_send = true
541
+ elsif md = /^register_(.*)$/.match(name.to_s)
542
+ return FacebookTemplate.register(self, md[1])
543
+ else
544
+ super
545
+ end
546
+
547
+ #now create the item
548
+ (publisher=new).send(method,*args)
549
+ case publisher._body
550
+ when UserAction
551
+ publisher._body.template_name = method
552
+ publisher._body.template_id ||= FacebookTemplate.bundle_id_for_class_and_method(self,method)
553
+ end
554
+
555
+ should_send ? publisher.send_message(method) : publisher._body
556
+ end
557
+
558
+ def controller_path
559
+ self.to_s.underscore
560
+ end
561
+
562
+ def helper(*args)
563
+ args.each do |arg|
564
+ case arg
565
+ when Symbol,String
566
+ add_template_helper("#{arg.to_s.camelcase}Helper".constantize)
567
+ when Module
568
+ add_template_helper(arg)
569
+ end
570
+ end
571
+ end
572
+
573
+ def add_template_helper(helper_module) #:nodoc:
574
+ master_helper_module.send :include,helper_module
575
+ include master_helper_module
576
+ end
577
+
578
+
579
+ def inherited(child)
580
+ super
581
+ child.master_helper_module=Module.new
582
+ child.master_helper_module.__send__(:include,self.master_helper_module)
583
+ child.send(:include, child.master_helper_module)
584
+ FacebookTemplate.clear_cache!
585
+ end
586
+
587
+ end
588
+ class PublisherController
589
+ include Facebooker::Rails::Publisher.master_helper_module
590
+ include ActionController::UrlWriter
591
+
592
+ def initialize(source)
593
+ self.class.url_option_source = source
594
+ end
595
+
596
+ class << self
597
+ attr_accessor :url_option_source
598
+ alias :old_default_url_options :default_url_options
599
+ def default_url_options(*args)
600
+ url_option_source.default_url_options(*args)
601
+ end
602
+ end
603
+
604
+ end
605
+
606
+ end
607
+ end
608
+ end