jet-facebooker 1.0.64

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 (156) hide show
  1. data/.autotest +15 -0
  2. data/CHANGELOG.rdoc +24 -0
  3. data/COPYING.rdoc +19 -0
  4. data/Manifest.txt +152 -0
  5. data/README.rdoc +119 -0
  6. data/Rakefile +94 -0
  7. data/TODO.rdoc +4 -0
  8. data/examples/desktop_login.rb +14 -0
  9. data/facebooker.gemspec +42 -0
  10. data/generators/facebook/facebook_generator.rb +14 -0
  11. data/generators/facebook/templates/config/facebooker.yml +49 -0
  12. data/generators/facebook/templates/public/javascripts/facebooker.js +332 -0
  13. data/generators/facebook_controller/USAGE +33 -0
  14. data/generators/facebook_controller/facebook_controller_generator.rb +40 -0
  15. data/generators/facebook_controller/templates/controller.rb +7 -0
  16. data/generators/facebook_controller/templates/functional_test.rb +11 -0
  17. data/generators/facebook_controller/templates/helper.rb +2 -0
  18. data/generators/facebook_controller/templates/view.fbml.erb +2 -0
  19. data/generators/facebook_controller/templates/view.html.erb +2 -0
  20. data/generators/facebook_publisher/facebook_publisher_generator.rb +14 -0
  21. data/generators/facebook_publisher/templates/create_facebook_templates.rb +15 -0
  22. data/generators/facebook_publisher/templates/publisher.rb +3 -0
  23. data/generators/facebook_scaffold/USAGE +27 -0
  24. data/generators/facebook_scaffold/facebook_scaffold_generator.rb +118 -0
  25. data/generators/facebook_scaffold/templates/controller.rb +93 -0
  26. data/generators/facebook_scaffold/templates/facebook_style.css +2579 -0
  27. data/generators/facebook_scaffold/templates/functional_test.rb +89 -0
  28. data/generators/facebook_scaffold/templates/helper.rb +2 -0
  29. data/generators/facebook_scaffold/templates/layout.fbml.erb +6 -0
  30. data/generators/facebook_scaffold/templates/layout.html.erb +17 -0
  31. data/generators/facebook_scaffold/templates/style.css +74 -0
  32. data/generators/facebook_scaffold/templates/view_edit.fbml.erb +13 -0
  33. data/generators/facebook_scaffold/templates/view_edit.html.erb +18 -0
  34. data/generators/facebook_scaffold/templates/view_index.fbml.erb +24 -0
  35. data/generators/facebook_scaffold/templates/view_index.html.erb +24 -0
  36. data/generators/facebook_scaffold/templates/view_new.fbml.erb +12 -0
  37. data/generators/facebook_scaffold/templates/view_new.html.erb +17 -0
  38. data/generators/facebook_scaffold/templates/view_show.fbml.erb +10 -0
  39. data/generators/facebook_scaffold/templates/view_show.html.erb +10 -0
  40. data/generators/publisher/publisher_generator.rb +14 -0
  41. data/generators/xd_receiver/templates/xd_receiver.html +10 -0
  42. data/generators/xd_receiver/templates/xd_receiver_ssl.html +10 -0
  43. data/generators/xd_receiver/xd_receiver_generator.rb +10 -0
  44. data/init.rb +26 -0
  45. data/install.rb +12 -0
  46. data/lib/facebooker.rb +261 -0
  47. data/lib/facebooker/adapters/adapter_base.rb +91 -0
  48. data/lib/facebooker/adapters/bebo_adapter.rb +77 -0
  49. data/lib/facebooker/adapters/facebook_adapter.rb +60 -0
  50. data/lib/facebooker/admin.rb +42 -0
  51. data/lib/facebooker/application.rb +37 -0
  52. data/lib/facebooker/attachment.rb +51 -0
  53. data/lib/facebooker/batch_request.rb +45 -0
  54. data/lib/facebooker/data.rb +57 -0
  55. data/lib/facebooker/feed.rb +78 -0
  56. data/lib/facebooker/logging.rb +44 -0
  57. data/lib/facebooker/mobile.rb +20 -0
  58. data/lib/facebooker/mock/service.rb +50 -0
  59. data/lib/facebooker/mock/session.rb +18 -0
  60. data/lib/facebooker/model.rb +139 -0
  61. data/lib/facebooker/models/affiliation.rb +10 -0
  62. data/lib/facebooker/models/album.rb +11 -0
  63. data/lib/facebooker/models/applicationproperties.rb +39 -0
  64. data/lib/facebooker/models/applicationrestrictions.rb +10 -0
  65. data/lib/facebooker/models/comment.rb +9 -0
  66. data/lib/facebooker/models/cookie.rb +10 -0
  67. data/lib/facebooker/models/education_info.rb +11 -0
  68. data/lib/facebooker/models/event.rb +28 -0
  69. data/lib/facebooker/models/family_relative_info.rb +7 -0
  70. data/lib/facebooker/models/friend_list.rb +16 -0
  71. data/lib/facebooker/models/group.rb +36 -0
  72. data/lib/facebooker/models/info_item.rb +10 -0
  73. data/lib/facebooker/models/info_section.rb +10 -0
  74. data/lib/facebooker/models/location.rb +8 -0
  75. data/lib/facebooker/models/message_thread.rb +89 -0
  76. data/lib/facebooker/models/notifications.rb +17 -0
  77. data/lib/facebooker/models/page.rb +46 -0
  78. data/lib/facebooker/models/photo.rb +19 -0
  79. data/lib/facebooker/models/tag.rb +12 -0
  80. data/lib/facebooker/models/user.rb +722 -0
  81. data/lib/facebooker/models/video.rb +9 -0
  82. data/lib/facebooker/models/work_info.rb +10 -0
  83. data/lib/facebooker/parser.rb +951 -0
  84. data/lib/facebooker/rails/backwards_compatible_param_checks.rb +31 -0
  85. data/lib/facebooker/rails/controller.rb +353 -0
  86. data/lib/facebooker/rails/cucumber.rb +28 -0
  87. data/lib/facebooker/rails/cucumber/world.rb +40 -0
  88. data/lib/facebooker/rails/extensions/action_controller.rb +48 -0
  89. data/lib/facebooker/rails/extensions/rack_setup.rb +16 -0
  90. data/lib/facebooker/rails/extensions/routing.rb +15 -0
  91. data/lib/facebooker/rails/facebook_form_builder.rb +141 -0
  92. data/lib/facebooker/rails/facebook_pretty_errors.rb +22 -0
  93. data/lib/facebooker/rails/facebook_request_fix.rb +28 -0
  94. data/lib/facebooker/rails/facebook_request_fix_2-3.rb +31 -0
  95. data/lib/facebooker/rails/facebook_session_handling.rb +68 -0
  96. data/lib/facebooker/rails/facebook_url_helper.rb +192 -0
  97. data/lib/facebooker/rails/facebook_url_rewriting.rb +61 -0
  98. data/lib/facebooker/rails/helpers.rb +835 -0
  99. data/lib/facebooker/rails/helpers/fb_connect.rb +161 -0
  100. data/lib/facebooker/rails/helpers/stream_publish.rb +22 -0
  101. data/lib/facebooker/rails/integration_session.rb +38 -0
  102. data/lib/facebooker/rails/profile_publisher_extensions.rb +42 -0
  103. data/lib/facebooker/rails/publisher.rb +600 -0
  104. data/lib/facebooker/rails/routing.rb +49 -0
  105. data/lib/facebooker/rails/test_helpers.rb +68 -0
  106. data/lib/facebooker/rails/utilities.rb +22 -0
  107. data/lib/facebooker/server_cache.rb +24 -0
  108. data/lib/facebooker/service.rb +103 -0
  109. data/lib/facebooker/service/base_service.rb +19 -0
  110. data/lib/facebooker/service/curl_service.rb +44 -0
  111. data/lib/facebooker/service/net_http_service.rb +12 -0
  112. data/lib/facebooker/service/typhoeus_multi_service.rb +27 -0
  113. data/lib/facebooker/service/typhoeus_service.rb +17 -0
  114. data/lib/facebooker/session.rb +771 -0
  115. data/lib/facebooker/stream_post.rb +19 -0
  116. data/lib/facebooker/version.rb +9 -0
  117. data/lib/net/http_multipart_post.rb +123 -0
  118. data/lib/rack/facebook.rb +89 -0
  119. data/lib/rack/facebook_session.rb +21 -0
  120. data/lib/tasks/facebooker.rake +19 -0
  121. data/lib/tasks/facebooker.rb +2 -0
  122. data/lib/tasks/tunnel.rake +46 -0
  123. data/rails/init.rb +1 -0
  124. data/setup.rb +1585 -0
  125. data/templates/layout.erb +24 -0
  126. data/test/facebooker/adapters_test.rb +191 -0
  127. data/test/facebooker/admin_test.rb +102 -0
  128. data/test/facebooker/application_test.rb +110 -0
  129. data/test/facebooker/attachment_test.rb +72 -0
  130. data/test/facebooker/batch_request_test.rb +83 -0
  131. data/test/facebooker/data_test.rb +86 -0
  132. data/test/facebooker/logging_test.rb +43 -0
  133. data/test/facebooker/mobile_test.rb +45 -0
  134. data/test/facebooker/model_test.rb +133 -0
  135. data/test/facebooker/models/event_test.rb +15 -0
  136. data/test/facebooker/models/page_test.rb +56 -0
  137. data/test/facebooker/models/photo_test.rb +16 -0
  138. data/test/facebooker/models/user_test.rb +1074 -0
  139. data/test/facebooker/rails/facebook_request_fix_2-3_test.rb +25 -0
  140. data/test/facebooker/rails/facebook_url_rewriting_test.rb +76 -0
  141. data/test/facebooker/rails/integration_session_test.rb +13 -0
  142. data/test/facebooker/rails/publisher_test.rb +532 -0
  143. data/test/facebooker/rails_integration_test.rb +1543 -0
  144. data/test/facebooker/server_cache_test.rb +44 -0
  145. data/test/facebooker/service_test.rb +58 -0
  146. data/test/facebooker/session_test.rb +883 -0
  147. data/test/facebooker_test.rb +1175 -0
  148. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  149. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  150. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  151. data/test/net/http_multipart_post_test.rb +52 -0
  152. data/test/rack/facebook_session_test.rb +34 -0
  153. data/test/rack/facebook_test.rb +73 -0
  154. data/test/rails_test_helper.rb +36 -0
  155. data/test/test_helper.rb +74 -0
  156. metadata +289 -0
