facebooker 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGELOG.txt +0 -0
  2. data/COPYING +19 -0
  3. data/History.txt +3 -0
  4. data/Manifest.txt +60 -0
  5. data/README +44 -0
  6. data/README.txt +79 -0
  7. data/Rakefile +38 -0
  8. data/TODO.txt +10 -0
  9. data/facebooker.yml.tpl +36 -0
  10. data/init.rb +52 -0
  11. data/install.rb +5 -0
  12. data/lib/facebooker.rb +63 -0
  13. data/lib/facebooker/affiliation.rb +10 -0
  14. data/lib/facebooker/album.rb +11 -0
  15. data/lib/facebooker/cookie.rb +10 -0
  16. data/lib/facebooker/data.rb +38 -0
  17. data/lib/facebooker/education_info.rb +11 -0
  18. data/lib/facebooker/event.rb +26 -0
  19. data/lib/facebooker/feed.rb +65 -0
  20. data/lib/facebooker/group.rb +35 -0
  21. data/lib/facebooker/location.rb +8 -0
  22. data/lib/facebooker/model.rb +118 -0
  23. data/lib/facebooker/notifications.rb +17 -0
  24. data/lib/facebooker/parser.rb +386 -0
  25. data/lib/facebooker/photo.rb +9 -0
  26. data/lib/facebooker/rails/controller.rb +174 -0
  27. data/lib/facebooker/rails/facebook_asset_path.rb +18 -0
  28. data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
  29. data/lib/facebooker/rails/facebook_request_fix.rb +14 -0
  30. data/lib/facebooker/rails/facebook_session_handling.rb +58 -0
  31. data/lib/facebooker/rails/facebook_url_rewriting.rb +31 -0
  32. data/lib/facebooker/rails/helpers.rb +535 -0
  33. data/lib/facebooker/rails/routing.rb +49 -0
  34. data/lib/facebooker/rails/test_helpers.rb +11 -0
  35. data/lib/facebooker/rails/utilities.rb +22 -0
  36. data/lib/facebooker/server_cache.rb +24 -0
  37. data/lib/facebooker/service.rb +25 -0
  38. data/lib/facebooker/session.rb +385 -0
  39. data/lib/facebooker/tag.rb +12 -0
  40. data/lib/facebooker/user.rb +200 -0
  41. data/lib/facebooker/version.rb +9 -0
  42. data/lib/facebooker/work_info.rb +9 -0
  43. data/lib/net/http_multipart_post.rb +123 -0
  44. data/lib/tasks/facebooker.rake +16 -0
  45. data/lib/tasks/tunnel.rake +39 -0
  46. data/setup.rb +1585 -0
  47. data/test/event_test.rb +15 -0
  48. data/test/facebook_cache_test.rb +43 -0
  49. data/test/facebook_data_test.rb +50 -0
  50. data/test/facebooker_test.rb +766 -0
  51. data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
  52. data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
  53. data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
  54. data/test/http_multipart_post_test.rb +54 -0
  55. data/test/model_test.rb +79 -0
  56. data/test/rails_integration_test.rb +732 -0
  57. data/test/session_test.rb +396 -0
  58. data/test/test_helper.rb +54 -0
  59. data/test/user_test.rb +101 -0
  60. metadata +130 -0
