yury-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 +16 -0
- data/Manifest.txt +77 -0
- data/README +46 -0
- data/README.txt +81 -0
- data/Rakefile +62 -0
- data/TODO.txt +10 -0
- data/facebooker.yml.tpl +41 -0
- data/init.rb +53 -0
- data/install.rb +7 -0
- data/lib/facebooker.rb +143 -0
- data/lib/facebooker/data.rb +40 -0
- data/lib/facebooker/feed.rb +78 -0
- data/lib/facebooker/model.rb +123 -0
- data/lib/facebooker/parser.rb +518 -0
- data/lib/facebooker/rails/controller.rb +241 -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 +24 -0
- data/lib/facebooker/rails/facebook_session_handling.rb +58 -0
- data/lib/facebooker/rails/facebook_url_rewriting.rb +39 -0
- data/lib/facebooker/rails/helpers.rb +615 -0
- data/lib/facebooker/rails/routing.rb +49 -0
- data/lib/facebooker/rails/test_helpers.rb +88 -0
- data/lib/facebooker/rails/utilities.rb +22 -0
- data/lib/facebooker/server_cache.rb +24 -0
- data/lib/facebooker/service.rb +31 -0
- data/lib/facebooker/session.rb +550 -0
- data/lib/facebooker/version.rb +9 -0
- data/lib/net/http_multipart_post.rb +123 -0
- data/lib/tasks/facebooker.rake +17 -0
- data/lib/tasks/tunnel.rake +43 -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 +855 -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 +91 -0
- data/test/rails_integration_test.rb +993 -0
- data/test/session_test.rb +559 -0
- data/test/test_helper.rb +60 -0
- data/test/user_test.rb +219 -0
- metadata +130 -0
|
@@ -0,0 +1,40 @@
|
|
|
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) {|response| response == '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) do |response|
|
|
34
|
+
response.map do |hash|
|
|
35
|
+
Cookie.from_hash(hash)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
def add_image(image,link=nil)
|
|
13
|
+
1.upto(4) do |num|
|
|
14
|
+
if send("image_#{num}").blank?
|
|
15
|
+
send("image_#{num}=",image)
|
|
16
|
+
send("image_#{num}_link=",link) unless link.nil?
|
|
17
|
+
return num
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
def image_params
|
|
25
|
+
image_hash = {}
|
|
26
|
+
1.upto(4) do |num|
|
|
27
|
+
image_attribute = "image_#{num}"
|
|
28
|
+
image_link_attribute = image_attribute + "_link"
|
|
29
|
+
self.__send__(image_attribute) ? image_hash[image_attribute] = self.__send__(image_attribute) : nil
|
|
30
|
+
self.__send__(image_link_attribute) ? image_hash[image_link_attribute] = self.__send__(image_link_attribute) : nil
|
|
31
|
+
end
|
|
32
|
+
image_hash
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Representation of a templatized action to be published into a user's news feed
|
|
38
|
+
class TemplatizedAction < ActionBase
|
|
39
|
+
attr_accessor :page_actor_id, :title_template, :title_data, :body_template, :body_data, :body_general, :target_ids
|
|
40
|
+
|
|
41
|
+
def to_params
|
|
42
|
+
raise "Must set title_template" if self.title_template.nil?
|
|
43
|
+
{ :page_actor_id => page_actor_id,
|
|
44
|
+
:title_template => title_template,
|
|
45
|
+
:title_data => convert_json(title_data),
|
|
46
|
+
:body_template => body_template,
|
|
47
|
+
:body_data => convert_json(body_data),
|
|
48
|
+
:body_general => body_general,
|
|
49
|
+
:target_ids => target_ids }.merge image_params
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def convert_json(hash_or_string)
|
|
53
|
+
(hash_or_string.is_a?(Hash) and hash_or_string.respond_to?(:to_json)) ? hash_or_string.to_json : hash_or_string
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Representation of a story to be published into a user's news feed.
|
|
59
|
+
class Story < ActionBase
|
|
60
|
+
attr_accessor :title, :body
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Converts Story to a Hash of its attributes for use as parameters to Facebook REST API calls
|
|
64
|
+
def to_params
|
|
65
|
+
raise "Must set title before converting" if self.title.nil?
|
|
66
|
+
{ :title => title, :body => body }.merge image_params
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
Action = Story.dup
|
|
71
|
+
def Action.name
|
|
72
|
+
"Action"
|
|
73
|
+
end
|
|
74
|
+
##
|
|
75
|
+
# Representation of an action to be published into a user's news feed. Alias for Story.
|
|
76
|
+
class Action; end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
set_attr_method = "#{key}="
|
|
113
|
+
if respond_to?(set_attr_method)
|
|
114
|
+
self.__send__(set_attr_method, value)
|
|
115
|
+
else
|
|
116
|
+
Facebooker::Logging.log_info("**Warning**, Attempt to set non-attribute: #{key}",hash)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
@populated = true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,518 @@
|
|
|
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 RegisterUsers
|
|
94
|
+
def self.process(data)
|
|
95
|
+
Facebooker.json_decode(data)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class GetSession < Parser#:nodoc:
|
|
100
|
+
def self.process(data)
|
|
101
|
+
hashinate(element('auth_getSession_response', data))
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class GetFriends < Parser#:nodoc:
|
|
106
|
+
def self.process(data)
|
|
107
|
+
array_of_text_values(element('friends_get_response', data), 'uid')
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class FriendListsGet < Parser#:nodoc:
|
|
112
|
+
def self.process(data)
|
|
113
|
+
array_of_hashes(element('friends_getLists_response', data), 'friendlist')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class UserInfo < Parser#:nodoc:
|
|
118
|
+
def self.process(data)
|
|
119
|
+
array_of_hashes(element('users_getInfo_response', data), 'user')
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class GetLoggedInUser < Parser#:nodoc:
|
|
124
|
+
def self.process(data)
|
|
125
|
+
Integer(element('users_getLoggedInUser_response', data).text_value)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class PagesIsAdmin < Parser#:nodoc:
|
|
130
|
+
def self.process(data)
|
|
131
|
+
element('pages_isAdmin_response', data).text_value == '1'
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class PagesGetInfo < Parser#:nodoc:
|
|
136
|
+
def self.process(data)
|
|
137
|
+
array_of_hashes(element('pages_getInfo_response', data), 'page')
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class PublishStoryToUser < Parser#:nodoc:
|
|
142
|
+
def self.process(data)
|
|
143
|
+
element('feed_publishStoryToUser_response', data).text_value
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class RegisterTemplateBundle < Parser#:nodoc:
|
|
148
|
+
def self.process(data)
|
|
149
|
+
element('feed_registerTemplateBundle_response', data).text_value.to_i
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class GetRegisteredTemplateBundles < Parser
|
|
154
|
+
def self.process(data)
|
|
155
|
+
array_of_hashes(element('feed_getRegisteredTemplateBundles_response',data), 'template_bundle')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class DeactivateTemplateBundleByID < Parser#:nodoc:
|
|
160
|
+
def self.process(data)
|
|
161
|
+
element('feed_deactivateTemplateBundleByID_response', data).text_value == '1'
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class PublishUserAction < Parser#:nodoc:
|
|
166
|
+
def self.process(data)
|
|
167
|
+
element('feed_publishUserAction_response', data).children[1].text_value == "1"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class PublishActionOfUser < Parser#:nodoc:
|
|
172
|
+
def self.process(data)
|
|
173
|
+
element('feed_publishActionOfUser_response', data).text_value
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class PublishTemplatizedAction < Parser#:nodoc:
|
|
178
|
+
def self.process(data)
|
|
179
|
+
element('feed_publishTemplatizedAction_response', data).children[1].text_value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
class SetAppProperties < Parser#:nodoc:
|
|
184
|
+
def self.process(data)
|
|
185
|
+
element('admin_setAppProperties_response', data).text_value
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
class GetAppProperties < Parser#:nodoc:
|
|
190
|
+
def self.process(data)
|
|
191
|
+
element('admin_getAppProperties_response', data).text_value
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
class GetAllocation < Parser#:nodoc:
|
|
196
|
+
def self.process(data)
|
|
197
|
+
element('admin_getAllocation_response', data).text_value
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class BatchRun < Parser #:nodoc:
|
|
202
|
+
class << self
|
|
203
|
+
def current_batch=(current_batch)
|
|
204
|
+
Thread.current[:facebooker_current_batch]=current_batch
|
|
205
|
+
end
|
|
206
|
+
def current_batch
|
|
207
|
+
Thread.current[:facebooker_current_batch]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
def self.process(data)
|
|
211
|
+
array_of_text_values(element('batch_run_response',data),"batch_run_response_elt").each_with_index do |response,i|
|
|
212
|
+
batch_request=current_batch[i]
|
|
213
|
+
body=Struct.new(:body).new
|
|
214
|
+
body.body=CGI.unescapeHTML(response)
|
|
215
|
+
begin
|
|
216
|
+
batch_request.result=Parser.parse(batch_request.method,body)
|
|
217
|
+
rescue Exception=>ex
|
|
218
|
+
batch_request.exception_raised=ex
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class GetAppUsers < Parser#:nodoc:
|
|
225
|
+
def self.process(data)
|
|
226
|
+
array_of_text_values(element('friends_getAppUsers_response', data), 'uid')
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class NotificationsGet < Parser#:nodoc:
|
|
231
|
+
def self.process(data)
|
|
232
|
+
hashinate(element('notifications_get_response', data))
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class NotificationsSend < Parser#:nodoc:
|
|
237
|
+
def self.process(data)
|
|
238
|
+
element('notifications_send_response', data).text_value
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class NotificationsSendEmail < Parser#:nodoc:
|
|
243
|
+
def self.process(data)
|
|
244
|
+
element('notifications_sendEmail_response', data).text_value
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
class GetTags < Parser#nodoc:
|
|
249
|
+
def self.process(data)
|
|
250
|
+
array_of_hashes(element('photos_getTags_response', data), 'photo_tag')
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class AddTags < Parser#nodoc:
|
|
255
|
+
def self.process(data)
|
|
256
|
+
element('photos_addTag_response', data)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
class GetPhotos < Parser#nodoc:
|
|
261
|
+
def self.process(data)
|
|
262
|
+
array_of_hashes(element('photos_get_response', data), 'photo')
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class GetAlbums < Parser#nodoc:
|
|
267
|
+
def self.process(data)
|
|
268
|
+
array_of_hashes(element('photos_getAlbums_response', data), 'album')
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class CreateAlbum < Parser#:nodoc:
|
|
273
|
+
def self.process(data)
|
|
274
|
+
hashinate(element('photos_createAlbum_response', data))
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
class UploadPhoto < Parser#:nodoc:
|
|
279
|
+
def self.process(data)
|
|
280
|
+
hashinate(element('photos_upload_response', data))
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class SendRequest < Parser#:nodoc:
|
|
285
|
+
def self.process(data)
|
|
286
|
+
element('notifications_sendRequest_response', data).text_value
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
class ProfileFBML < Parser#:nodoc:
|
|
291
|
+
def self.process(data)
|
|
292
|
+
element('profile_getFBML_response', data).text_value
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class ProfileFBMLSet < Parser#:nodoc:
|
|
297
|
+
def self.process(data)
|
|
298
|
+
element('profile_setFBML_response', data).text_value
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
class ProfileInfo < Parser#:nodoc:
|
|
303
|
+
def self.process(data)
|
|
304
|
+
hashinate(element('profile_getInfo_response info_fields', data))
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
class ProfileInfoSet < Parser#:nodoc:
|
|
309
|
+
def self.process(data)
|
|
310
|
+
element('profile_setInfo_response', data).text_value
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
class FqlQuery < Parser#nodoc
|
|
315
|
+
def self.process(data)
|
|
316
|
+
root = element('fql_query_response', data)
|
|
317
|
+
first_child = root.children.reject{|c| c.kind_of?(REXML::Text)}.first
|
|
318
|
+
first_child.nil? ? [] : [first_child.name, array_of_hashes(root, first_child.name)]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
class SetRefHandle < Parser#:nodoc:
|
|
323
|
+
def self.process(data)
|
|
324
|
+
element('fbml_setRefHandle_response', data).text_value
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
class RefreshRefURL < Parser#:nodoc:
|
|
329
|
+
def self.process(data)
|
|
330
|
+
element('fbml_refreshRefUrl_response', data).text_value
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
class RefreshImgSrc < Parser#:nodoc:
|
|
335
|
+
def self.process(data)
|
|
336
|
+
element('fbml_refreshImgSrc_response', data).text_value
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
class SetCookie < Parser#:nodoc:
|
|
341
|
+
def self.process(data)
|
|
342
|
+
element('data_setCookie_response', data).text_value
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
class GetCookies < Parser#:nodoc:
|
|
347
|
+
def self.process(data)
|
|
348
|
+
array_of_hashes(element('data_getCookie_response', data), 'cookies')
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
class EventsGet < Parser#:nodoc:
|
|
353
|
+
def self.process(data)
|
|
354
|
+
array_of_hashes(element('events_get_response', data), 'event')
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
class GroupGetMembers < Parser#:nodoc:
|
|
359
|
+
def self.process(data)
|
|
360
|
+
root = element('groups_getMembers_response', data)
|
|
361
|
+
result = ['members', 'admins', 'officers', 'not_replied'].map do |position|
|
|
362
|
+
array_of(root, position) {|element| element}.map do |element|
|
|
363
|
+
array_of_text_values(element, 'uid').map do |uid|
|
|
364
|
+
{:position => position}.merge(:uid => uid)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end.flatten
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
class EventMembersGet < Parser#:nodoc:
|
|
372
|
+
def self.process(data)
|
|
373
|
+
root = element('events_getMembers_response', data)
|
|
374
|
+
result = ['attending', 'declined', 'unsure', 'not_replied'].map do |rsvp_status|
|
|
375
|
+
array_of(root, rsvp_status) {|element| element}.map do |element|
|
|
376
|
+
array_of_text_values(element, 'uid').map do |uid|
|
|
377
|
+
{:rsvp_status => rsvp_status}.merge(:uid => uid)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end.flatten
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
class GroupsGet < Parser#:nodoc:
|
|
385
|
+
def self.process(data)
|
|
386
|
+
array_of_hashes(element('groups_get_response', data), 'group')
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
class AreFriends < Parser#:nodoc:
|
|
391
|
+
def self.process(data)
|
|
392
|
+
array_of_hashes(element('friends_areFriends_response', data), 'friend_info').inject({}) do |memo, hash|
|
|
393
|
+
memo[[Integer(hash['uid1']), Integer(hash['uid2'])].sort] = are_friends?(hash['are_friends'])
|
|
394
|
+
memo
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
private
|
|
399
|
+
def self.are_friends?(raw_value)
|
|
400
|
+
if raw_value == '1'
|
|
401
|
+
true
|
|
402
|
+
elsif raw_value == '0'
|
|
403
|
+
false
|
|
404
|
+
else
|
|
405
|
+
nil
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
class SetStatus < Parser
|
|
411
|
+
def self.process(data)
|
|
412
|
+
element('users_setStatus_response',data).text_value == '1'
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
class Errors < Parser#:nodoc:
|
|
417
|
+
EXCEPTIONS = {
|
|
418
|
+
1 => Facebooker::Session::UnknownError,
|
|
419
|
+
2 => Facebooker::Session::ServiceUnavailable,
|
|
420
|
+
4 => Facebooker::Session::MaxRequestsDepleted,
|
|
421
|
+
5 => Facebooker::Session::HostNotAllowed,
|
|
422
|
+
100 => Facebooker::Session::MissingOrInvalidParameter,
|
|
423
|
+
101 => Facebooker::Session::InvalidAPIKey,
|
|
424
|
+
102 => Facebooker::Session::SessionExpired,
|
|
425
|
+
103 => Facebooker::Session::CallOutOfOrder,
|
|
426
|
+
104 => Facebooker::Session::IncorrectSignature,
|
|
427
|
+
120 => Facebooker::Session::InvalidAlbumId,
|
|
428
|
+
250 => Facebooker::Session::ExtendedPermissionRequired,
|
|
429
|
+
321 => Facebooker::Session::AlbumIsFull,
|
|
430
|
+
324 => Facebooker::Session::MissingOrInvalidImageFile,
|
|
431
|
+
325 => Facebooker::Session::TooManyUnapprovedPhotosPending,
|
|
432
|
+
330 => Facebooker::Session::TemplateDataMissingRequiredTokens,
|
|
433
|
+
340 => Facebooker::Session::TooManyUserCalls,
|
|
434
|
+
341 => Facebooker::Session::TooManyUserActionCalls,
|
|
435
|
+
342 => Facebooker::Session::InvalidFeedTitleLink,
|
|
436
|
+
343 => Facebooker::Session::InvalidFeedTitleLength,
|
|
437
|
+
344 => Facebooker::Session::InvalidFeedTitleName,
|
|
438
|
+
345 => Facebooker::Session::BlankFeedTitle,
|
|
439
|
+
346 => Facebooker::Session::FeedBodyLengthTooLong,
|
|
440
|
+
347 => Facebooker::Session::InvalidFeedPhotoSource,
|
|
441
|
+
348 => Facebooker::Session::InvalidFeedPhotoLink,
|
|
442
|
+
330 => Facebooker::Session::FeedMarkupInvalid,
|
|
443
|
+
360 => Facebooker::Session::FeedTitleDataInvalid,
|
|
444
|
+
361 => Facebooker::Session::FeedTitleTemplateInvalid,
|
|
445
|
+
362 => Facebooker::Session::FeedBodyDataInvalid,
|
|
446
|
+
363 => Facebooker::Session::FeedBodyTemplateInvalid,
|
|
447
|
+
364 => Facebooker::Session::FeedPhotosNotRetrieved,
|
|
448
|
+
366 => Facebooker::Session::FeedTargetIdsInvalid,
|
|
449
|
+
601 => Facebooker::Session::FQLParseError,
|
|
450
|
+
602 => Facebooker::Session::FQLFieldDoesNotExist,
|
|
451
|
+
603 => Facebooker::Session::FQLTableDoesNotExist,
|
|
452
|
+
604 => Facebooker::Session::FQLStatementNotIndexable,
|
|
453
|
+
605 => Facebooker::Session::FQLFunctionDoesNotExist,
|
|
454
|
+
606 => Facebooker::Session::FQLWrongNumberArgumentsPassedToFunction,
|
|
455
|
+
807 => Facebooker::Session::TemplateBundleInvalid
|
|
456
|
+
}
|
|
457
|
+
def self.process(data)
|
|
458
|
+
response_element = element('error_response', data) rescue nil
|
|
459
|
+
if response_element
|
|
460
|
+
hash = hashinate(response_element)
|
|
461
|
+
exception = EXCEPTIONS[Integer(hash['error_code'])] || StandardError
|
|
462
|
+
raise exception.new(hash['error_msg'])
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
class Parser
|
|
468
|
+
PARSERS = {
|
|
469
|
+
'facebook.auth.createToken' => CreateToken,
|
|
470
|
+
'facebook.auth.getSession' => GetSession,
|
|
471
|
+
'facebook.connect.registerUsers' => RegisterUsers,
|
|
472
|
+
'facebook.users.getInfo' => UserInfo,
|
|
473
|
+
'facebook.users.setStatus' => SetStatus,
|
|
474
|
+
'facebook.users.getLoggedInUser' => GetLoggedInUser,
|
|
475
|
+
'facebook.pages.isAdmin' => PagesIsAdmin,
|
|
476
|
+
'facebook.pages.getInfo' => PagesGetInfo,
|
|
477
|
+
'facebook.friends.get' => GetFriends,
|
|
478
|
+
'facebook.friends.getLists' => FriendListsGet,
|
|
479
|
+
'facebook.friends.areFriends' => AreFriends,
|
|
480
|
+
'facebook.friends.getAppUsers' => GetAppUsers,
|
|
481
|
+
'facebook.feed.publishStoryToUser' => PublishStoryToUser,
|
|
482
|
+
'facebook.feed.publishActionOfUser' => PublishActionOfUser,
|
|
483
|
+
'facebook.feed.publishTemplatizedAction' => PublishTemplatizedAction,
|
|
484
|
+
'facebook.feed.registerTemplateBundle' => RegisterTemplateBundle,
|
|
485
|
+
'facebook.feed.deactivateTemplateBundleByID' => DeactivateTemplateBundleByID,
|
|
486
|
+
'facebook.feed.getRegisteredTemplateBundles' => GetRegisteredTemplateBundles,
|
|
487
|
+
'facebook.feed.publishUserAction' => PublishUserAction,
|
|
488
|
+
'facebook.notifications.get' => NotificationsGet,
|
|
489
|
+
'facebook.notifications.send' => NotificationsSend,
|
|
490
|
+
'facebook.notifications.sendRequest' => SendRequest,
|
|
491
|
+
'facebook.profile.getFBML' => ProfileFBML,
|
|
492
|
+
'facebook.profile.setFBML' => ProfileFBMLSet,
|
|
493
|
+
'facebook.profile.getInfo' => ProfileInfo,
|
|
494
|
+
'facebook.profile.setInfo' => ProfileInfoSet,
|
|
495
|
+
'facebook.fbml.setRefHandle' => SetRefHandle,
|
|
496
|
+
'facebook.fbml.refreshRefUrl' => RefreshRefURL,
|
|
497
|
+
'facebook.fbml.refreshImgSrc' => RefreshImgSrc,
|
|
498
|
+
'facebook.data.setCookie' => SetCookie,
|
|
499
|
+
'facebook.data.getCookies' => GetCookies,
|
|
500
|
+
'facebook.admin.setAppProperties' => SetAppProperties,
|
|
501
|
+
'facebook.admin.getAppProperties' => GetAppProperties,
|
|
502
|
+
'facebook.admin.getAllocation' => GetAllocation,
|
|
503
|
+
'facebook.batch.run' => BatchRun,
|
|
504
|
+
'facebook.fql.query' => FqlQuery,
|
|
505
|
+
'facebook.photos.get' => GetPhotos,
|
|
506
|
+
'facebook.photos.getAlbums' => GetAlbums,
|
|
507
|
+
'facebook.photos.createAlbum' => CreateAlbum,
|
|
508
|
+
'facebook.photos.getTags' => GetTags,
|
|
509
|
+
'facebook.photos.addTag' => AddTags,
|
|
510
|
+
'facebook.photos.upload' => UploadPhoto,
|
|
511
|
+
'facebook.events.get' => EventsGet,
|
|
512
|
+
'facebook.groups.get' => GroupsGet,
|
|
513
|
+
'facebook.events.getMembers' => EventMembersGet,
|
|
514
|
+
'facebook.groups.getMembers' => GroupGetMembers,
|
|
515
|
+
'facebook.notifications.sendEmail' => NotificationsSendEmail
|
|
516
|
+
}
|
|
517
|
+
end
|
|
518
|
+
end
|