koala 1.0.0 → 1.1.0
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/.autotest +12 -0
- data/.gitignore +3 -1
- data/.travis.yml +8 -0
- data/CHANGELOG +26 -2
- data/Gemfile +4 -0
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +8 -8
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +103 -102
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_services/net_http_service.rb +92 -0
- data/lib/koala/http_services/typhoeus_service.rb +37 -0
- data/lib/koala/http_services.rb +13 -113
- data/lib/koala/oauth.rb +181 -0
- data/lib/koala/realtime_updates.rb +5 -14
- data/lib/koala/rest_api.rb +13 -8
- data/lib/koala/uploadable_io.rb +137 -77
- data/lib/koala.rb +36 -196
- data/readme.md +51 -32
- data/spec/cases/api_base_spec.rb +4 -4
- data/spec/cases/graph_api_batch_spec.rb +609 -0
- data/spec/cases/http_services/http_service_spec.rb +87 -12
- data/spec/cases/http_services/net_http_service_spec.rb +259 -77
- data/spec/cases/http_services/typhoeus_service_spec.rb +29 -21
- data/spec/cases/koala_spec.rb +55 -0
- data/spec/cases/oauth_spec.rb +1 -1
- data/spec/cases/realtime_updates_spec.rb +3 -3
- data/spec/cases/test_users_spec.rb +1 -1
- data/spec/cases/uploadable_io_spec.rb +56 -14
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/mock_facebook_responses.yml +100 -5
- data/spec/spec_helper.rb +2 -1
- data/spec/support/graph_api_shared_examples.rb +106 -35
- data/spec/support/json_testing_fix.rb +18 -0
- data/spec/support/mock_http_service.rb +57 -56
- data/spec/support/rest_api_shared_examples.rb +131 -7
- data/spec/support/setup_mocks_or_live.rb +3 -4
- metadata +34 -47
data/.autotest
ADDED
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
v1.1
|
|
2
|
+
New methods:
|
|
3
|
+
-- Added Batch API support (thanks, seejohnrun and spiegela!)
|
|
4
|
+
-- includes file uploads, error handling, and FQL
|
|
5
|
+
-- Added GraphAPI#put_video
|
|
6
|
+
-- Added GraphAPI#get_comments_for_urls (thanks, amrnt!)
|
|
7
|
+
-- Added RestAPI#fql_multiquery, which simplifies the results (thanks, amrnt!)
|
|
8
|
+
-- HTTP services support global proxy and timeout settings (thanks, itchy!)
|
|
9
|
+
-- Net::HTTP supports global ca_path, ca_file, and verify_mode settings (thanks, spiegela!)
|
|
10
|
+
Updated methods:
|
|
11
|
+
-- RealtimeUpdates now uses a GraphAPI object instead of its own API
|
|
12
|
+
-- RestAPI#rest_call now has an optional last argument for method, for calls requiring POST, DELETE, etc. (thanks, sshilo!)
|
|
13
|
+
-- Filename can now be specified when uploading (e.g. for Ads API) (thanks, sshilo!)
|
|
14
|
+
-- get_objects([]) returns [] instead of a Facebook error in non-batch mode (thanks, aselder!)
|
|
15
|
+
Internal improvements:
|
|
16
|
+
-- Koala is now more compatible with other Rubies (JRuby, Rubinius, etc.)
|
|
17
|
+
-- HTTP services are more modular and can be changed on the fly (thanks, chadk!)
|
|
18
|
+
-- Includes support for uploading StringIOs and other non-files via Net::HTTP even when using TyphoeusService
|
|
19
|
+
-- Koala now uses multi_json to improve compatibility with Rubinius and other Ruby versions
|
|
20
|
+
-- Koala now uses the modern Typhoeus API (thanks, aselder!)
|
|
21
|
+
-- Koala now uses the current modern Net::HTTP interface (thanks, romanbsd!)
|
|
22
|
+
-- Fixed bugs and typos (thanks, waynn, mokevnin, and tikh!)
|
|
23
|
+
|
|
1
24
|
v1.0
|
|
2
25
|
New methods:
|
|
3
26
|
-- Photo and file upload now supported through #put_picture
|
|
@@ -6,13 +29,13 @@ New methods:
|
|
|
6
29
|
-- Added put_connection and delete_connection convenience methods
|
|
7
30
|
Updated methods:
|
|
8
31
|
-- Search can now search places, checkins, etc. (thanks, rickyc!)
|
|
9
|
-
-- You can now pass :beta => true in the http options to use Facebook's beta tier
|
|
32
|
+
-- You can now pass :beta => true in the http options to use Facebook's beta tier
|
|
10
33
|
-- TestUser#befriend now requires user info hashes (id and access token) due to Facebook API changes (thanks, pulsd and kbighorse!)
|
|
11
34
|
-- All methods now accept an http_options hash as their optional last parameter (thanks, spiegela!)
|
|
12
35
|
-- url_for_oauth_code can now take a :display option (thanks, netbe!)
|
|
13
36
|
-- Net::HTTP can now accept :timeout and :proxy options (thanks, gilles!)
|
|
14
37
|
-- Test users now supports using test accounts across multiple apps
|
|
15
|
-
Internal improvements:
|
|
38
|
+
Internal improvements:
|
|
16
39
|
-- For public requests, Koala now uses http by default (instead of https) to improve speed
|
|
17
40
|
-- This can be overridden through Koala.always_use_ssl= or by passing :use_ssl => true in the options hash for an api call
|
|
18
41
|
-- Read-only REST API requests now go through the faster api-read server
|
|
@@ -22,6 +45,7 @@ Internal improvements:
|
|
|
22
45
|
-- Updated parse_signed_request to match Facebook's current implementation (thanks, imajes!)
|
|
23
46
|
-- APIError is now < StandardError, not Exception
|
|
24
47
|
-- Added KoalaError for non-API errors
|
|
48
|
+
-- Net::HTTP's SSL verification is no longer disabled by default
|
|
25
49
|
Test improvements:
|
|
26
50
|
-- Incorporated joshk's awesome rewrite of the entire Koala test suite (thanks, joshk!)
|
|
27
51
|
-- Expanded HTTP service tests (added Typhoeus test suite and additional Net::HTTP test cases)
|
data/Gemfile
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/koala.gemspec
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = %q{koala}
|
|
5
|
-
s.version = "1.
|
|
6
|
-
s.date = %q{2011-
|
|
5
|
+
s.version = "1.1.0"
|
|
6
|
+
s.date = %q{2011-07-18}
|
|
7
7
|
|
|
8
8
|
s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the REST API, realtime updates, and OAuth authentication.}
|
|
9
9
|
s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph and REST APIs, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
|
|
@@ -28,22 +28,22 @@ Gem::Specification.new do |s|
|
|
|
28
28
|
s.specification_version = 3
|
|
29
29
|
|
|
30
30
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
31
|
-
s.add_runtime_dependency(%q<
|
|
31
|
+
s.add_runtime_dependency(%q<multi_json>, ["~> 1.0"])
|
|
32
32
|
s.add_runtime_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
33
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.5
|
|
33
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.5"])
|
|
34
34
|
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
|
|
35
35
|
s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
36
36
|
else
|
|
37
|
-
s.add_dependency(%q<
|
|
37
|
+
s.add_dependency(%q<multi_json>, ["~> 1.0"])
|
|
38
38
|
s.add_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
39
|
-
s.add_dependency(%q<rspec>, ["~> 2.5
|
|
39
|
+
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
|
40
40
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
|
41
41
|
s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
42
42
|
end
|
|
43
43
|
else
|
|
44
|
-
s.add_dependency(%q<
|
|
44
|
+
s.add_dependency(%q<multi_json>, ["~> 1.0"])
|
|
45
45
|
s.add_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
46
|
-
s.add_dependency(%q<rspec>, ["~> 2.5
|
|
46
|
+
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
|
47
47
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
|
48
48
|
s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
49
49
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
module Facebook
|
|
3
|
+
class BatchOperation
|
|
4
|
+
attr_reader :access_token, :http_options, :post_processing, :files, :batch_api, :identifier
|
|
5
|
+
|
|
6
|
+
@identifier = 0
|
|
7
|
+
|
|
8
|
+
def self.next_identifier
|
|
9
|
+
@identifier += 1
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@identifier = self.class.next_identifier
|
|
14
|
+
@args = (options[:args] || {}).dup # because we modify it below
|
|
15
|
+
@access_token = options[:access_token]
|
|
16
|
+
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
|
17
|
+
@batch_args = @http_options.delete(:batch_args) || {}
|
|
18
|
+
@url = options[:url]
|
|
19
|
+
@method = options[:method].to_sym
|
|
20
|
+
@post_processing = options[:post_processing]
|
|
21
|
+
|
|
22
|
+
process_binary_args
|
|
23
|
+
|
|
24
|
+
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_batch_params(main_access_token)
|
|
28
|
+
# set up the arguments
|
|
29
|
+
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
|
30
|
+
|
|
31
|
+
response = {
|
|
32
|
+
:method => @method.to_s,
|
|
33
|
+
:relative_url => @url,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# handle batch-level arguments, such as name, depends_on, and attached_files
|
|
37
|
+
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
|
38
|
+
response.merge!(@batch_args) if @batch_args
|
|
39
|
+
|
|
40
|
+
# for get and delete, we append args to the URL string
|
|
41
|
+
# otherwise, they go in the body
|
|
42
|
+
if args_string.length > 0
|
|
43
|
+
if args_in_url?
|
|
44
|
+
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
|
45
|
+
else
|
|
46
|
+
response[:body] = args_string if args_string.length > 0
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
response
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
def process_binary_args
|
|
56
|
+
# collect binary files
|
|
57
|
+
@args.each_pair do |key, value|
|
|
58
|
+
if UploadableIO.binary_content?(value)
|
|
59
|
+
@files ||= {}
|
|
60
|
+
# we use a class-level counter to ensure unique file identifiers across multiple batch operations
|
|
61
|
+
# (this is thread safe, since we just care about uniqueness)
|
|
62
|
+
# so remove the file from the original hash and add it to the file store
|
|
63
|
+
id = "op#{identifier}_file#{@files.keys.length}"
|
|
64
|
+
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def args_in_url?
|
|
70
|
+
@method == :get || @method == :delete
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/koala/graph_api.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Koala
|
|
|
28
28
|
# If you are using the JavaScript SDK, you can use the
|
|
29
29
|
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
|
30
30
|
# for the active user from the cookie saved by the SDK.
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# Objects
|
|
33
33
|
|
|
34
34
|
def get_object(id, args = {}, options = {})
|
|
@@ -37,10 +37,10 @@ module Koala
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def get_objects(ids, args = {}, options = {})
|
|
40
|
-
# Fetchs all of the given
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
graph_call("", args.merge("ids" => ids.join(",")), "get", options)
|
|
40
|
+
# Fetchs all of the given objects from the graph.
|
|
41
|
+
# If any of the IDs are invalid, they'll raise an exception.
|
|
42
|
+
return [] if ids.empty?
|
|
43
|
+
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def put_object(parent_object, connection_name, args = {}, options = {})
|
|
@@ -71,10 +71,19 @@ module Koala
|
|
|
71
71
|
|
|
72
72
|
def get_connections(id, connection_name, args = {}, options = {})
|
|
73
73
|
# Fetchs the connections for given object.
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
|
|
75
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
76
|
+
end
|
|
76
77
|
end
|
|
77
78
|
|
|
79
|
+
def get_comments_for_urls(urls = [], args = {}, options = {})
|
|
80
|
+
# Fetchs the comments for given URLs (array or comma-separated string)
|
|
81
|
+
# see https://developers.facebook.com/blog/post/490
|
|
82
|
+
return [] if urls.empty?
|
|
83
|
+
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
|
84
|
+
get_object("comments", args, options)
|
|
85
|
+
end
|
|
86
|
+
|
|
78
87
|
def put_connections(id, connection_name, args = {}, options = {})
|
|
79
88
|
# Posts a certain connection
|
|
80
89
|
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
|
@@ -87,44 +96,41 @@ module Koala
|
|
|
87
96
|
graph_call("#{id}/#{connection_name}", args, "delete", options)
|
|
88
97
|
end
|
|
89
98
|
|
|
90
|
-
#
|
|
91
|
-
# to delete
|
|
92
|
-
# note: you'll need the user_photos
|
|
99
|
+
# Media (photos and videos)
|
|
100
|
+
# to delete photos or videos, use delete_object(object_id)
|
|
101
|
+
# note: you'll need the user_photos or user_videos permissions to actually access media after upload
|
|
93
102
|
|
|
94
103
|
def get_picture(object, args = {}, options = {})
|
|
95
104
|
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
|
106
|
+
result["Location"]
|
|
107
|
+
end
|
|
98
108
|
end
|
|
99
109
|
|
|
110
|
+
# Can be called in multiple ways:
|
|
111
|
+
#
|
|
112
|
+
# put_picture(file, [content_type], ...)
|
|
113
|
+
# put_picture(path_to_file, [content_type], ...)
|
|
114
|
+
#
|
|
115
|
+
# You can pass in uploaded files directly from Rails or Sinatra.
|
|
116
|
+
# (See lib/koala/uploadable_io.rb for supported frameworks)
|
|
117
|
+
#
|
|
118
|
+
# Optional parameters can be added to the end of the argument list:
|
|
119
|
+
# - args: a hash of request parameters (default: {})
|
|
120
|
+
# - target_id: ID of the target where to post the picture (default: "me")
|
|
121
|
+
# - options: a hash of http options passed to the HTTPService module
|
|
122
|
+
#
|
|
123
|
+
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
|
124
|
+
# put_picture(params[:file], {:message => "Message"})
|
|
125
|
+
|
|
100
126
|
def put_picture(*picture_args)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# put_picture(file, [content_type], ...)
|
|
104
|
-
# put_picture(path_to_file, [content_type], ...)
|
|
105
|
-
#
|
|
106
|
-
# You can pass in uploaded files directly from Rails or Sinatra.
|
|
107
|
-
# (See lib/koala/uploadable_io.rb for supported frameworks)
|
|
108
|
-
#
|
|
109
|
-
# Optional parameters can be added to the end of the argument list:
|
|
110
|
-
# - args: a hash of request parameters (default: {})
|
|
111
|
-
# - target_id: ID of the target where to post the picture (default: "me")
|
|
112
|
-
# - options: a hash of http options passed to the HTTPService module
|
|
113
|
-
#
|
|
114
|
-
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
|
115
|
-
# put_picture(params[:file], {:message => "Message"})
|
|
116
|
-
|
|
117
|
-
raise KoalaError.new("Wrong number of arguments for put_picture") unless picture_args.size.between?(1, 5)
|
|
118
|
-
|
|
119
|
-
args_offset = picture_args[1].kind_of?(Hash) || picture_args.size == 1 ? 0 : 1
|
|
120
|
-
|
|
121
|
-
args = picture_args[1 + args_offset] || {}
|
|
122
|
-
target_id = picture_args[2 + args_offset] || "me"
|
|
123
|
-
options = picture_args[3 + args_offset] || {}
|
|
124
|
-
|
|
125
|
-
args["source"] = Koala::UploadableIO.new(*picture_args.slice(0, 1 + args_offset))
|
|
127
|
+
put_object(*parse_media_args(picture_args, "photos"))
|
|
128
|
+
end
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
def put_video(*video_args)
|
|
131
|
+
args = parse_media_args(video_args, "videos")
|
|
132
|
+
args.last[:video] = true
|
|
133
|
+
put_object(*args)
|
|
128
134
|
end
|
|
129
135
|
|
|
130
136
|
# Wall posts
|
|
@@ -172,86 +178,81 @@ module Koala
|
|
|
172
178
|
|
|
173
179
|
def search(search_terms, args = {}, options = {})
|
|
174
180
|
args.merge!({:q => search_terms}) unless search_terms.nil?
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# API access
|
|
180
|
-
|
|
181
|
-
def graph_call(*args)
|
|
182
|
-
# Direct access to the Facebook API
|
|
183
|
-
# see any of the above methods for example invocations
|
|
184
|
-
response = api(*args) do |response|
|
|
185
|
-
# check for Graph API-specific errors
|
|
186
|
-
if response.is_a?(Hash) && error_details = response["error"]
|
|
187
|
-
raise APIError.new(error_details)
|
|
188
|
-
end
|
|
181
|
+
graph_call("search", args, "get", options) do |result|
|
|
182
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
189
183
|
end
|
|
184
|
+
end
|
|
190
185
|
|
|
191
|
-
response
|
|
192
|
-
end
|
|
193
186
|
|
|
194
187
|
# GraphCollection support
|
|
195
|
-
|
|
196
188
|
def get_page(params)
|
|
197
189
|
# Pages through a set of results stored in a GraphCollection
|
|
198
190
|
# Used for connections and search results
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
graph_call(*params) do |result|
|
|
192
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
193
|
+
end
|
|
201
194
|
end
|
|
202
195
|
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class GraphCollection < Array
|
|
207
|
-
#This class is a light wrapper for collections returned
|
|
208
|
-
#from the Graph API.
|
|
209
|
-
#
|
|
210
|
-
#It extends Array to allow direct access to the data colleciton
|
|
211
|
-
#which should allow it to drop in seamlessly.
|
|
212
|
-
#
|
|
213
|
-
#It also allows access to paging information and the
|
|
214
|
-
#ability to get the next/previous page in the collection
|
|
215
|
-
#by calling next_page or previous_page.
|
|
216
|
-
attr_reader :paging
|
|
217
|
-
attr_reader :api
|
|
218
196
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# def next_page
|
|
229
|
-
# def previous_page
|
|
230
|
-
define_method "#{this.to_sym}_page" do
|
|
231
|
-
base, args = send("#{this}_page_params")
|
|
232
|
-
base ? @api.get_page([base, args]) : nil
|
|
197
|
+
# Batch API
|
|
198
|
+
def batch(http_options = {}, &block)
|
|
199
|
+
batch_client = GraphBatchAPI.new(access_token)
|
|
200
|
+
if block
|
|
201
|
+
yield batch_client
|
|
202
|
+
batch_client.execute(http_options)
|
|
203
|
+
else
|
|
204
|
+
batch_client
|
|
233
205
|
end
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def self.included(base)
|
|
209
|
+
base.class_eval do
|
|
210
|
+
def self.batch
|
|
211
|
+
raise NoMethodError, "The BatchAPI signature has changed (the original implementation was not thread-safe). Please see https://github.com/arsduo/koala/wiki/Batch-requests. (This message will be removed in the final 1.1 release.)"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Direct access to the Facebook API
|
|
217
|
+
# see any of the above methods for example invocations
|
|
218
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
|
219
|
+
result = api(path, args, verb, options) do |response|
|
|
220
|
+
error = check_response(response)
|
|
221
|
+
raise error if error
|
|
240
222
|
end
|
|
223
|
+
|
|
224
|
+
# now process as appropriate (get picture header, make GraphCollection, etc.)
|
|
225
|
+
post_processing ? post_processing.call(result) : result
|
|
241
226
|
end
|
|
242
227
|
|
|
243
|
-
def
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
params.each_pair do |key,value|
|
|
250
|
-
new_params[key] = value.join ","
|
|
228
|
+
def check_response(response)
|
|
229
|
+
# check for Graph API-specific errors
|
|
230
|
+
# this returns an error, which is immediately raised (non-batch)
|
|
231
|
+
# or added to the list of batch results (batch)
|
|
232
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
|
233
|
+
APIError.new(error_details)
|
|
251
234
|
end
|
|
252
|
-
[base,new_params]
|
|
253
235
|
end
|
|
254
236
|
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def parse_media_args(media_args, method)
|
|
240
|
+
# photo and video uploads can accept different types of arguments (see above)
|
|
241
|
+
# so here, we parse the arguments into a form directly usable in put_object
|
|
242
|
+
raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
|
|
243
|
+
|
|
244
|
+
args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
|
|
245
|
+
|
|
246
|
+
args = media_args[1 + args_offset] || {}
|
|
247
|
+
target_id = media_args[2 + args_offset] || "me"
|
|
248
|
+
options = media_args[3 + args_offset] || {}
|
|
249
|
+
|
|
250
|
+
args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
|
|
251
|
+
|
|
252
|
+
options[:http_service] = Koala.base_http_service if args["source"].requires_base_http_service
|
|
253
|
+
|
|
254
|
+
[target_id, method, args, options]
|
|
255
|
+
end
|
|
255
256
|
end
|
|
256
257
|
end
|
|
257
|
-
end
|
|
258
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
module Facebook
|
|
3
|
+
module GraphBatchAPIMethods
|
|
4
|
+
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.class_eval do
|
|
7
|
+
alias_method :graph_call_outside_batch, :graph_call
|
|
8
|
+
alias_method :graph_call, :graph_call_in_batch
|
|
9
|
+
|
|
10
|
+
alias_method :check_graph_api_response, :check_response
|
|
11
|
+
alias_method :check_response, :check_graph_batch_api_response
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def batch_calls
|
|
16
|
+
@batch_calls ||= []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
|
|
20
|
+
# for batch APIs, we queue up the call details (incl. post-processing)
|
|
21
|
+
batch_calls << BatchOperation.new(
|
|
22
|
+
:url => path,
|
|
23
|
+
:args => args,
|
|
24
|
+
:method => verb,
|
|
25
|
+
:access_token => options['access_token'] || access_token,
|
|
26
|
+
:http_options => options,
|
|
27
|
+
:post_processing => post_processing
|
|
28
|
+
)
|
|
29
|
+
nil # batch operations return nothing immediately
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def check_graph_batch_api_response(response)
|
|
33
|
+
if response.is_a?(Hash) && response["error"] && !response["error"].is_a?(Hash)
|
|
34
|
+
APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"])
|
|
35
|
+
else
|
|
36
|
+
check_graph_api_response(response)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def execute(http_options = {})
|
|
41
|
+
return [] unless batch_calls.length > 0
|
|
42
|
+
# Turn the call args collected into what facebook expects
|
|
43
|
+
args = {}
|
|
44
|
+
args["batch"] = MultiJson.encode(batch_calls.map { |batch_op|
|
|
45
|
+
args.merge!(batch_op.files) if batch_op.files
|
|
46
|
+
batch_op.to_batch_params(access_token)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
graph_call_outside_batch('/', args, 'post', http_options) do |response|
|
|
50
|
+
# map the results with post-processing included
|
|
51
|
+
index = 0 # keep compat with ruby 1.8 - no with_index for map
|
|
52
|
+
response.map do |call_result|
|
|
53
|
+
# Get the options hash
|
|
54
|
+
batch_op = batch_calls[index]
|
|
55
|
+
index += 1
|
|
56
|
+
|
|
57
|
+
if call_result
|
|
58
|
+
# (see note in regular api method about JSON parsing)
|
|
59
|
+
body = MultiJson.decode("[#{call_result['body'].to_s}]")[0]
|
|
60
|
+
|
|
61
|
+
unless call_result["code"].to_i >= 500 || error = check_response(body)
|
|
62
|
+
# Get the HTTP component they want
|
|
63
|
+
data = case batch_op.http_options[:http_component]
|
|
64
|
+
when :status
|
|
65
|
+
call_result["code"].to_i
|
|
66
|
+
when :headers
|
|
67
|
+
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
|
68
|
+
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
|
69
|
+
else
|
|
70
|
+
body
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# process it if we are given a block to process with
|
|
74
|
+
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
|
75
|
+
else
|
|
76
|
+
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
module Facebook
|
|
3
|
+
class GraphCollection < Array
|
|
4
|
+
# This class is a light wrapper for collections returned
|
|
5
|
+
# from the Graph API.
|
|
6
|
+
#
|
|
7
|
+
# It extends Array to allow direct access to the data colleciton
|
|
8
|
+
# which should allow it to drop in seamlessly.
|
|
9
|
+
#
|
|
10
|
+
# It also allows access to paging information and the
|
|
11
|
+
# ability to get the next/previous page in the collection
|
|
12
|
+
# by calling next_page or previous_page.
|
|
13
|
+
attr_reader :paging
|
|
14
|
+
attr_reader :api
|
|
15
|
+
|
|
16
|
+
def initialize(response, api)
|
|
17
|
+
super response["data"]
|
|
18
|
+
@paging = response["paging"]
|
|
19
|
+
@api = api
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# defines methods for NEXT and PREVIOUS pages
|
|
23
|
+
%w{next previous}.each do |this|
|
|
24
|
+
|
|
25
|
+
# def next_page
|
|
26
|
+
# def previous_page
|
|
27
|
+
define_method "#{this.to_sym}_page" do
|
|
28
|
+
base, args = send("#{this}_page_params")
|
|
29
|
+
base ? @api.get_page([base, args]) : nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# def next_page_params
|
|
33
|
+
# def previous_page_params
|
|
34
|
+
define_method "#{this.to_sym}_page_params" do
|
|
35
|
+
return nil unless @paging and @paging[this]
|
|
36
|
+
parse_page_url(@paging[this])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_page_url(url)
|
|
41
|
+
match = url.match(/.com\/(.*)\?(.*)/)
|
|
42
|
+
base = match[1]
|
|
43
|
+
args = match[2]
|
|
44
|
+
params = CGI.parse(args)
|
|
45
|
+
new_params = {}
|
|
46
|
+
params.each_pair do |key,value|
|
|
47
|
+
new_params[key] = value.join ","
|
|
48
|
+
end
|
|
49
|
+
[base,new_params]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|