@@ -0,0 +1,161 @@
1
+ module Facebooker
2
+ module Rails
3
+ module Helpers
4
+ module FbConnect
5
+ include Facebooker::Rails::Helpers::StreamPublish
6
+ def fb_connect_javascript_tag(options = {})
7
+ # accept both Rails and Facebook locale formatting, i.e. "en-US" and "en_US".
8
+ lang = "/#{options[:lang].to_s.gsub('-', '_')}" if options[:lang]
9
+ # dont use the javascript_include_tag helper since it adds a .js at the end
10
+ if request.ssl?
11
+ "<script src=\"https://ssl.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
12
+ else
13
+ "<script src=\"http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php#{lang}\" type=\"text/javascript\"></script>"
14
+ end
15
+ end
16
+
17
+ #
18
+ # For information on the :app_settings argument see http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Facebook.Init_2
19
+ # 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
20
+ # and Rails' Hash#to_json always quotes strings so there is no way to indicate when the value should be a javascript function.
21
+ # For this reason :app_settings needs to be a string that is valid JSON (including the {}'s).
22
+ #
23
+ def init_fb_connect(*required_features,&proc)
24
+ additions = ""
25
+ if block_given?
26
+ additions = capture(&proc)
27
+ end
28
+
29
+ # Yes, app_settings is set to a string of an empty JSON element. That's intentional.
30
+ options = {:js => :prototype, :app_settings => '{}'}
31
+
32
+ if required_features.last.is_a?(Hash)
33
+ options.merge!(required_features.pop.symbolize_keys)
34
+ end
35
+
36
+ if request.ssl?
37
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver_ssl.html', #{options[:app_settings]});"
38
+ else
39
+ init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver.html', #{options[:app_settings]});"
40
+ end
41
+ unless required_features.blank?
42
+ init_string = <<-FBML
43
+ #{case options[:js]
44
+ when :jquery then "jQuery(document).ready("
45
+ when :mootools then "window.addEvent('domready',"
46
+ when :dojo then "dojo.addOnLoad("
47
+ else "Element.observe(window,'load',"
48
+ end} function() {
49
+ FB_RequireFeatures(#{required_features.to_json}, function() {
50
+ #{init_string}
51
+ #{additions}
52
+ });
53
+ });
54
+ FBML
55
+ end
56
+
57
+ # block_is_within_action_view? is rails 2.1.x and has been
58
+ # deprecated. rails >= 2.2.x uses block_called_from_erb?
59
+ block_tester = respond_to?(:block_is_within_action_view?) ?
60
+ :block_is_within_action_view? : :block_called_from_erb?
61
+
62
+ if block_given? && send(block_tester, proc)
63
+ versioned_concat(javascript_tag(init_string),proc.binding)
64
+ else
65
+ javascript_tag init_string
66
+ end
67
+ end
68
+
69
+ #
70
+ # Render an <fb:login-button> element
71
+ #
72
+ # ==== Examples
73
+ #
74
+ # <%= fb_login_button%>
75
+ # => <fb:login-button></fb:login-button>
76
+ #
77
+ # Specifying a javascript callback
78
+ #
79
+ # <%= fb_login_button 'update_something();'%>
80
+ # => <fb:login-button onlogin='update_something();'></fb:login-button>
81
+ #
82
+ # Adding options <em>See:</em> http://wiki.developers.facebook.com/index.php/Fb:login-button
83
+ #
84
+ # <%= fb_login_button 'update_something();', :size => :small, :background => :dark%>
85
+ # => <fb:login-button background='dark' onlogin='update_something();' size='small'></fb:login-button>
86
+ #
87
+ # :text option allows you to set the text value of the
88
+ # button. *A note!* This will only do what you expect it to do
89
+ # if you set :v => 2 as well.
90
+ #
91
+ # <%= fb_login_button 'update_somethign();',
92
+ # :text => 'Loginto Facebook', :v => 2 %>
93
+ # => <fb:login-button v='2' onlogin='update_something();'>Login to Facebook</fb:login-button>
94
+ def fb_login_button(*args)
95
+
96
+ callback = args.first
97
+ options = args[1] || {}
98
+ options.merge!(:onlogin=>callback) if callback
99
+
100
+ text = options.delete(:text)
101
+
102
+ content_tag("fb:login-button",text, options)
103
+ end
104
+
105
+ #
106
+ # Render an <fb:login-button> element, similar to
107
+ # fb_login_button. Adds a js redirect to the onlogin event via rjs.
108
+ #
109
+ # ==== Examples
110
+ #
111
+ # fb_login_and_redirect '/other_page'
112
+ # => <fb:login-button onlogin="window.location.href = &quot;/other_page&quot;;"></fb:login-button>
113
+ #
114
+ # Like #fb_login_button, this also supports the :text option
115
+ #
116
+ # fb_login_and_redirect '/other_page', :text => "Login with Facebook", :v => '2'
117
+ # => <fb:login-button onlogin="window.location.href = &quot;/other_page&quot;;" v="2">Login with Facebook</fb:login-button>
118
+ #
119
+ def fb_login_and_redirect(url, options = {})
120
+ js = update_page do |page|
121
+ page.redirect_to url
122
+ end
123
+
124
+ text = options.delete(:text)
125
+
126
+ content_tag("fb:login-button",text,options.merge(:onlogin=>js))
127
+ end
128
+
129
+ def fb_unconnected_friends_count
130
+ content_tag "fb:unconnected-friends-count",nil
131
+ end
132
+
133
+ def fb_logout_link(text,url,*args)
134
+ js = update_page do |page|
135
+ page.call "FB.Connect.logoutAndRedirect",url
136
+ end
137
+ link_to_function text, js, *args
138
+ end
139
+
140
+ def fb_bookmark_link(text,url,*args)
141
+ js = update_page do |page|
142
+ page.call "FB.Connect.showBookmarkDialog",url
143
+ end
144
+ link_to_function text, js, *args
145
+ end
146
+
147
+ def fb_user_action(action, user_message = nil, prompt = "", callback = nil)
148
+ defaulted_callback = callback || "null"
149
+ update_page do |page|
150
+ page.call("FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,page.literal("FB.RequireConnect.promptConnect"),page.literal(defaulted_callback),prompt,user_message.nil? ? nil : {:value=>user_message})
151
+ end
152
+ end
153
+
154
+ def fb_connect_stream_publish(stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil)
155
+ stream_publish("FB.Connect.streamPublish",stream_post,user_message_prompt,callback,auto_publish,actor)
156
+ end
157
+
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,22 @@
1
+ module Facebooker
2
+ module Rails
3
+ module Helpers
4
+ module StreamPublish
5
+ def stream_publish(js_method,stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil)
6
+ defaulted_callback = callback || "null"
7
+ update_page do |page|
8
+ page.call(js_method,
9
+ stream_post.user_message,
10
+ stream_post.attachment.to_hash,
11
+ stream_post.action_links,
12
+ Facebooker::User.cast_to_facebook_id(stream_post.target),
13
+ user_message_prompt,
14
+ page.literal(defaulted_callback),
15
+ auto_publish,
16
+ Facebooker::User.cast_to_facebook_id(actor))
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ 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,600 @@
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 method_missing(name,*args)
526
+ should_send = false
527
+ method = ''
528
+ if md = /^create_(.*)$/.match(name.to_s)
529
+ method = md[1]
530
+ elsif md = /^deliver_(.*)$/.match(name.to_s)
531
+ method = md[1]
532
+ should_send = true
533
+ elsif md = /^register_(.*)$/.match(name.to_s)
534
+ return FacebookTemplate.register(self, md[1])
535
+ else
536
+ super
537
+ end
538
+
539
+ #now create the item
540
+ (publisher=new).send(method,*args)
541
+ case publisher._body
542
+ when UserAction
543
+ publisher._body.template_name = method
544
+ publisher._body.template_id ||= FacebookTemplate.bundle_id_for_class_and_method(self,method)
545
+ end
546
+
547
+ should_send ? publisher.send_message(method) : publisher._body
548
+ end
549
+
550
+ def controller_path
551
+ self.to_s.underscore
552
+ end
553
+
554
+ def helper(*args)
555
+ args.each do |arg|
556
+ case arg
557
+ when Symbol,String
558
+ add_template_helper("#{arg.to_s.camelcase}Helper".constantize)
559
+ when Module
560
+ add_template_helper(arg)
561
+ end
562
+ end
563
+ end
564
+
565
+ def add_template_helper(helper_module) #:nodoc:
566
+ master_helper_module.send :include,helper_module
567
+ include master_helper_module
568
+ end
569
+
570
+
571
+ def inherited(child)
572
+ super
573
+ child.master_helper_module=Module.new
574
+ child.master_helper_module.__send__(:include,self.master_helper_module)
575
+ child.send(:include, child.master_helper_module)
576
+ FacebookTemplate.clear_cache!
577
+ end
578
+
579
+ end
580
+ class PublisherController
581
+ include Facebooker::Rails::Publisher.master_helper_module
582
+ include ActionController::UrlWriter
583
+
584
+ def initialize(source)
585
+ self.class.url_option_source = source
586
+ end
587
+
588
+ class << self
589
+ attr_accessor :url_option_source
590
+ alias :old_default_url_options :default_url_options
591
+ def default_url_options(*args)
592
+ url_option_source.default_url_options(*args)
593
+ end
594
+ end
595
+
596
+ end
597
+
598
+ end
599
+ end
600
+ end