koala 1.5.0 → 1.6.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/.gitignore +2 -1
- data/.travis.yml +4 -1
- data/Gemfile +1 -1
- data/changelog.md +294 -0
- data/koala.gemspec +3 -2
- data/lib/koala/api/batch_operation.rb +1 -1
- data/lib/koala/api/graph_api.rb +132 -62
- data/lib/koala/api/graph_batch_api.rb +28 -38
- data/lib/koala/api/graph_collection.rb +3 -1
- data/lib/koala/api/rest_api.rb +19 -3
- data/lib/koala/api.rb +11 -31
- data/lib/koala/errors.rb +86 -0
- data/lib/koala/oauth.rb +21 -21
- data/lib/koala/realtime_updates.rb +42 -21
- data/lib/koala/version.rb +1 -1
- data/lib/koala.rb +1 -2
- data/readme.md +130 -103
- data/spec/cases/api_spec.rb +3 -3
- data/spec/cases/error_spec.rb +91 -20
- data/spec/cases/graph_api_batch_spec.rb +57 -22
- data/spec/cases/graph_api_spec.rb +68 -0
- data/spec/cases/graph_collection_spec.rb +6 -0
- data/spec/cases/oauth_spec.rb +16 -16
- data/spec/cases/realtime_updates_spec.rb +80 -82
- data/spec/cases/test_users_spec.rb +25 -20
- data/spec/fixtures/mock_facebook_responses.yml +45 -29
- data/spec/spec_helper.rb +6 -6
- data/spec/support/graph_api_shared_examples.rb +13 -13
- data/spec/support/koala_test.rb +13 -13
- data/spec/support/rest_api_shared_examples.rb +3 -3
- metadata +28 -9
- data/CHANGELOG +0 -275
data/lib/koala/api/graph_api.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'addressable/uri'
|
|
2
|
+
|
|
1
3
|
require 'koala/api/graph_collection'
|
|
2
4
|
require 'koala/http_service/uploadable_io'
|
|
3
5
|
|
|
@@ -40,13 +42,20 @@ module Koala
|
|
|
40
42
|
# @param args any additional arguments
|
|
41
43
|
# (fields, metadata, etc. -- see {http://developers.facebook.com/docs/reference/api/ Facebook's documentation})
|
|
42
44
|
# @param options (see Koala::Facebook::API#api)
|
|
45
|
+
# @param block for post-processing. It receives the result data; the
|
|
46
|
+
# return value of the method is the result of the block, if
|
|
47
|
+
# provided. (see Koala::Facebook::API#api)
|
|
43
48
|
#
|
|
44
49
|
# @raise [Koala::Facebook::APIError] if the ID is invalid or you don't have access to that object
|
|
45
50
|
#
|
|
51
|
+
# @example
|
|
52
|
+
# get_object("me") # => {"id" => ..., "name" => ...}
|
|
53
|
+
# get_object("me") {|data| data['education']} # => only education section of profile
|
|
54
|
+
#
|
|
46
55
|
# @return a hash of object data
|
|
47
|
-
def get_object(id, args = {}, options = {})
|
|
48
|
-
#
|
|
49
|
-
graph_call(id, args, "get", options)
|
|
56
|
+
def get_object(id, args = {}, options = {}, &block)
|
|
57
|
+
# Fetches the given object from the graph.
|
|
58
|
+
graph_call(id, args, "get", options, &block)
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
# Get information about multiple Facebook objects in one call.
|
|
@@ -54,15 +63,16 @@ module Koala
|
|
|
54
63
|
# @param ids an array or comma-separated string of object IDs
|
|
55
64
|
# @param args (see #get_object)
|
|
56
65
|
# @param options (see Koala::Facebook::API#api)
|
|
66
|
+
# @param block (see Koala::Facebook::API#api)
|
|
57
67
|
#
|
|
58
68
|
# @raise [Koala::Facebook::APIError] if any ID is invalid or you don't have access to that object
|
|
59
69
|
#
|
|
60
70
|
# @return an array of object data hashes
|
|
61
|
-
def get_objects(ids, args = {}, options = {})
|
|
62
|
-
#
|
|
71
|
+
def get_objects(ids, args = {}, options = {}, &block)
|
|
72
|
+
# Fetches all of the given objects from the graph.
|
|
63
73
|
# If any of the IDs are invalid, they'll raise an exception.
|
|
64
74
|
return [] if ids.empty?
|
|
65
|
-
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
|
75
|
+
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options, &block)
|
|
66
76
|
end
|
|
67
77
|
|
|
68
78
|
# Write an object to the Graph for a specific user.
|
|
@@ -71,20 +81,21 @@ module Koala
|
|
|
71
81
|
# @note put_object is (for historical reasons) the same as put_connections.
|
|
72
82
|
# Please use put_connections; in a future version of Koala (2.0?),
|
|
73
83
|
# put_object will issue a POST directly to an individual object, not to a connection.
|
|
74
|
-
def put_object(parent_object, connection_name, args = {}, options = {})
|
|
75
|
-
put_connections(parent_object, connection_name, args, options)
|
|
84
|
+
def put_object(parent_object, connection_name, args = {}, options = {}, &block)
|
|
85
|
+
put_connections(parent_object, connection_name, args, options, &block)
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
# Delete an object from the Graph if you have appropriate permissions.
|
|
79
89
|
#
|
|
80
90
|
# @param id (see #get_object)
|
|
81
91
|
# @param options (see #get_object)
|
|
92
|
+
# @param block (see Koala::Facebook::API#api)
|
|
82
93
|
#
|
|
83
94
|
# @return true if successful, false (or an APIError) if not
|
|
84
|
-
def delete_object(id, options = {})
|
|
95
|
+
def delete_object(id, options = {}, &block)
|
|
85
96
|
# Deletes the object with the given ID from the graph.
|
|
86
|
-
raise
|
|
87
|
-
graph_call(id, {}, "delete", options)
|
|
97
|
+
raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
|
|
98
|
+
graph_call(id, {}, "delete", options, &block)
|
|
88
99
|
end
|
|
89
100
|
|
|
90
101
|
# Fetch information about a given connection (e.g. type of activity -- feed, events, photos, etc.)
|
|
@@ -98,11 +109,12 @@ module Koala
|
|
|
98
109
|
# @param connection_name what
|
|
99
110
|
# @param args any additional arguments
|
|
100
111
|
# @param options (see #get_object)
|
|
112
|
+
# @param block (see Koala::Facebook::API#api)
|
|
101
113
|
#
|
|
102
114
|
# @return [Koala::Facebook::API::GraphCollection] an array of object hashes (in most cases)
|
|
103
|
-
def get_connection(id, connection_name, args = {}, options = {})
|
|
104
|
-
#
|
|
105
|
-
graph_call("#{id}/#{connection_name}", args, "get", options)
|
|
115
|
+
def get_connection(id, connection_name, args = {}, options = {}, &block)
|
|
116
|
+
# Fetches the connections for given object.
|
|
117
|
+
graph_call("#{id}/#{connection_name}", args, "get", options, &block)
|
|
106
118
|
end
|
|
107
119
|
alias_method :get_connections, :get_connection
|
|
108
120
|
|
|
@@ -126,12 +138,13 @@ module Koala
|
|
|
126
138
|
# @param connection_name (see #get_connection)
|
|
127
139
|
# @param args (see #get_connection)
|
|
128
140
|
# @param options (see #get_object)
|
|
141
|
+
# @param block (see Koala::Facebook::API#api)
|
|
129
142
|
#
|
|
130
143
|
# @return a hash containing the new object's id
|
|
131
|
-
def put_connections(id, connection_name, args = {}, options = {})
|
|
144
|
+
def put_connections(id, connection_name, args = {}, options = {}, &block)
|
|
132
145
|
# Posts a certain connection
|
|
133
|
-
raise
|
|
134
|
-
graph_call("#{id}/#{connection_name}", args, "post", options)
|
|
146
|
+
raise AuthenticationError.new(nil, nil, "Write operations require an access token") unless @access_token
|
|
147
|
+
graph_call("#{id}/#{connection_name}", args, "post", options, &block)
|
|
135
148
|
end
|
|
136
149
|
|
|
137
150
|
# Delete an object's connection (for instance, unliking the object).
|
|
@@ -142,12 +155,13 @@ module Koala
|
|
|
142
155
|
# @param connection_name (see #get_connection)
|
|
143
156
|
# @args (see #get_connection)
|
|
144
157
|
# @param options (see #get_object)
|
|
158
|
+
# @param block (see Koala::Facebook::API#api)
|
|
145
159
|
#
|
|
146
160
|
# @return (see #delete_object)
|
|
147
|
-
def delete_connections(id, connection_name, args = {}, options = {})
|
|
161
|
+
def delete_connections(id, connection_name, args = {}, options = {}, &block)
|
|
148
162
|
# Deletes a given connection
|
|
149
|
-
raise
|
|
150
|
-
graph_call("#{id}/#{connection_name}", args, "delete", options)
|
|
163
|
+
raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
|
|
164
|
+
graph_call("#{id}/#{connection_name}", args, "delete", options, &block)
|
|
151
165
|
end
|
|
152
166
|
|
|
153
167
|
# Fetches a photo.
|
|
@@ -156,15 +170,17 @@ module Koala
|
|
|
156
170
|
#
|
|
157
171
|
# @param options options for Facebook (see #get_object).
|
|
158
172
|
# To get a different size photo, pass :type => size (small, normal, large, square).
|
|
173
|
+
# @param block (see Koala::Facebook::API#api)
|
|
159
174
|
#
|
|
160
175
|
# @note to delete photos or videos, use delete_object(id)
|
|
161
176
|
#
|
|
162
177
|
# @return the URL to the image
|
|
163
|
-
def get_picture(object, args = {}, options = {})
|
|
178
|
+
def get_picture(object, args = {}, options = {}, &block)
|
|
164
179
|
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
|
165
|
-
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
|
180
|
+
resolved_result = graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
|
166
181
|
result["Location"]
|
|
167
182
|
end
|
|
183
|
+
block ? block.call(resolved_result) : resolved_result
|
|
168
184
|
end
|
|
169
185
|
|
|
170
186
|
# Upload a photo.
|
|
@@ -180,6 +196,7 @@ module Koala
|
|
|
180
196
|
# @param args (see #get_object)
|
|
181
197
|
# @param target_id the Facebook object to which to post the picture (default: "me")
|
|
182
198
|
# @param options (see #get_object)
|
|
199
|
+
# @param block (see Koala::Facebook::API#api)
|
|
183
200
|
#
|
|
184
201
|
# @example
|
|
185
202
|
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
|
@@ -190,16 +207,16 @@ module Koala
|
|
|
190
207
|
# @note to access the media after upload, you'll need the user_photos or user_videos permission as appropriate.
|
|
191
208
|
#
|
|
192
209
|
# @return (see #put_connections)
|
|
193
|
-
def put_picture(*picture_args)
|
|
194
|
-
put_connections(*parse_media_args(picture_args, "photos"))
|
|
210
|
+
def put_picture(*picture_args, &block)
|
|
211
|
+
put_connections(*parse_media_args(picture_args, "photos"), &block)
|
|
195
212
|
end
|
|
196
213
|
|
|
197
214
|
# Upload a video. Functions exactly the same as put_picture.
|
|
198
215
|
# @see #put_picture
|
|
199
|
-
def put_video(*video_args)
|
|
216
|
+
def put_video(*video_args, &block)
|
|
200
217
|
args = parse_media_args(video_args, "videos")
|
|
201
218
|
args.last[:video] = true
|
|
202
|
-
put_connections(*args)
|
|
219
|
+
put_connections(*args, &block)
|
|
203
220
|
end
|
|
204
221
|
|
|
205
222
|
# Write directly to the user's wall.
|
|
@@ -213,6 +230,7 @@ module Koala
|
|
|
213
230
|
# (see the {https://developers.facebook.com/docs/guides/attachments/ stream attachments} documentation.)
|
|
214
231
|
# @param target_id the target wall
|
|
215
232
|
# @param options (see #get_object)
|
|
233
|
+
# @param block (see Koala::Facebook::API#api)
|
|
216
234
|
#
|
|
217
235
|
# @example
|
|
218
236
|
# @api.put_wall_post("Hello there!", {
|
|
@@ -225,8 +243,8 @@ module Koala
|
|
|
225
243
|
#
|
|
226
244
|
# @see #put_connections
|
|
227
245
|
# @return (see #put_connections)
|
|
228
|
-
def put_wall_post(message, attachment = {}, target_id = "me", options = {})
|
|
229
|
-
put_connections(target_id, "feed", attachment.merge({:message => message}), options)
|
|
246
|
+
def put_wall_post(message, attachment = {}, target_id = "me", options = {}, &block)
|
|
247
|
+
put_connections(target_id, "feed", attachment.merge({:message => message}), options, &block)
|
|
230
248
|
end
|
|
231
249
|
|
|
232
250
|
# Comment on a given object.
|
|
@@ -238,11 +256,12 @@ module Koala
|
|
|
238
256
|
# @param id (see #get_object)
|
|
239
257
|
# @param message the comment to write
|
|
240
258
|
# @param options (see #get_object)
|
|
259
|
+
# @param block (see Koala::Facebook::API#api)
|
|
241
260
|
#
|
|
242
261
|
# @return (see #put_connections)
|
|
243
|
-
def put_comment(id, message, options = {})
|
|
262
|
+
def put_comment(id, message, options = {}, &block)
|
|
244
263
|
# Writes the given comment on the given post.
|
|
245
|
-
put_connections(id, "comments", {:message => message}, options)
|
|
264
|
+
put_connections(id, "comments", {:message => message}, options, &block)
|
|
246
265
|
end
|
|
247
266
|
|
|
248
267
|
# Like a given object.
|
|
@@ -252,11 +271,12 @@ module Koala
|
|
|
252
271
|
#
|
|
253
272
|
# @param id (see #get_object)
|
|
254
273
|
# @param options (see #get_object)
|
|
274
|
+
# @param block (see Koala::Facebook::API#api)
|
|
255
275
|
#
|
|
256
276
|
# @return (see #put_connections)
|
|
257
|
-
def put_like(id, options = {})
|
|
277
|
+
def put_like(id, options = {}, &block)
|
|
258
278
|
# Likes the given post.
|
|
259
|
-
put_connections(id, "likes", {}, options)
|
|
279
|
+
put_connections(id, "likes", {}, options, &block)
|
|
260
280
|
end
|
|
261
281
|
|
|
262
282
|
# Unlike a given object.
|
|
@@ -264,12 +284,13 @@ module Koala
|
|
|
264
284
|
#
|
|
265
285
|
# @param id (see #get_object)
|
|
266
286
|
# @param options (see #get_object)
|
|
287
|
+
# @param block (see Koala::Facebook::API#api)
|
|
267
288
|
#
|
|
268
289
|
# @return (see #delete_object)
|
|
269
|
-
def delete_like(id, options = {})
|
|
290
|
+
def delete_like(id, options = {}, &block)
|
|
270
291
|
# Unlikes a given object for the logged-in user
|
|
271
|
-
raise
|
|
272
|
-
graph_call("#{id}/likes", {}, "delete", options)
|
|
292
|
+
raise AuthenticationError.new(nil, nil, "Unliking requires an access token") unless @access_token
|
|
293
|
+
graph_call("#{id}/likes", {}, "delete", options, &block)
|
|
273
294
|
end
|
|
274
295
|
|
|
275
296
|
# Search for a given query among visible Facebook objects.
|
|
@@ -278,11 +299,12 @@ module Koala
|
|
|
278
299
|
# @param search_terms the query to search for
|
|
279
300
|
# @param args additional arguments, such as type, fields, etc.
|
|
280
301
|
# @param options (see #get_object)
|
|
302
|
+
# @param block (see Koala::Facebook::API#api)
|
|
281
303
|
#
|
|
282
304
|
# @return [Koala::Facebook::API::GraphCollection] an array of search results
|
|
283
|
-
def search(search_terms, args = {}, options = {})
|
|
305
|
+
def search(search_terms, args = {}, options = {}, &block)
|
|
284
306
|
args.merge!({:q => search_terms}) unless search_terms.nil?
|
|
285
|
-
graph_call("search", args, "get", options)
|
|
307
|
+
graph_call("search", args, "get", options, &block)
|
|
286
308
|
end
|
|
287
309
|
|
|
288
310
|
# Convenience Methods
|
|
@@ -296,8 +318,11 @@ module Koala
|
|
|
296
318
|
# @param query the FQL query to perform
|
|
297
319
|
# @param args (see #get_object)
|
|
298
320
|
# @param options (see #get_object)
|
|
299
|
-
|
|
300
|
-
|
|
321
|
+
# @param block (see Koala::Facebook::API#api)
|
|
322
|
+
#
|
|
323
|
+
# @return the result of the FQL query.
|
|
324
|
+
def fql_query(query, args = {}, options = {}, &block)
|
|
325
|
+
get_object("fql", args.merge(:q => query), options, &block)
|
|
301
326
|
end
|
|
302
327
|
|
|
303
328
|
# Make an FQL multiquery.
|
|
@@ -306,6 +331,7 @@ module Koala
|
|
|
306
331
|
# @param queries a hash of query names => FQL queries
|
|
307
332
|
# @param args (see #get_object)
|
|
308
333
|
# @param options (see #get_object)
|
|
334
|
+
# @param block (see Koala::Facebook::API#api)
|
|
309
335
|
#
|
|
310
336
|
# @example
|
|
311
337
|
# @api.fql_multiquery({
|
|
@@ -316,11 +342,13 @@ module Koala
|
|
|
316
342
|
# # instead of [{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]}]
|
|
317
343
|
#
|
|
318
344
|
# @return a hash of FQL results keyed to the appropriate query
|
|
319
|
-
def fql_multiquery(queries = {}, args = {}, options = {})
|
|
320
|
-
if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
|
|
345
|
+
def fql_multiquery(queries = {}, args = {}, options = {}, &block)
|
|
346
|
+
resolved_results = if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
|
|
321
347
|
# simplify the multiquery result format
|
|
322
348
|
results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
|
|
323
349
|
end
|
|
350
|
+
|
|
351
|
+
block ? block.call(resolved_results) : resolved_results
|
|
324
352
|
end
|
|
325
353
|
|
|
326
354
|
# Get a page's access token, allowing you to act as the page.
|
|
@@ -329,30 +357,34 @@ module Koala
|
|
|
329
357
|
# @param id the page ID
|
|
330
358
|
# @param args (see #get_object)
|
|
331
359
|
# @param options (see #get_object)
|
|
360
|
+
# @param block (see Koala::Facebook::API#api)
|
|
332
361
|
#
|
|
333
362
|
# @return the page's access token (discarding expiration and any other information)
|
|
334
|
-
def get_page_access_token(id, args = {}, options = {})
|
|
335
|
-
|
|
363
|
+
def get_page_access_token(id, args = {}, options = {}, &block)
|
|
364
|
+
access_token = get_object(id, args.merge(:fields => "access_token"), options) do |result|
|
|
336
365
|
result ? result["access_token"] : nil
|
|
337
366
|
end
|
|
367
|
+
|
|
368
|
+
block ? block.call(access_token) : access_token
|
|
338
369
|
end
|
|
339
370
|
|
|
340
|
-
#
|
|
371
|
+
# Fetches the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
|
|
341
372
|
# See https://developers.facebook.com/blog/post/490.
|
|
342
373
|
#
|
|
343
374
|
# @param urls the URLs for which you want comments
|
|
344
375
|
# @param args (see #get_object)
|
|
345
376
|
# @param options (see #get_object)
|
|
377
|
+
# @param block (see Koala::Facebook::API#api)
|
|
346
378
|
#
|
|
347
379
|
# @returns a hash of urls => comment arrays
|
|
348
|
-
def get_comments_for_urls(urls = [], args = {}, options = {})
|
|
380
|
+
def get_comments_for_urls(urls = [], args = {}, options = {}, &block)
|
|
349
381
|
return [] if urls.empty?
|
|
350
382
|
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
|
351
|
-
get_object("comments", args, options)
|
|
383
|
+
get_object("comments", args, options, &block)
|
|
352
384
|
end
|
|
353
385
|
|
|
354
|
-
def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {})
|
|
355
|
-
graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options)
|
|
386
|
+
def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {}, &block)
|
|
387
|
+
graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options, &block)
|
|
356
388
|
end
|
|
357
389
|
|
|
358
390
|
# Certain calls such as {#get_connections} return an array of results which you can page through
|
|
@@ -364,10 +396,11 @@ module Koala
|
|
|
364
396
|
#
|
|
365
397
|
# @param params an array of arguments to graph_call
|
|
366
398
|
# as returned by {Koala::Facebook::GraphCollection.parse_page_url}.
|
|
399
|
+
# @param block (see Koala::Facebook::API#api)
|
|
367
400
|
#
|
|
368
401
|
# @return Koala::Facebook::GraphCollection the appropriate page of results (an empty array if there are none)
|
|
369
|
-
def get_page(params)
|
|
370
|
-
graph_call(*params)
|
|
402
|
+
def get_page(params, &block)
|
|
403
|
+
graph_call(*params, &block)
|
|
371
404
|
end
|
|
372
405
|
|
|
373
406
|
# Execute a set of Graph API calls as a batch.
|
|
@@ -386,10 +419,21 @@ module Koala
|
|
|
386
419
|
#
|
|
387
420
|
# @example
|
|
388
421
|
# results = @api.batch do |batch_api|
|
|
389
|
-
#
|
|
390
|
-
#
|
|
422
|
+
# batch_api.get_object('me')
|
|
423
|
+
# batch_api.get_object(KoalaTest.user1)
|
|
424
|
+
# end
|
|
425
|
+
# # => [{'id' => my_id, ...}, {'id' => koppel_id, ...}]
|
|
426
|
+
#
|
|
427
|
+
# # You can also provide blocks to your operations to process the
|
|
428
|
+
# # results, which is often useful if you're constructing batch
|
|
429
|
+
# # requests in various locations and want to keep the code
|
|
430
|
+
# # together in logical places.
|
|
431
|
+
# # See readme.md and the wiki for more examples.
|
|
432
|
+
# @api.batch do |batch_api|
|
|
433
|
+
# batch_api.get_object('me') {|data| data["id"] }
|
|
434
|
+
# batch_api.get_object(KoalaTest.user1) {|data| data["name"] }
|
|
391
435
|
# end
|
|
392
|
-
# # => [
|
|
436
|
+
# # => [my_id, "Alex Koppel"]
|
|
393
437
|
#
|
|
394
438
|
# @return an array of results from your batch calls (as if you'd made them individually),
|
|
395
439
|
# arranged in the same order they're made.
|
|
@@ -422,7 +466,7 @@ module Koala
|
|
|
422
466
|
# @return the result from Facebook
|
|
423
467
|
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
|
424
468
|
result = api(path, args, verb, options) do |response|
|
|
425
|
-
error = check_response(response)
|
|
469
|
+
error = check_response(response.status, response.body)
|
|
426
470
|
raise error if error
|
|
427
471
|
end
|
|
428
472
|
|
|
@@ -435,12 +479,38 @@ module Koala
|
|
|
435
479
|
|
|
436
480
|
private
|
|
437
481
|
|
|
438
|
-
def check_response(
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
482
|
+
def check_response(http_status, response_body)
|
|
483
|
+
# Check for Graph API-specific errors. This returns an error of the appropriate type
|
|
484
|
+
# which is immediately raised (non-batch) or added to the list of batch results (batch)
|
|
485
|
+
http_status = http_status.to_i
|
|
486
|
+
|
|
487
|
+
if http_status >= 400
|
|
488
|
+
begin
|
|
489
|
+
response_hash = MultiJson.load(response_body)
|
|
490
|
+
rescue MultiJson::DecodeError
|
|
491
|
+
response_hash = {}
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
if response_hash['error_code']
|
|
495
|
+
# Old batch api error format. This can be removed on July 5, 2012.
|
|
496
|
+
# See https://developers.facebook.com/roadmap/#graph-batch-api-exception-format
|
|
497
|
+
error_info = {
|
|
498
|
+
'code' => response_hash['error_code'],
|
|
499
|
+
'message' => response_hash['error_description']
|
|
500
|
+
}
|
|
501
|
+
else
|
|
502
|
+
error_info = response_hash['error'] || {}
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
if error_info['type'] == 'OAuthException' &&
|
|
506
|
+
( !error_info['code'] || [102, 190, 450, 452, 2500].include?(error_info['code'].to_i))
|
|
507
|
+
|
|
508
|
+
# See: https://developers.facebook.com/docs/authentication/access-token-expiration/
|
|
509
|
+
# https://developers.facebook.com/bugs/319643234746794?browse=search_4fa075c0bd9117b20604672
|
|
510
|
+
AuthenticationError.new(http_status, response_body, error_info)
|
|
511
|
+
else
|
|
512
|
+
ClientError.new(http_status, response_body, error_info)
|
|
513
|
+
end
|
|
444
514
|
end
|
|
445
515
|
end
|
|
446
516
|
|
|
@@ -468,9 +538,9 @@ module Koala
|
|
|
468
538
|
def url?(data)
|
|
469
539
|
return false unless data.is_a? String
|
|
470
540
|
begin
|
|
471
|
-
uri = URI.parse(data)
|
|
541
|
+
uri = Addressable::URI.parse(data)
|
|
472
542
|
%w( http https ).include?(uri.scheme)
|
|
473
|
-
rescue URI::
|
|
543
|
+
rescue Addressable::URI::InvalidURIError
|
|
474
544
|
false
|
|
475
545
|
end
|
|
476
546
|
end
|
|
@@ -19,35 +19,23 @@ module Koala
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
# for batch APIs, we queue up the call details (incl. post-processing)
|
|
23
|
+
batch_calls << BatchOperation.new(
|
|
24
|
+
:url => path,
|
|
25
|
+
:args => args,
|
|
26
|
+
:method => verb,
|
|
27
|
+
:access_token => options['access_token'] || access_token,
|
|
28
|
+
:http_options => options,
|
|
29
|
+
:post_processing => post_processing
|
|
30
|
+
)
|
|
31
|
+
nil # batch operations return nothing immediately
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# old error format -- see http://developers.facebook.com/blog/post/596/
|
|
37
|
-
APIError.new({"type" => "Error #{response["error"]}", "message" => response["error_description"]}.merge(response))
|
|
38
|
-
else
|
|
39
|
-
check_graph_api_response(response)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# redefine the graph_call and check_response methods
|
|
44
|
-
# so we can use this API inside the batch block just like any regular Graph API
|
|
34
|
+
# redefine the graph_call method so we can use this API inside the batch block
|
|
35
|
+
# just like any regular Graph API
|
|
45
36
|
alias_method :graph_call_outside_batch, :graph_call
|
|
46
37
|
alias_method :graph_call, :graph_call_in_batch
|
|
47
38
|
|
|
48
|
-
alias_method :check_graph_api_response, :check_response
|
|
49
|
-
alias_method :check_response, :check_graph_batch_api_response
|
|
50
|
-
|
|
51
39
|
# execute the queued batch calls
|
|
52
40
|
def execute(http_options = {})
|
|
53
41
|
return [] unless batch_calls.length > 0
|
|
@@ -62,7 +50,7 @@ module Koala
|
|
|
62
50
|
unless response
|
|
63
51
|
# Facebook sometimes reportedly returns an empty body at times
|
|
64
52
|
# see https://github.com/arsduo/koala/issues/184
|
|
65
|
-
raise
|
|
53
|
+
raise BadFacebookResponse.new(200, '', "Facebook returned an empty body")
|
|
66
54
|
end
|
|
67
55
|
|
|
68
56
|
# map the results with post-processing included
|
|
@@ -72,13 +60,16 @@ module Koala
|
|
|
72
60
|
batch_op = batch_calls[index]
|
|
73
61
|
index += 1
|
|
74
62
|
|
|
63
|
+
raw_result = nil
|
|
75
64
|
if call_result
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
if ( error = check_response(call_result['code'], call_result['body'].to_s) )
|
|
66
|
+
raw_result = error
|
|
67
|
+
else
|
|
68
|
+
# (see note in regular api method about JSON parsing)
|
|
69
|
+
body = MultiJson.load("[#{call_result['body'].to_s}]")[0]
|
|
78
70
|
|
|
79
|
-
unless call_result["code"].to_i >= 500 || error = check_response(body)
|
|
80
71
|
# Get the HTTP component they want
|
|
81
|
-
|
|
72
|
+
raw_result = case batch_op.http_options[:http_component]
|
|
82
73
|
when :status
|
|
83
74
|
call_result["code"].to_i
|
|
84
75
|
when :headers
|
|
@@ -87,20 +78,19 @@ module Koala
|
|
|
87
78
|
else
|
|
88
79
|
body
|
|
89
80
|
end
|
|
90
|
-
|
|
91
|
-
# process it if we are given a block to process with
|
|
92
|
-
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
|
93
|
-
else
|
|
94
|
-
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
|
95
81
|
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# turn any results that are pageable into GraphCollections
|
|
85
|
+
# and pass to post-processing callback if given
|
|
86
|
+
result = GraphCollection.evaluate(raw_result, @original_api)
|
|
87
|
+
if batch_op.post_processing
|
|
88
|
+
batch_op.post_processing.call(result)
|
|
96
89
|
else
|
|
97
|
-
|
|
90
|
+
result
|
|
98
91
|
end
|
|
99
92
|
end
|
|
100
93
|
end
|
|
101
|
-
|
|
102
|
-
# turn any results that are pageable into GraphCollections
|
|
103
|
-
batch_result.inject([]) {|processed_results, raw| processed_results << GraphCollection.evaluate(raw, @original_api)}
|
|
104
94
|
end
|
|
105
95
|
|
|
106
96
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'addressable/uri'
|
|
2
|
+
|
|
1
3
|
module Koala
|
|
2
4
|
module Facebook
|
|
3
5
|
class API
|
|
@@ -87,7 +89,7 @@ module Koala
|
|
|
87
89
|
#
|
|
88
90
|
# @return an array of parameters that can be provided via graph_call(*parsed_params)
|
|
89
91
|
def self.parse_page_url(url)
|
|
90
|
-
uri = URI.parse(url)
|
|
92
|
+
uri = Addressable::URI.parse(url)
|
|
91
93
|
|
|
92
94
|
base = uri.path.sub(/^\//, '')
|
|
93
95
|
params = CGI.parse(uri.query)
|
data/lib/koala/api/rest_api.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Koala
|
|
|
21
21
|
#
|
|
22
22
|
# @return true if successful, false if not. (This call currently doesn't give useful feedback on failure.)
|
|
23
23
|
def set_app_properties(properties, args = {}, options = {})
|
|
24
|
-
raise
|
|
24
|
+
raise AuthenticationError.new(nil, nil, "setAppProperties requires an access token") unless @access_token
|
|
25
25
|
rest_call("admin.setAppProperties", args.merge(:properties => MultiJson.dump(properties)), options, "post")
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -44,8 +44,24 @@ module Koala
|
|
|
44
44
|
|
|
45
45
|
api("method/#{fb_method}", args.merge('format' => 'json'), verb, options) do |response|
|
|
46
46
|
# check for REST API-specific errors
|
|
47
|
-
if response.
|
|
48
|
-
|
|
47
|
+
if response.status >= 400
|
|
48
|
+
begin
|
|
49
|
+
response_hash = MultiJson.load(response.body)
|
|
50
|
+
rescue MultiJson::DecodeError
|
|
51
|
+
response_hash = {}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
error_info = {
|
|
55
|
+
'code' => response_hash['error_code'],
|
|
56
|
+
'error_subcode' => response_hash['error_subcode'],
|
|
57
|
+
'message' => response_hash['error_msg']
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if response.status >= 500
|
|
61
|
+
raise ServerError.new(response.status, response.body, error_info)
|
|
62
|
+
else
|
|
63
|
+
raise ClientError.new(response.status, response.body, error_info)
|
|
64
|
+
end
|
|
49
65
|
end
|
|
50
66
|
end
|
|
51
67
|
end
|
data/lib/koala/api.rb
CHANGED
|
@@ -35,9 +35,9 @@ module Koala
|
|
|
35
35
|
# (All API requests with access tokens use SSL.)
|
|
36
36
|
# @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
|
|
37
37
|
#
|
|
38
|
-
# @yield The response
|
|
38
|
+
# @yield The response for evaluation
|
|
39
39
|
#
|
|
40
|
-
# @raise [Koala::Facebook::
|
|
40
|
+
# @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
|
|
41
41
|
#
|
|
42
42
|
# @return the body of the response from Facebook (unless another http_component is requested)
|
|
43
43
|
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
|
@@ -50,43 +50,23 @@ module Koala
|
|
|
50
50
|
# make the request via the provided service
|
|
51
51
|
result = Koala.make_request(path, args, verb, options)
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# parse the body as JSON and run it through the error checker (if provided)
|
|
59
|
-
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
|
60
|
-
# and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
|
|
61
|
-
body = MultiJson.load("[#{result.body.to_s}]")[0]
|
|
62
|
-
yield body if error_checking_block
|
|
53
|
+
if result.status.to_i >= 500
|
|
54
|
+
raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
yield result if error_checking_block
|
|
63
58
|
|
|
64
59
|
# if we want a component other than the body (e.g. redirect header for images), return that
|
|
65
60
|
if component = options[:http_component]
|
|
66
61
|
component == :response ? result : result.send(options[:http_component])
|
|
67
62
|
else
|
|
68
|
-
body
|
|
63
|
+
# parse the body as JSON and run it through the error checker (if provided)
|
|
64
|
+
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
|
65
|
+
# and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
|
|
66
|
+
MultiJson.load("[#{result.body.to_s}]")[0]
|
|
69
67
|
end
|
|
70
68
|
end
|
|
71
69
|
end
|
|
72
|
-
|
|
73
|
-
class APIError < StandardError
|
|
74
|
-
attr_accessor :fb_error_type, :fb_error_code, :fb_error_message, :raw_response
|
|
75
|
-
|
|
76
|
-
# Creates a new APIError.
|
|
77
|
-
#
|
|
78
|
-
# Assigns the error type (as reported by Facebook) to #fb_error_type
|
|
79
|
-
# and the raw error response available to #raw_response.
|
|
80
|
-
#
|
|
81
|
-
# @param details error details containing "type" and "message" keys.
|
|
82
|
-
def initialize(details = {})
|
|
83
|
-
self.raw_response = details
|
|
84
|
-
self.fb_error_type = details["type"]
|
|
85
|
-
self.fb_error_code = details["code"]
|
|
86
|
-
self.fb_error_message = details["message"]
|
|
87
|
-
super("#{fb_error_type}: #{details["message"]}")
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
70
|
end
|
|
91
71
|
end
|
|
92
72
|
|