facebooker 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +0 -0
- data/COPYING +19 -0
- data/History.txt +3 -0
- data/Manifest.txt +60 -0
- data/README +44 -0
- data/README.txt +79 -0
- data/Rakefile +38 -0
- data/TODO.txt +10 -0
- data/facebooker.yml.tpl +36 -0
- data/init.rb +52 -0
- data/install.rb +5 -0
- data/lib/facebooker.rb +63 -0
- data/lib/facebooker/affiliation.rb +10 -0
- data/lib/facebooker/album.rb +11 -0
- data/lib/facebooker/cookie.rb +10 -0
- data/lib/facebooker/data.rb +38 -0
- data/lib/facebooker/education_info.rb +11 -0
- data/lib/facebooker/event.rb +26 -0
- data/lib/facebooker/feed.rb +65 -0
- data/lib/facebooker/group.rb +35 -0
- data/lib/facebooker/location.rb +8 -0
- data/lib/facebooker/model.rb +118 -0
- data/lib/facebooker/notifications.rb +17 -0
- data/lib/facebooker/parser.rb +386 -0
- data/lib/facebooker/photo.rb +9 -0
- data/lib/facebooker/rails/controller.rb +174 -0
- data/lib/facebooker/rails/facebook_asset_path.rb +18 -0
- data/lib/facebooker/rails/facebook_form_builder.rb +112 -0
- data/lib/facebooker/rails/facebook_request_fix.rb +14 -0
- data/lib/facebooker/rails/facebook_session_handling.rb +58 -0
- data/lib/facebooker/rails/facebook_url_rewriting.rb +31 -0
- data/lib/facebooker/rails/helpers.rb +535 -0
- data/lib/facebooker/rails/routing.rb +49 -0
- data/lib/facebooker/rails/test_helpers.rb +11 -0
- data/lib/facebooker/rails/utilities.rb +22 -0
- data/lib/facebooker/server_cache.rb +24 -0
- data/lib/facebooker/service.rb +25 -0
- data/lib/facebooker/session.rb +385 -0
- data/lib/facebooker/tag.rb +12 -0
- data/lib/facebooker/user.rb +200 -0
- data/lib/facebooker/version.rb +9 -0
- data/lib/facebooker/work_info.rb +9 -0
- data/lib/net/http_multipart_post.rb +123 -0
- data/lib/tasks/facebooker.rake +16 -0
- data/lib/tasks/tunnel.rake +39 -0
- data/setup.rb +1585 -0
- data/test/event_test.rb +15 -0
- data/test/facebook_cache_test.rb +43 -0
- data/test/facebook_data_test.rb +50 -0
- data/test/facebooker_test.rb +766 -0
- data/test/fixtures/multipart_post_body_with_only_parameters.txt +33 -0
- data/test/fixtures/multipart_post_body_with_single_file.txt +38 -0
- data/test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt +38 -0
- data/test/http_multipart_post_test.rb +54 -0
- data/test/model_test.rb +79 -0
- data/test/rails_integration_test.rb +732 -0
- data/test/session_test.rb +396 -0
- data/test/test_helper.rb +54 -0
- data/test/user_test.rb +101 -0
- metadata +130 -0
@@ -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,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,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
|