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/errors.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
|
|
3
|
+
class KoalaError < StandardError; end
|
|
4
|
+
|
|
5
|
+
module Facebook
|
|
6
|
+
|
|
7
|
+
# The OAuth signature is incomplete, invalid, or using an unsupported algorithm
|
|
8
|
+
class OAuthSignatureError < ::Koala::KoalaError; end
|
|
9
|
+
|
|
10
|
+
# Facebook responded with an error to an API request. If the exception contains a nil
|
|
11
|
+
# http_status, then the error was detected before making a call to Facebook. (e.g. missing access token)
|
|
12
|
+
class APIError < ::Koala::KoalaError
|
|
13
|
+
attr_accessor :fb_error_type, :fb_error_code, :fb_error_subcode, :fb_error_message,
|
|
14
|
+
:http_status, :response_body
|
|
15
|
+
|
|
16
|
+
# Create a new API Error
|
|
17
|
+
#
|
|
18
|
+
# @param http_status [Integer] The HTTP status code of the response
|
|
19
|
+
# @param response_body [String] The response body
|
|
20
|
+
# @param error_info One of the following:
|
|
21
|
+
# [Hash] The error information extracted from the request
|
|
22
|
+
# ("type", "code", "error_subcode", "message")
|
|
23
|
+
# [String] The error description
|
|
24
|
+
# If error_info is nil or not provided, the method will attempt to extract
|
|
25
|
+
# the error info from the response_body
|
|
26
|
+
#
|
|
27
|
+
# @return the newly created APIError
|
|
28
|
+
def initialize(http_status, response_body, error_info = nil)
|
|
29
|
+
if response_body
|
|
30
|
+
self.response_body = response_body.strip
|
|
31
|
+
else
|
|
32
|
+
self.response_body = ''
|
|
33
|
+
end
|
|
34
|
+
self.http_status = http_status
|
|
35
|
+
|
|
36
|
+
if error_info && error_info.is_a?(String)
|
|
37
|
+
message = error_info
|
|
38
|
+
else
|
|
39
|
+
unless error_info
|
|
40
|
+
begin
|
|
41
|
+
error_info = MultiJson.load(response_body)['error'] if response_body
|
|
42
|
+
rescue
|
|
43
|
+
end
|
|
44
|
+
error_info ||= {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
self.fb_error_type = error_info["type"]
|
|
48
|
+
self.fb_error_code = error_info["code"]
|
|
49
|
+
self.fb_error_subcode = error_info["error_subcode"]
|
|
50
|
+
self.fb_error_message = error_info["message"]
|
|
51
|
+
|
|
52
|
+
error_array = []
|
|
53
|
+
%w(type code error_subcode message).each do |key|
|
|
54
|
+
error_array << "#{key}: #{error_info[key]}" if error_info[key]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if error_array.empty?
|
|
58
|
+
message = self.response_body
|
|
59
|
+
else
|
|
60
|
+
message = error_array.join(', ')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
message += " [HTTP #{http_status}]" if http_status
|
|
64
|
+
|
|
65
|
+
super(message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Facebook returned an invalid response body
|
|
70
|
+
class BadFacebookResponse < APIError; end
|
|
71
|
+
|
|
72
|
+
# Facebook responded with an error while attempting to request an access token
|
|
73
|
+
class OAuthTokenRequestError < APIError; end
|
|
74
|
+
|
|
75
|
+
# Any error with a 5xx HTTP status code
|
|
76
|
+
class ServerError < APIError; end
|
|
77
|
+
|
|
78
|
+
# Any error with a 4xx HTTP status code
|
|
79
|
+
class ClientError < APIError; end
|
|
80
|
+
|
|
81
|
+
# All graph API authentication failures.
|
|
82
|
+
class AuthenticationError < ClientError; end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
data/lib/koala/oauth.rb
CHANGED
|
@@ -139,7 +139,7 @@ module Koala
|
|
|
139
139
|
# @param code (see #url_for_access_token)
|
|
140
140
|
# @param options any additional parameters to send to Facebook when redeeming the token
|
|
141
141
|
#
|
|
142
|
-
# @raise Koala::Facebook::
|
|
142
|
+
# @raise Koala::Facebook::OAuthTokenRequestError if Facebook returns an error response
|
|
143
143
|
#
|
|
144
144
|
# @return a hash of the access token info returned by Facebook (token, expiration, etc.)
|
|
145
145
|
def get_access_token_info(code, options = {})
|
|
@@ -221,21 +221,21 @@ module Koala
|
|
|
221
221
|
#
|
|
222
222
|
# @param input the signed request from Facebook
|
|
223
223
|
#
|
|
224
|
-
# @raise
|
|
224
|
+
# @raise OAuthSignatureError if the signature is incomplete, invalid, or using an unsupported algorithm
|
|
225
225
|
#
|
|
226
226
|
# @return a hash of the validated request information
|
|
227
227
|
def parse_signed_request(input)
|
|
228
228
|
encoded_sig, encoded_envelope = input.split('.', 2)
|
|
229
|
-
raise '
|
|
229
|
+
raise OAuthSignatureError, 'Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
|
|
230
230
|
|
|
231
231
|
signature = base64_url_decode(encoded_sig).unpack("H*").first
|
|
232
232
|
envelope = MultiJson.load(base64_url_decode(encoded_envelope))
|
|
233
233
|
|
|
234
|
-
raise "
|
|
234
|
+
raise OAuthSignatureError, "Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
|
|
235
235
|
|
|
236
236
|
# now see if the signature is valid (digest, key, data)
|
|
237
237
|
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
|
|
238
|
-
raise '
|
|
238
|
+
raise OAuthSignatureError, 'Invalid signature' if (signature != hmac)
|
|
239
239
|
|
|
240
240
|
envelope
|
|
241
241
|
end
|
|
@@ -254,10 +254,7 @@ module Koala
|
|
|
254
254
|
|
|
255
255
|
# Facebook returns an empty body in certain error conditions
|
|
256
256
|
if response == ""
|
|
257
|
-
raise
|
|
258
|
-
"type" => "ArgumentError",
|
|
259
|
-
"message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
|
|
260
|
-
})
|
|
257
|
+
raise BadFacebookResponse.new(200, '', "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
|
|
261
258
|
end
|
|
262
259
|
|
|
263
260
|
MultiJson.load(response)
|
|
@@ -282,13 +279,8 @@ module Koala
|
|
|
282
279
|
|
|
283
280
|
def get_token_from_server(args, post = false, options = {})
|
|
284
281
|
# fetch the result from Facebook's servers
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
# if we have an error, parse the error JSON and raise an error
|
|
288
|
-
raise APIError.new((MultiJson.load(result)["error"] rescue nil) || {}) if result =~ /error/
|
|
289
|
-
|
|
290
|
-
# otherwise, parse the access token
|
|
291
|
-
parse_access_token(result)
|
|
282
|
+
response = fetch_token_string(args, post, "access_token", options)
|
|
283
|
+
parse_access_token(response)
|
|
292
284
|
end
|
|
293
285
|
|
|
294
286
|
def parse_access_token(response_text)
|
|
@@ -318,9 +310,12 @@ module Koala
|
|
|
318
310
|
if code = components["code"]
|
|
319
311
|
begin
|
|
320
312
|
token_info = get_access_token_info(code, :redirect_uri => '')
|
|
321
|
-
rescue Koala::Facebook::
|
|
322
|
-
|
|
323
|
-
|
|
313
|
+
rescue Koala::Facebook::OAuthTokenRequestError => err
|
|
314
|
+
if err.fb_error_type == 'OAuthException' && err.fb_error_message =~ /Code was invalid or expired/
|
|
315
|
+
return nil
|
|
316
|
+
else
|
|
317
|
+
raise
|
|
318
|
+
end
|
|
324
319
|
end
|
|
325
320
|
|
|
326
321
|
components.merge(token_info) if token_info
|
|
@@ -331,10 +326,15 @@ module Koala
|
|
|
331
326
|
end
|
|
332
327
|
|
|
333
328
|
def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
|
|
334
|
-
Koala.make_request("/oauth/#{endpoint}", {
|
|
329
|
+
response = Koala.make_request("/oauth/#{endpoint}", {
|
|
335
330
|
:client_id => @app_id,
|
|
336
331
|
:client_secret => @app_secret
|
|
337
|
-
}.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options))
|
|
332
|
+
}.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options))
|
|
333
|
+
|
|
334
|
+
raise ServerError.new(response.status, response.body) if response.status >= 500
|
|
335
|
+
raise OAuthTokenRequestError.new(response.status, response.body) if response.status >= 400
|
|
336
|
+
|
|
337
|
+
response.body
|
|
338
338
|
end
|
|
339
339
|
|
|
340
340
|
# base 64
|
|
@@ -7,19 +7,19 @@ module Koala
|
|
|
7
7
|
# @note: to subscribe to real-time updates, you must have an application access token
|
|
8
8
|
# or provide the app secret when initializing your RealtimeUpdates object.
|
|
9
9
|
|
|
10
|
-
# The application API interface used to communicate with Facebook.
|
|
11
|
-
# @return [Koala::Facebook::API]
|
|
10
|
+
# The application API interface used to communicate with Facebook.
|
|
11
|
+
# @return [Koala::Facebook::API]
|
|
12
12
|
attr_reader :api
|
|
13
13
|
attr_reader :app_id, :app_access_token, :secret
|
|
14
14
|
|
|
15
|
-
# Create a new RealtimeUpdates instance.
|
|
16
|
-
# If you don't have your app's access token, provide the app's secret and
|
|
15
|
+
# Create a new RealtimeUpdates instance.
|
|
16
|
+
# If you don't have your app's access token, provide the app's secret and
|
|
17
17
|
# Koala will make a request to Facebook for the appropriate token.
|
|
18
|
-
#
|
|
18
|
+
#
|
|
19
19
|
# @param options initialization options.
|
|
20
20
|
# @option options :app_id the application's ID.
|
|
21
21
|
# @option options :app_access_token an application access token, if known.
|
|
22
|
-
# @option options :secret the application's secret.
|
|
22
|
+
# @option options :secret the application's secret.
|
|
23
23
|
#
|
|
24
24
|
# @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
|
|
25
25
|
def initialize(options = {})
|
|
@@ -39,7 +39,7 @@ module Koala
|
|
|
39
39
|
@api = API.new(@app_access_token)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
# Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
|
|
42
|
+
# Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
|
|
43
43
|
# See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
|
|
44
44
|
# for more information on what objects and fields you can register for.
|
|
45
45
|
#
|
|
@@ -48,11 +48,11 @@ module Koala
|
|
|
48
48
|
# @param object a Facebook ID (name or number)
|
|
49
49
|
# @param fields the fields you want your app to be updated about
|
|
50
50
|
# @param callback_url the URL Facebook should ping when an update is available
|
|
51
|
-
# @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
|
|
51
|
+
# @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
|
|
52
52
|
# (see the docs for more information)
|
|
53
53
|
# @param options (see Koala::HTTPService.make_request)
|
|
54
54
|
#
|
|
55
|
-
# @
|
|
55
|
+
# @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
|
|
56
56
|
def subscribe(object, fields, callback_url, verify_token, options = {})
|
|
57
57
|
args = {
|
|
58
58
|
:object => object,
|
|
@@ -60,23 +60,23 @@ module Koala
|
|
|
60
60
|
:callback_url => callback_url,
|
|
61
61
|
}.merge(verify_token ? {:verify_token => verify_token} : {})
|
|
62
62
|
# a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
|
|
63
|
-
@api.graph_call(subscription_path, args, 'post', options
|
|
63
|
+
@api.graph_call(subscription_path, args, 'post', options)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
# Unsubscribe from updates for a particular object or from updates.
|
|
66
|
+
# Unsubscribe from updates for a particular object or from updates.
|
|
67
67
|
#
|
|
68
|
-
# @param object the object whose subscriptions to delete.
|
|
68
|
+
# @param object the object whose subscriptions to delete.
|
|
69
69
|
# If no object is provided, all subscriptions will be removed.
|
|
70
70
|
# @param options (see Koala::HTTPService.make_request)
|
|
71
71
|
#
|
|
72
|
-
# @
|
|
72
|
+
# @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
|
|
73
73
|
def unsubscribe(object = nil, options = {})
|
|
74
|
-
@api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options
|
|
74
|
+
@api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# List all active subscriptions for this application.
|
|
78
|
-
#
|
|
79
|
-
# @param options (see Koala::HTTPService.make_request)
|
|
78
|
+
#
|
|
79
|
+
# @param options (see Koala::HTTPService.make_request)
|
|
80
80
|
#
|
|
81
81
|
# @return [Array] a list of active subscriptions
|
|
82
82
|
def list_subscriptions(options = {})
|
|
@@ -89,12 +89,12 @@ module Koala
|
|
|
89
89
|
#
|
|
90
90
|
# @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
|
|
91
91
|
# @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
|
|
92
|
-
#
|
|
92
|
+
#
|
|
93
93
|
# @yield verify_token if you need to compute the verification token
|
|
94
94
|
# (for instance, if your callback URL includes a record ID, which you look up
|
|
95
|
-
# and use to calculate a hash), you can pass meet_challenge a block, which
|
|
95
|
+
# and use to calculate a hash), you can pass meet_challenge a block, which
|
|
96
96
|
# will receive the verify_token received back from Facebook.
|
|
97
|
-
#
|
|
97
|
+
#
|
|
98
98
|
# @return the challenge string to be sent back to Facebook, or false if the request is invalid.
|
|
99
99
|
def self.meet_challenge(params, verify_token = nil, &verification_block)
|
|
100
100
|
if params["hub.mode"] == "subscribe" &&
|
|
@@ -111,12 +111,33 @@ module Koala
|
|
|
111
111
|
false
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
|
-
|
|
114
|
+
|
|
115
|
+
# Public: As a security measure, all updates from facebook are signed using
|
|
116
|
+
# X-Hub-Signature: sha1=XXXX where XXX is the sha1 of the json payload
|
|
117
|
+
# using your application secret as the key.
|
|
118
|
+
#
|
|
119
|
+
# Example:
|
|
120
|
+
# # in Rails controller
|
|
121
|
+
# # @oauth being a previously defined Koala::Facebook::OAuth instance
|
|
122
|
+
# def receive_update
|
|
123
|
+
# if @oauth.validate_update(request.body, headers)
|
|
124
|
+
# ...
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
def validate_update(body, headers)
|
|
128
|
+
if request_signature = headers['X-Hub-Signature'] || headers['HTTP_X_HUB_SIGNATURE'] and
|
|
129
|
+
signature_parts = request_signature.split("sha1=")
|
|
130
|
+
request_signature = signature_parts[1]
|
|
131
|
+
calculated_signature = OpenSSL::HMAC.hexdigest('sha1', @secret, body)
|
|
132
|
+
calculated_signature == request_signature
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
115
136
|
# The Facebook subscription management URL for your application.
|
|
116
137
|
def subscription_path
|
|
117
138
|
@subscription_path ||= "#{@app_id}/subscriptions"
|
|
118
139
|
end
|
|
119
|
-
|
|
140
|
+
|
|
120
141
|
# @private
|
|
121
142
|
def graph_api
|
|
122
143
|
Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
|
data/lib/koala/version.rb
CHANGED
data/lib/koala.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'digest/md5'
|
|
|
3
3
|
require 'multi_json'
|
|
4
4
|
|
|
5
5
|
# include koala modules
|
|
6
|
+
require 'koala/errors'
|
|
6
7
|
require 'koala/api'
|
|
7
8
|
require 'koala/oauth'
|
|
8
9
|
require 'koala/realtime_updates'
|
|
@@ -20,8 +21,6 @@ module Koala
|
|
|
20
21
|
# See http://github.com/arsduo/koala/wiki for a general introduction to Koala
|
|
21
22
|
# and the Graph API.
|
|
22
23
|
|
|
23
|
-
class KoalaError < StandardError; end
|
|
24
|
-
|
|
25
24
|
# Making HTTP requests
|
|
26
25
|
class << self
|
|
27
26
|
# Control which HTTP service framework Koala uses.
|
data/readme.md
CHANGED
|
@@ -13,52 +13,78 @@ Installation
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
Easy:
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
```bash
|
|
17
|
+
[sudo|rvm] gem install koala
|
|
18
|
+
```
|
|
18
19
|
|
|
19
20
|
Or in Bundler:
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
```ruby
|
|
22
|
+
gem "koala"
|
|
23
|
+
```
|
|
22
24
|
|
|
23
25
|
Graph API
|
|
24
26
|
----
|
|
25
|
-
The Graph API is the simple, slick new interface to Facebook's data.
|
|
27
|
+
The Graph API is the simple, slick new interface to Facebook's data.
|
|
28
|
+
Using it with Koala is quite straightforward. First, you'll need an access token, which you can get through
|
|
29
|
+
Facebook's [Graph API Explorer](https://developers.facebook.com/tools/explorer) (click on 'Get Access Token').
|
|
30
|
+
Then, go exploring:
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
```ruby
|
|
33
|
+
@graph = Koala::Facebook::API.new(oauth_access_token)
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
profile = @graph.get_object("me")
|
|
36
|
+
friends = @graph.get_connections("me", "friends")
|
|
37
|
+
@graph.put_connections("me", "feed", :message => "I am writing on my wall!")
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
# three-part queries are easy too!
|
|
40
|
+
@graph.get_connections("me", "mutualfriends/#{friend_id}")
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
# you can even use the new Timeline API
|
|
43
|
+
# see https://developers.facebook.com/docs/beta/opengraph/tutorial/
|
|
44
|
+
@graph.put_connections("me", "namespace:action", :object => object_url)
|
|
45
|
+
```
|
|
40
46
|
|
|
41
47
|
The response of most requests is the JSON data returned from the Facebook servers as a Hash.
|
|
42
48
|
|
|
43
|
-
When retrieving data that returns an array of results (for example, when calling API#get_connections or API#search)
|
|
49
|
+
When retrieving data that returns an array of results (for example, when calling `API#get_connections` or `API#search`)
|
|
50
|
+
a GraphCollection object will be returned, which makes it easy to page through the results:
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
```ruby
|
|
53
|
+
# Returns the feed items for the currently logged-in user as a GraphCollection
|
|
54
|
+
feed = @graph.get_connections("me", "feed")
|
|
55
|
+
feed.each {|f| do_something_with_item(f) } # it's a subclass of Array
|
|
56
|
+
next_feed = feed.next_page
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
# You can also get an array describing the URL for the next page: [path, arguments]
|
|
59
|
+
# This is useful for storing page state across multiple browser requests
|
|
60
|
+
next_page_params = feed.next_page_params
|
|
61
|
+
page = @graph.get_page(next_page_params)
|
|
62
|
+
```
|
|
54
63
|
|
|
55
64
|
You can also make multiple calls at once using Facebook's batch API:
|
|
65
|
+
```ruby
|
|
66
|
+
# Returns an array of results as if they were called non-batch
|
|
67
|
+
@graph.batch do |batch_api|
|
|
68
|
+
batch_api.get_object('me')
|
|
69
|
+
batch_api.put_wall_post('Making a post in a batch.')
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You can pass a "post-processing" block to each of Koala's Graph API methods. This is handy for two reasons:
|
|
74
|
+
|
|
75
|
+
1. You can modify the result returned by the Graph API method:
|
|
76
|
+
|
|
77
|
+
education = @graph.get_object("me") { |data| data['education'] }
|
|
78
|
+
# returned value only contains the "education" portion of the profile
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
2. You can consume the data in place which is particularly useful in the batch case, so you don't have to pull
|
|
81
|
+
the results apart from a long list of array entries:
|
|
82
|
+
|
|
83
|
+
@graph.batch do |batch_api|
|
|
84
|
+
# Assuming you have database fields "about_me" and "photos"
|
|
85
|
+
batch_api.get_object('me') {|me| self.about_me = me }
|
|
86
|
+
batch_api.get_connections('me', 'photos') {|photos| self.photos = photos }
|
|
87
|
+
end
|
|
62
88
|
|
|
63
89
|
Check out the wiki for more details and examples.
|
|
64
90
|
|
|
@@ -67,55 +93,56 @@ The REST API
|
|
|
67
93
|
Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls that can't yet be done via the Graph.
|
|
68
94
|
|
|
69
95
|
Fortunately, Koala supports the REST API using the very same interface; to use this, instantiate an API:
|
|
96
|
+
```ruby
|
|
97
|
+
@rest = Koala::Facebook::API.new(oauth_access_token)
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@rest.fql_multiquery(fql_query_hash) # convenience method
|
|
76
|
-
@rest.rest_call("stream.publish", arguments_hash) # generic version
|
|
99
|
+
@rest.fql_query(my_fql_query) # convenience method
|
|
100
|
+
@rest.fql_multiquery(fql_query_hash) # convenience method
|
|
101
|
+
@rest.rest_call("stream.publish", arguments_hash) # generic version
|
|
102
|
+
```
|
|
77
103
|
|
|
78
104
|
Of course, you can use the Graph API methods on the same object -- the power of two APIs right in the palm of your hand.
|
|
105
|
+
```ruby
|
|
106
|
+
@api = Koala::Facebook::API.new(oauth_access_token)
|
|
79
107
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fql = @api.fql_query(my_fql_query)
|
|
85
|
-
@api.put_wall_post(process_result(fql))
|
|
86
|
-
|
|
108
|
+
@api = Koala::Facebook::API.new(oauth_access_token)
|
|
109
|
+
fql = @api.fql_query(my_fql_query)
|
|
110
|
+
@api.put_wall_post(process_result(fql))
|
|
111
|
+
```
|
|
87
112
|
|
|
88
113
|
OAuth
|
|
89
114
|
-----
|
|
90
115
|
You can use the Graph and REST APIs without an OAuth access token, but the real magic happens when you provide Facebook an OAuth token to prove you're authenticated. Koala provides an OAuth class to make that process easy:
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
```ruby
|
|
117
|
+
@oauth = Koala::Facebook::OAuth.new(app_id, app_secret, callback_url)
|
|
118
|
+
```
|
|
93
119
|
|
|
94
120
|
If your application uses Koala and the Facebook [JavaScript SDK](http://github.com/facebook/facebook-js-sdk) (formerly Facebook Connect), you can use the OAuth class to parse the cookies:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
121
|
+
```ruby
|
|
122
|
+
@oauth.get_user_from_cookies(cookies) # gets the user's ID
|
|
123
|
+
@oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
|
|
124
|
+
```
|
|
99
125
|
And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
126
|
+
```ruby
|
|
127
|
+
# generate authenticating URL
|
|
128
|
+
@oauth.url_for_oauth_code
|
|
129
|
+
# fetch the access token once you have the code
|
|
130
|
+
@oauth.get_access_token(code)
|
|
131
|
+
```
|
|
105
132
|
|
|
106
133
|
You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
134
|
+
```ruby
|
|
135
|
+
@oauth.get_app_access_token
|
|
136
|
+
```
|
|
110
137
|
For those building apps on Facebook, parsing signed requests is simple:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
```ruby
|
|
139
|
+
@oauth.parse_signed_request(signed_request_string)
|
|
140
|
+
```
|
|
114
141
|
Or, if for some horrible reason, you're still using session keys, despair not! It's easy to turn them into shiny, modern OAuth tokens:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
142
|
+
```ruby
|
|
143
|
+
@oauth.get_token_from_session_key(session_key)
|
|
144
|
+
@oauth.get_tokens_from_session_keys(array_of_session_keys)
|
|
145
|
+
```
|
|
119
146
|
That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
|
|
120
147
|
|
|
121
148
|
Real-time Updates
|
|
@@ -123,50 +150,50 @@ Real-time Updates
|
|
|
123
150
|
Sometimes, reaching out to Facebook is a pain -- let it reach out to you instead. The Graph API allows your application to subscribe to real-time updates for certain objects in the graph; check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details on what objects you can subscribe to and what limitations may apply.
|
|
124
151
|
|
|
125
152
|
Koala makes it easy to interact with your applications using the RealtimeUpdates class:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
```ruby
|
|
154
|
+
@updates = Koala::Facebook::RealtimeUpdates.new(:app_id => app_id, :secret => secret)
|
|
155
|
+
```
|
|
129
156
|
You can do just about anything with your real-time update subscriptions using the RealtimeUpdates class:
|
|
157
|
+
```ruby
|
|
158
|
+
# Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
|
|
159
|
+
@updates.subscribe("user", "first_name, last_name", callback_url, verify_token)
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# Get an array of your current subscriptions (one hash for each object you've subscribed to)
|
|
135
|
-
@updates.list_subscriptions
|
|
136
|
-
|
|
137
|
-
# Unsubscribe from updates for an object
|
|
138
|
-
@updates.unsubscribe("user")
|
|
161
|
+
# Get an array of your current subscriptions (one hash for each object you've subscribed to)
|
|
162
|
+
@updates.list_subscriptions
|
|
139
163
|
|
|
164
|
+
# Unsubscribe from updates for an object
|
|
165
|
+
@updates.unsubscribe("user")
|
|
166
|
+
```
|
|
140
167
|
And to top it all off, RealtimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
```ruby
|
|
169
|
+
# Returns the hub.challenge parameter in params if the verify token in params matches verify_token
|
|
170
|
+
Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
|
|
171
|
+
```
|
|
145
172
|
For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
|
|
146
173
|
|
|
147
174
|
Test Users
|
|
148
175
|
-----
|
|
149
176
|
|
|
150
177
|
We also support the test users API, allowing you to conjure up fake users and command them to do your bidding using the Graph or REST API:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
```ruby
|
|
179
|
+
@test_users = Koala::Facebook::TestUsers.new(:app_id => id, :secret => secret)
|
|
180
|
+
user = @test_users.create(is_app_installed, desired_permissions)
|
|
181
|
+
user_graph_api = Koala::Facebook::API.new(user["access_token"])
|
|
182
|
+
# or, if you want to make a whole community:
|
|
183
|
+
@test_users.create_network(network_size, is_app_installed, common_permissions)
|
|
184
|
+
```
|
|
158
185
|
Talking to Facebook
|
|
159
186
|
-----
|
|
160
187
|
|
|
161
188
|
Koala uses Faraday to make HTTP requests, which means you have complete control over how your app makes HTTP requests to Facebook. You can set Faraday options globally or pass them in on a per-request (or both):
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
```ruby
|
|
190
|
+
# Set an SSL certificate to avoid Net::HTTP errors
|
|
191
|
+
Koala.http_service.http_options = {
|
|
192
|
+
:ssl => { :ca_path => "/etc/ssl/certs" }
|
|
193
|
+
}
|
|
194
|
+
# or on a per-request basis
|
|
195
|
+
@api.get_object(id, args_hash, { :timeout => 10 })
|
|
196
|
+
```
|
|
170
197
|
The <a href="https://github.com/arsduo/koala/wiki/HTTP-Services">HTTP Services wiki page</a> has more information on what options are available, as well as on how to configure your own Faraday middleware stack (for instance, to implement request logging).
|
|
171
198
|
|
|
172
199
|
See examples, ask questions
|
|
@@ -184,16 +211,16 @@ Testing
|
|
|
184
211
|
-----
|
|
185
212
|
|
|
186
213
|
Unit tests are provided for all of Koala's methods. By default, these tests run against mock responses and hence are ready out of the box:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
```bash
|
|
215
|
+
# From anywhere in the project directory:
|
|
216
|
+
bundle exec rake spec
|
|
217
|
+
```
|
|
191
218
|
|
|
192
219
|
You can also run live tests against Facebook's servers:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
```bash
|
|
221
|
+
# Again from anywhere in the project directory:
|
|
222
|
+
LIVE=true bundle exec rake spec
|
|
223
|
+
# you can also test against Facebook's beta tier
|
|
224
|
+
LIVE=true BETA=true bundle exec rake spec
|
|
225
|
+
```
|
|
199
226
|
By default, the live tests are run against test users, so you can run them as frequently as you want. If you want to run them against a real user, however, you can fill in the OAuth token, code, and access\_token values in spec/fixtures/facebook_data.yml. See the wiki for more details.
|
data/spec/cases/api_spec.rb
CHANGED
|
@@ -65,15 +65,15 @@ describe "Koala::Facebook::API" do
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
it "executes an error checking block if provided" do
|
|
68
|
-
|
|
69
|
-
Koala.stub(:make_request).and_return(
|
|
68
|
+
response = Koala::HTTPService::Response.new(200, '{}', {})
|
|
69
|
+
Koala.stub(:make_request).and_return(response)
|
|
70
70
|
|
|
71
71
|
yield_test = mock('Yield Tester')
|
|
72
72
|
yield_test.should_receive(:pass)
|
|
73
73
|
|
|
74
74
|
@service.api('anything', {}, "get") do |arg|
|
|
75
75
|
yield_test.pass
|
|
76
|
-
arg.should ==
|
|
76
|
+
arg.should == response
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|