@@ -0,0 +1,10 @@
1
+ require 'facebooker/model'
2
+ module Facebooker
3
+
4
+ ##
5
+ # A simple representation of a cookie.
6
+ class Cookie
7
+ include Model
8
+ attr_accessor :uid, :name, :value, :expires, :path
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ module Facebooker
2
+ class Data
3
+ def initialize(session)
4
+ @session = session
5
+ end
6
+
7
+ ##
8
+ # ** BETA ***
9
+ # Sets a cookie on Facebook
10
+ # +user+ The user for whom this cookie needs to be set.
11
+ # +name+ Name of the cookie
12
+ # +value+ Value of the cookie
13
+ # Optional:
14
+ # +expires+ Time when the cookie should expire. If not specified, the cookie never expires.
15
+ # +path+ Path relative to the application's callback URL, with which the cookie should be associated. (default value is /?
16
+ def set_cookie(user, name, value, expires=nil, path=nil)
17
+ (@session.post 'facebook.data.setCookie',
18
+ :uid => User.cast_to_facebook_id(user),
19
+ :name => name,
20
+ :value => value,
21
+ :expires => expires,
22
+ :path => path) == '1'
23
+ end
24
+
25
+ ##
26
+ # ** BETA ***
27
+ # Gets a cookie stored on Facebook
28
+ # +user+ The user from whom to get the cookies.
29
+ # Optional:
30
+ # +name+ The name of the cookie. If not specified, all the cookies for the given user get returned.
31
+ def get_cookies(user, name=nil)
32
+ @cookies = @session.post(
33
+ 'facebook.data.getCookies', :uid => User.cast_to_facebook_id(user), :name => name).map do |hash|
34
+ Cookie.from_hash(hash)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ module Facebooker
2
+ class EducationInfo
3
+ class HighschoolInfo
4
+ include Model
5
+ attr_accessor :hs1_id, :hs2_id, :grad_year, :hs1_name, :hs2_name
6
+ end
7
+
8
+ include Model
9
+ attr_accessor :concentrations, :name, :year, :degree
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ require 'facebooker/model'
2
+ module Facebooker
3
+ class Event
4
+
5
+ ##
6
+ # The relationship between a Facebook user and an Event to which he or she has been
7
+ # invited and may or may not be attending (based on #rsvp_status)
8
+ class Attendance
9
+ include Model
10
+ attr_accessor :eid, :uid, :rsvp_status
11
+
12
+ ##
13
+ # Get the full, populated Event object which this Attendance is associated with.
14
+ # First access will query the Facebook API (facebook.events.get). Subsequent
15
+ # calls are retrieved from in-memory cache.
16
+ def event
17
+ @event ||= Event.from_hash(session.post('facebook.events.get', :eids => [eid]).first)
18
+ end
19
+
20
+ #TODO: implement user() method
21
+ end
22
+
23
+ include Model
24
+ attr_accessor :eid, :pic, :pic_small, :pic_big, :name, :creator, :update_time, :description, :tagline, :venue, :host, :event_type, :nid, :location, :end_time, :start_time, :event_subtype
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ module Facebooker
2
+ module Feed
3
+ METHODS = {'Action' => 'facebook.feed.publishActionOfUser', 'Story' => 'facebook.feed.publishStoryToUser',
4
+ 'TemplatizedAction' => 'facebook.feed.publishTemplatizedAction' }
5
+
6
+ class ActionBase
7
+ 1.upto(4) do |num|
8
+ attr_accessor "image_#{num}"
9
+ attr_accessor "image_#{num}_link"
10
+ end
11
+
12
+ protected
13
+ def image_params
14
+ image_hash = {}
15
+ 1.upto(4) do |num|
16
+ image_attribute = "image_#{num}"
17
+ image_link_attribute = image_attribute + "_link"
18
+ self.__send__(image_attribute) ? image_hash[image_attribute] = self.__send__(image_attribute) : nil
19
+ self.__send__(image_link_attribute) ? image_hash[image_link_attribute] = self.__send__(image_link_attribute) : nil
20
+ end
21
+ image_hash
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Representation of a templatized action to be published into a user's news feed
27
+ # Deprecation Notice: +actor_id+ will be removed by Facebook sometime in February 2008
28
+ class TemplatizedAction < ActionBase
29
+ attr_accessor :actor_id, :page_actor_id, :title_template, :title_data, :body_template, :body_data, :body_general, :target_ids
30
+
31
+ def to_params
32
+ raise "Must set title_template" if self.title_template.nil?
33
+ { :actor_id => actor_id, :page_actor_id => page_actor_id,
34
+ :title_template => title_template, :title_data => convert_json(title_data),
35
+ :body_template => body_template, :body_data => convert_json(body_data), :body_general => body_general,
36
+ :target_ids => target_ids }.merge image_params
37
+ end
38
+
39
+ def convert_json(hash_or_string)
40
+ (hash_or_string.is_a?(Hash) and hash_or_string.respond_to?(:to_json)) ? hash_or_string.to_json : hash_or_string
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Representation of a story to be published into a user's news feed.
46
+ class Story < ActionBase
47
+ attr_accessor :title, :body
48
+
49
+ ##
50
+ # Converts Story to a Hash of its attributes for use as parameters to Facebook REST API calls
51
+ def to_params
52
+ raise "Must set title before converting" if self.title.nil?
53
+ { :title => title, :body => body }.merge image_params
54
+ end
55
+
56
+ end
57
+ Action = Story.dup
58
+ def Action.name
59
+ "Action"
60
+ end
61
+ ##
62
+ # Representation of an action to be published into a user's news feed. Alias for Story.
63
+ class Action; end
64
+ end
65
+ end
@@ -0,0 +1,35 @@
1
+ require 'facebooker/model'
2
+ module Facebooker
3
+ class Group
4
+ ##
5
+ # The model of a user's relationship to a group. Users can occupy different positions within a group (e.g. 'owner')
6
+ class Membership
7
+ include Model
8
+ attr_accessor :position, :gid, :uid
9
+ end
10
+ include Model
11
+ attr_accessor :pic, :pic_small, :pic_big, :name, :creator, :recent_news, :gid, :update_time, :group_subtype, :group_type, :website, :office, :description, :venue, :nid
12
+
13
+
14
+ ##
15
+ # Get the full list of members as populated User objects. First time fetches group members via Facebook API call.
16
+ # Subsequent calls return cached values.
17
+ # This is a convenience method for getting all of the Membership instances and instantiating User instances for each Membership.
18
+ def members
19
+ @members ||= memberships.map do |membership|
20
+ User.new(membership.uid, session)
21
+ end
22
+ end
23
+
24
+ ##
25
+ # Get a list of Membership instances associated with this Group. First call retrieves the Membership instances via a Facebook
26
+ # API call. Subsequent calls are retrieved from in-memory cache.
27
+ def memberships
28
+ @memberships ||= session.post('facebook.groups.getMembers', :gid => gid).map do |hash|
29
+ Membership.from_hash(hash) do |membership|
30
+ membership.gid = gid
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ module Facebooker
2
+ ##
3
+ # Representation of Location used in all places where a Location is specified.
4
+ class Location
5
+ include Model
6
+ attr_accessor :city, :zip, :country, :state
7
+ end
8
+ end
@@ -0,0 +1,118 @@
1
+ module Facebooker
2
+ ##
3
+ # helper methods primarily supporting the management of Ruby objects which are populatable via Hashes.
4
+ # Since most Facebook API calls accept and return hashes of data (as XML), the Model module allows us to
5
+ # directly populate a model's attributes given a Hash with matching key names.
6
+ module Model
7
+ class UnboundSessionException < Exception; end
8
+ def self.included(includer)
9
+ includer.extend ClassMethods
10
+ includer.__send__(:attr_writer, :session)
11
+ includer.__send__(:attr_reader, :anonymous_fields)
12
+ end
13
+ module ClassMethods
14
+ ##
15
+ # Instantiate a new instance of the class into which we are included and populate that instance's
16
+ # attributes given the provided Hash. Key names in the Hash should map to attribute names on the model.
17
+ def from_hash(hash)
18
+ instance = new(hash)
19
+ yield instance if block_given?
20
+ instance
21
+ end
22
+
23
+ ##
24
+ # Create a standard attr_writer and a populating_attr_reader
25
+ def populating_attr_accessor(*symbols)
26
+ attr_writer *symbols
27
+ populating_attr_reader *symbols
28
+ end
29
+
30
+ ##
31
+ # Create a reader that will attempt to populate the model if it has not already been populated
32
+ def populating_attr_reader(*symbols)
33
+ symbols.each do |symbol|
34
+ define_method(symbol) do
35
+ populate unless populated?
36
+ instance_variable_get("@#{symbol}")
37
+ end
38
+ end
39
+ end
40
+
41
+ def populating_hash_settable_accessor(symbol, klass)
42
+ populating_attr_reader symbol
43
+ hash_settable_writer(symbol, klass)
44
+ end
45
+
46
+ def populating_hash_settable_list_accessor(symbol, klass)
47
+ populating_attr_reader symbol
48
+ hash_settable_list_writer(symbol, klass)
49
+ end
50
+
51
+ #
52
+ # Declares an attribute named ::symbol:: which can be set with either an instance of ::klass::
53
+ # or a Hash which will be used to populate a new instance of ::klass::.
54
+ def hash_settable_accessor(symbol, klass)
55
+ attr_reader symbol
56
+ hash_settable_writer(symbol, klass)
57
+ end
58
+
59
+ def hash_settable_writer(symbol, klass)
60
+ define_method("#{symbol}=") do |value|
61
+ instance_variable_set("@#{symbol}", value.kind_of?(Hash) ? klass.from_hash(value) : value)
62
+ end
63
+ end
64
+
65
+ #
66
+ # Declares an attribute named ::symbol:: which can be set with either a list of instances of ::klass::
67
+ # or a list of Hashes which will be used to populate a new instance of ::klass::.
68
+ def hash_settable_list_accessor(symbol, klass)
69
+ attr_reader symbol
70
+ hash_settable_list_writer(symbol, klass)
71
+ end
72
+
73
+ def hash_settable_list_writer(symbol, klass)
74
+ define_method("#{symbol}=") do |list|
75
+ instance_variable_set("@#{symbol}", list.map do |item|
76
+ item.kind_of?(Hash) ? klass.from_hash(item) : item
77
+ end)
78
+ end
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Centralized, error-checked place for a model to get the session to which it is bound.
84
+ # Any Facebook API queries require a Session instance.
85
+ def session
86
+ @session || (raise UnboundSessionException, "Must bind this object to a Facebook session before querying")
87
+ end
88
+
89
+ #
90
+ # This gets populated from FQL queries.
91
+ def anon=(value)
92
+ @anonymous_fields = value
93
+ end
94
+
95
+ def initialize(hash = {})
96
+ populate_from_hash!(hash)
97
+ end
98
+
99
+ def populate
100
+ raise NotImplementedError, "#{self.class} included me and should have overriden me"
101
+ end
102
+
103
+ def populated?
104
+ !@populated.nil?
105
+ end
106
+
107
+ ##
108
+ # Set model's attributes via Hash. Keys should map directly to the model's attribute names.
109
+ def populate_from_hash!(hash)
110
+ unless hash.empty?
111
+ hash.each do |key, value|
112
+ self.__send__("#{key}=", value)
113
+ end
114
+ @populated = true
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,17 @@
1
+ module Facebooker
2
+ class Notifications
3
+ include Model
4
+ attr_accessor :messages, :group_invites, :pokes, :friend_requests, :event_invites, :shares
5
+
6
+ [:Messages, :Pokes, :Shares].each do |notification_type|
7
+ const_set(notification_type, Class.new do
8
+ include Model
9
+ attr_accessor :unread, :most_recent
10
+ end)
11
+ attribute_name = "#{notification_type.to_s.downcase}"
12
+ define_method("#{attribute_name}=") do |value|
13
+ instance_variable_set("@#{attribute_name}", value.kind_of?(Hash) ? Notifications.const_get(notification_type).from_hash(value) : value)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,386 @@
1
+ require 'rexml/document'
2
+ require 'facebooker/session'
3
+ module Facebooker
4
+ class Parser
5
+
6
+ module REXMLElementExtensions
7
+ def text_value
8
+ self.children.first.to_s.strip
9
+ end
10
+ end
11
+
12
+ ::REXML::Element.__send__(:include, REXMLElementExtensions)
13
+
14
+ def self.parse(method, data)
15
+ Errors.process(data)
16
+ parser = Parser::PARSERS[method]
17
+ parser.process(
18
+ data
19
+ )
20
+ end
21
+
22
+ def self.array_of(response_element, element_name)
23
+ values_to_return = []
24
+ response_element.elements.each(element_name) do |element|
25
+ values_to_return << yield(element)
26
+ end
27
+ values_to_return
28
+ end
29
+
30
+ def self.array_of_text_values(response_element, element_name)
31
+ array_of(response_element, element_name) do |element|
32
+ element.text_value
33
+ end
34
+ end
35
+
36
+ def self.array_of_hashes(response_element, element_name)
37
+ array_of(response_element, element_name) do |element|
38
+ hashinate(element)
39
+ end
40
+ end
41
+
42
+ def self.element(name, data)
43
+ data = data.body rescue data # either data or an HTTP response
44
+ doc = REXML::Document.new(data)
45
+ doc.elements.each(name) do |element|
46
+ return element
47
+ end
48
+ raise "Element #{name} not found in #{data}"
49
+ end
50
+
51
+ def self.hash_or_value_for(element)
52
+ if element.children.size == 1 && element.children.first.kind_of?(REXML::Text)
53
+ element.text_value
54
+ else
55
+ hashinate(element)
56
+ end
57
+ end
58
+
59
+ def self.hashinate(response_element)
60
+ response_element.children.reject{|c| c.kind_of? REXML::Text}.inject({}) do |hash, child|
61
+ hash[child.name] = if child.children.size == 1 && child.children.first.kind_of?(REXML::Text)
62
+ anonymous_field_from(child, hash) || child.text_value
63
+ else
64
+ if child.attributes['list'] == 'true'
65
+ child.children.reject{|c| c.kind_of? REXML::Text}.map do |subchild|
66
+ hash_or_value_for(subchild)
67
+ end
68
+ else
69
+ child.children.reject{|c| c.kind_of? REXML::Text}.inject({}) do |subhash, subchild|
70
+ subhash[subchild.name] = hash_or_value_for(subchild)
71
+ subhash
72
+ end
73
+ end
74
+ end
75
+ hash
76
+ end
77
+ end
78
+
79
+ def self.anonymous_field_from(child, hash)
80
+ if child.name == 'anon'
81
+ (hash[child.name] || []) << child.text_value
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ class CreateToken < Parser#:nodoc:
88
+ def self.process(data)
89
+ element('auth_createToken_response', data).text_value
90
+ end
91
+ end
92
+
93
+ class GetSession < Parser#:nodoc:
94
+ def self.process(data)
95
+ hashinate(element('auth_getSession_response', data))
96
+ end
97
+ end
98
+
99
+ class GetFriends < Parser#:nodoc:
100
+ def self.process(data)
101
+ array_of_text_values(element('friends_get_response', data), 'uid')
102
+ end
103
+ end
104
+
105
+ class UserInfo < Parser#:nodoc:
106
+ def self.process(data)
107
+ array_of_hashes(element('users_getInfo_response', data), 'user')
108
+ end
109
+ end
110
+
111
+ class PublishStoryToUser < Parser#:nodoc:
112
+ def self.process(data)
113
+ element('feed_publishStoryToUser_response', data).text_value
114
+ end
115
+ end
116
+
117
+ class PublishActionOfUser < Parser#:nodoc:
118
+ def self.process(data)
119
+ element('feed_publishActionOfUser_response', data).text_value
120
+ end
121
+ end
122
+
123
+ class PublishTemplatizedAction < Parser#:nodoc:
124
+ def self.process(data)
125
+ element('feed_publishTemplatizedAction_response', data).children[1].text_value
126
+ end
127
+ end
128
+
129
+ class GetAppUsers < Parser#:nodoc:
130
+ def self.process(data)
131
+ array_of_text_values(element('friends_getAppUsers_response', data), 'uid')
132
+ end
133
+ end
134
+
135
+ class NotificationsGet < Parser#:nodoc:
136
+ def self.process(data)
137
+ hashinate(element('notifications_get_response', data))
138
+ end
139
+ end
140
+
141
+ class NotificationsSend < Parser#:nodoc:
142
+ def self.process(data)
143
+ element('notifications_send_response', data).text_value
144
+ end
145
+ end
146
+
147
+ class NotificationsSendEmail < Parser#:nodoc:
148
+ def self.process(data)
149
+ element('notifications_sendEmail_response', data).text_value
150
+ end
151
+ end
152
+
153
+ class GetTags < Parser#nodoc:
154
+ def self.process(data)
155
+ array_of_hashes(element('photos_getTags_response', data), 'photo_tag')
156
+ end
157
+ end
158
+
159
+ class AddTags < Parser#nodoc:
160
+ def self.process(data)
161
+ element('photos_addTag_response', data)
162
+ end
163
+ end
164
+
165
+ class GetPhotos < Parser#nodoc:
166
+ def self.process(data)
167
+ array_of_hashes(element('photos_get_response', data), 'photo')
168
+ end
169
+ end
170
+
171
+ class GetAlbums < Parser#nodoc:
172
+ def self.process(data)
173
+ array_of_hashes(element('photos_getAlbums_response', data), 'album')
174
+ end
175
+ end
176
+
177
+ class CreateAlbum < Parser#:nodoc:
178
+ def self.process(data)
179
+ hashinate(element('photos_createAlbum_response', data))
180
+ end
181
+ end
182
+
183
+ class UploadPhoto < Parser#:nodoc:
184
+ def self.process(data)
185
+ hashinate(element('photos_upload_response', data))
186
+ end
187
+ end
188
+
189
+ class SendRequest < Parser#:nodoc:
190
+ def self.process(data)
191
+ element('notifications_sendRequest_response', data).text_value
192
+ end
193
+ end
194
+
195
+ class ProfileFBML < Parser#:nodoc:
196
+ def self.process(data)
197
+ element('profile_getFBML_response', data).text_value
198
+ end
199
+ end
200
+
201
+ class ProfileFBMLSet < Parser#:nodoc:
202
+ def self.process(data)
203
+ element('profile_setFBML_response', data).text_value
204
+ end
205
+ end
206
+
207
+ class FqlQuery < Parser#nodoc
208
+ def self.process(data)
209
+ root = element('fql_query_response', data)
210
+ first_child = root.children.reject{|c| c.kind_of?(REXML::Text)}.first
211
+ [first_child.name, array_of_hashes(root, first_child.name)]
212
+ end
213
+ end
214
+
215
+ class SetRefHandle < Parser#:nodoc:
216
+ def self.process(data)
217
+ element('fbml_setRefHandle_response', data).text_value
218
+ end
219
+ end
220
+
221
+ class RefreshRefURL < Parser#:nodoc:
222
+ def self.process(data)
223
+ element('fbml_refreshRefUrl_response', data).text_value
224
+ end
225
+ end
226
+
227
+ class RefreshImgSrc < Parser#:nodoc:
228
+ def self.process(data)
229
+ element('fbml_refreshImgSrc_response', data).text_value
230
+ end
231
+ end
232
+
233
+ class SetCookie < Parser#:nodoc:
234
+ def self.process(data)
235
+ element('data_setCookie_response', data).text_value
236
+ end
237
+ end
238
+
239
+ class GetCookies < Parser#:nodoc:
240
+ def self.process(data)
241
+ array_of_hashes(element('data_getCookie_response', data), 'cookies')
242
+ end
243
+ end
244
+
245
+ class EventsGet < Parser#:nodoc:
246
+ def self.process(data)
247
+ array_of_hashes(element('events_get_response', data), 'event')
248
+ end
249
+ end
250
+
251
+ class GroupGetMembers < Parser#:nodoc:
252
+ def self.process(data)
253
+ root = element('groups_getMembers_response', data)
254
+ result = ['members', 'admins', 'officers', 'not_replied'].map do |position|
255
+ array_of(root, position) {|element| element}.map do |element|
256
+ array_of_text_values(element, 'uid').map do |uid|
257
+ {:position => position}.merge(:uid => uid)
258
+ end
259
+ end
260
+ end.flatten
261
+ end
262
+ end
263
+
264
+ class EventMembersGet < Parser#:nodoc:
265
+ def self.process(data)
266
+ root = element('events_getMembers_response', data)
267
+ result = ['attending', 'declined', 'unsure', 'not_replied'].map do |rsvp_status|
268
+ array_of(root, rsvp_status) {|element| element}.map do |element|
269
+ array_of_text_values(element, 'uid').map do |uid|
270
+ {:rsvp_status => rsvp_status}.merge(:uid => uid)
271
+ end
272
+ end
273
+ end.flatten
274
+ end
275
+ end
276
+
277
+ class GroupsGet < Parser#:nodoc:
278
+ def self.process(data)
279
+ array_of_hashes(element('groups_get_response', data), 'group')
280
+ end
281
+ end
282
+
283
+ class AreFriends < Parser#:nodoc:
284
+ def self.process(data)
285
+ array_of_hashes(element('friends_areFriends_response', data), 'friend_info').inject({}) do |memo, hash|
286
+ memo[[Integer(hash['uid1']), Integer(hash['uid2'])].sort] = are_friends?(hash['are_friends'])
287
+ memo
288
+ end
289
+ end
290
+
291
+ private
292
+ def self.are_friends?(raw_value)
293
+ if raw_value == '1'
294
+ true
295
+ elsif raw_value == '0'
296
+ false
297
+ else
298
+ nil
299
+ end
300
+ end
301
+ end
302
+
303
+ class Errors < Parser#:nodoc:
304
+ EXCEPTIONS = {
305
+ 1 => Facebooker::Session::UnknownError,
306
+ 2 => Facebooker::Session::ServiceUnavailable,
307
+ 4 => Facebooker::Session::MaxRequestsDepleted,
308
+ 5 => Facebooker::Session::HostNotAllowed,
309
+ 100 => Facebooker::Session::MissingOrInvalidParameter,
310
+ 101 => Facebooker::Session::InvalidAPIKey,
311
+ 102 => Facebooker::Session::SessionExpired,
312
+ 103 => Facebooker::Session::CallOutOfOrder,
313
+ 104 => Facebooker::Session::IncorrectSignature,
314
+ 120 => Facebooker::Session::InvalidAlbumId,
315
+ 321 => Facebooker::Session::AlbumIsFull,
316
+ 324 => Facebooker::Session::MissingOrInvalidImageFile,
317
+ 325 => Facebooker::Session::TooManyUnapprovedPhotosPending,
318
+ 340 => Facebooker::Session::TooManyUserCalls,
319
+ 341 => Facebooker::Session::TooManyUserActionCalls,
320
+ 342 => Facebooker::Session::InvalidFeedTitleLink,
321
+ 343 => Facebooker::Session::InvalidFeedTitleLength,
322
+ 344 => Facebooker::Session::InvalidFeedTitleName,
323
+ 345 => Facebooker::Session::BlankFeedTitle,
324
+ 346 => Facebooker::Session::FeedBodyLengthTooLong,
325
+ 347 => Facebooker::Session::InvalidFeedPhotoSource,
326
+ 348 => Facebooker::Session::InvalidFeedPhotoLink,
327
+ 330 => Facebooker::Session::FeedMarkupInvalid,
328
+ 360 => Facebooker::Session::FeedTitleDataInvalid,
329
+ 361 => Facebooker::Session::FeedTitleTemplateInvalid,
330
+ 362 => Facebooker::Session::FeedBodyDataInvalid,
331
+ 363 => Facebooker::Session::FeedBodyTemplateInvalid,
332
+ 364 => Facebooker::Session::FeedPhotosNotRetrieved,
333
+ 366 => Facebooker::Session::FeedTargetIdsInvalid,
334
+ 601 => Facebooker::Session::FQLParseError,
335
+ 602 => Facebooker::Session::FQLFieldDoesNotExist,
336
+ 603 => Facebooker::Session::FQLTableDoesNotExist,
337
+ 604 => Facebooker::Session::FQLStatementNotIndexable,
338
+ 605 => Facebooker::Session::FQLFunctionDoesNotExist,
339
+ 606 => Facebooker::Session::FQLWrongNumberArgumentsPassedToFunction
340
+ }
341
+ def self.process(data)
342
+ response_element = element('error_response', data) rescue nil
343
+ if response_element
344
+ hash = hashinate(response_element)
345
+ raise EXCEPTIONS[Integer(hash['error_code'])].new(hash['error_msg'])
346
+ end
347
+ end
348
+ end
349
+
350
+ class Parser
351
+ PARSERS = {
352
+ 'facebook.auth.createToken' => CreateToken,
353
+ 'facebook.auth.getSession' => GetSession,
354
+ 'facebook.users.getInfo' => UserInfo,
355
+ 'facebook.friends.get' => GetFriends,
356
+ 'facebook.friends.areFriends' => AreFriends,
357
+ 'facebook.friends.getAppUsers' => GetAppUsers,
358
+ 'facebook.feed.publishStoryToUser' => PublishStoryToUser,
359
+ 'facebook.feed.publishActionOfUser' => PublishActionOfUser,
360
+ 'facebook.feed.publishTemplatizedAction' => PublishTemplatizedAction,
361
+ 'facebook.notifications.get' => NotificationsGet,
362
+ 'facebook.notifications.send' => NotificationsSend,
363
+ 'facebook.notifications.sendRequest' => SendRequest,
364
+ 'facebook.profile.getFBML' => ProfileFBML,
365
+ 'facebook.profile.setFBML' => ProfileFBMLSet,
366
+ 'facebook.fbml.setRefHandle' => SetRefHandle,
367
+ 'facebook.fbml.refreshRefUrl' => RefreshRefURL,
368
+ 'facebook.fbml.refreshImgSrc' => RefreshImgSrc,
369
+ 'facebook.data.setCookie' => SetCookie,
370
+ 'facebook.data.getCookies' => GetCookies,
371
+ 'facebook.fql.query' => FqlQuery,
372
+ 'facebook.photos.get' => GetPhotos,
373
+ 'facebook.photos.getAlbums' => GetAlbums,
374
+ 'facebook.photos.createAlbum' => CreateAlbum,
375
+ 'facebook.photos.getTags' => GetTags,
376
+ 'facebook.photos.addTag' => AddTags,
377
+ 'facebook.photos.upload' => UploadPhoto,
378
+ 'facebook.events.get' => EventsGet,
379
+ 'facebook.groups.get' => GroupsGet,
380
+ 'facebook.events.getMembers' => EventMembersGet,
381
+ 'facebook.groups.getMembers' => GroupGetMembers,
382
+ 'facebook.notifications.sendEmail' => NotificationsSendEmail
383
+
384
+ }
385
+ end
386
+ end