facebooker 0.9.5

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 (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