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