tyler_koala 1.2.0beta
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +12 -0
- data/.gitignore +5 -0
- data/.travis.yml +9 -0
- data/CHANGELOG +185 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/Manifest +39 -0
- data/Rakefile +16 -0
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +50 -0
- data/lib/koala.rb +119 -0
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +281 -0
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_service.rb +161 -0
- data/lib/koala/oauth.rb +181 -0
- data/lib/koala/realtime_updates.rb +89 -0
- data/lib/koala/rest_api.rb +95 -0
- data/lib/koala/test_users.rb +102 -0
- data/lib/koala/uploadable_io.rb +180 -0
- data/lib/koala/utils.rb +7 -0
- data/readme.md +160 -0
- data/spec/cases/api_base_spec.rb +101 -0
- data/spec/cases/error_spec.rb +30 -0
- data/spec/cases/graph_and_rest_api_spec.rb +48 -0
- data/spec/cases/graph_api_batch_spec.rb +600 -0
- data/spec/cases/graph_api_spec.rb +42 -0
- data/spec/cases/http_service_spec.rb +420 -0
- data/spec/cases/koala_spec.rb +21 -0
- data/spec/cases/oauth_spec.rb +428 -0
- data/spec/cases/realtime_updates_spec.rb +198 -0
- data/spec/cases/rest_api_spec.rb +41 -0
- data/spec/cases/test_users_spec.rb +281 -0
- data/spec/cases/uploadable_io_spec.rb +206 -0
- data/spec/cases/utils_spec.rb +8 -0
- data/spec/fixtures/beach.jpg +0 -0
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/facebook_data.yml +61 -0
- data/spec/fixtures/mock_facebook_responses.yml +439 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/graph_api_shared_examples.rb +502 -0
- data/spec/support/json_testing_fix.rb +42 -0
- data/spec/support/koala_test.rb +163 -0
- data/spec/support/mock_http_service.rb +98 -0
- data/spec/support/ordered_hash.rb +205 -0
- data/spec/support/rest_api_shared_examples.rb +285 -0
- data/spec/support/uploadable_io_shared_examples.rb +70 -0
- metadata +221 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_stack'
|
3
|
+
|
4
|
+
module Koala
|
5
|
+
class Response
|
6
|
+
attr_reader :status, :body, :headers
|
7
|
+
def initialize(status, body, headers)
|
8
|
+
@status = status
|
9
|
+
@body = body
|
10
|
+
@headers = headers
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module HTTPService
|
15
|
+
# common functionality for all HTTP services
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :faraday_middleware, :faraday_options
|
19
|
+
end
|
20
|
+
|
21
|
+
@faraday_options ||= {}
|
22
|
+
|
23
|
+
DEFAULT_MIDDLEWARE = Proc.new do |builder|
|
24
|
+
builder.request :multipart
|
25
|
+
builder.request :url_encoded
|
26
|
+
builder.adapter Faraday.default_adapter
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.server(options = {})
|
30
|
+
server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
|
31
|
+
server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
|
32
|
+
"#{options[:use_ssl] ? "https" : "http"}://#{options[:beta] ? "beta." : ""}#{server}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.make_request(path, args, verb, options = {})
|
36
|
+
# if the verb isn't get or post, send it as a post argument
|
37
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
38
|
+
|
39
|
+
# turn all the keys to strings (Faraday has issues with symbols under 1.8.7) and resolve UploadableIOs
|
40
|
+
params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last.is_a?(UploadableIO) ? kv.last.to_upload_io : kv.last; hash}
|
41
|
+
|
42
|
+
# figure out our options for this request
|
43
|
+
http_options = {:params => (verb == "get" ? params : {})}.merge(faraday_options || {}).merge(process_options(options))
|
44
|
+
http_options[:use_ssl] = true if args["access_token"] # require http if there's a token
|
45
|
+
|
46
|
+
# set up our Faraday connection
|
47
|
+
# we have to manually assign params to the URL or the
|
48
|
+
conn = Faraday.new(server(http_options), http_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
|
49
|
+
|
50
|
+
response = conn.send(verb, path, (verb == "post" ? params : {}))
|
51
|
+
Koala::Response.new(response.status.to_i, response.body, response.headers)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.encode_params(param_hash)
|
55
|
+
# unfortunately, we can't use to_query because that's Rails, not Ruby
|
56
|
+
# if no hash (e.g. no auth token) return empty string
|
57
|
+
# this is used mainly by the Batch API nowadays
|
58
|
+
((param_hash || {}).collect do |key_and_value|
|
59
|
+
key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
|
60
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
61
|
+
end).join("&")
|
62
|
+
end
|
63
|
+
|
64
|
+
# deprecations
|
65
|
+
# not elegant or compact code, but temporary
|
66
|
+
|
67
|
+
def self.always_use_ssl
|
68
|
+
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.faraday_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
69
|
+
faraday_options[:use_ssl]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.always_use_ssl=(value)
|
73
|
+
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.faraday_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
74
|
+
faraday_options[:use_ssl] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.timeout
|
78
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
|
79
|
+
faraday_options[:timeout]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.timeout=(value)
|
83
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
|
84
|
+
faraday_options[:timeout] = value
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.timeout
|
88
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
|
89
|
+
faraday_options[:timeout]
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.timeout=(value)
|
93
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
|
94
|
+
faraday_options[:timeout] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.proxy
|
98
|
+
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.faraday_options[:proxy]; .proxy will be removed in a future version.")
|
99
|
+
faraday_options[:proxy]
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.proxy=(value)
|
103
|
+
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.faraday_options[:proxy]; .proxy will be removed in a future version.")
|
104
|
+
faraday_options[:proxy] = value
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.ca_path
|
108
|
+
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
109
|
+
(faraday_options[:ssl] || {})[:ca_path]
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.ca_path=(value)
|
113
|
+
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
114
|
+
(faraday_options[:ssl] ||= {})[:ca_path] = value
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.ca_file
|
118
|
+
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
119
|
+
(faraday_options[:ssl] || {})[:ca_file]
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.ca_file=(value)
|
123
|
+
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
124
|
+
(faraday_options[:ssl] ||= {})[:ca_file] = value
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.verify_mode
|
128
|
+
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.faraday_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
129
|
+
(faraday_options[:ssl] || {})[:verify_mode]
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.verify_mode=(value)
|
133
|
+
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.faraday_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
134
|
+
(faraday_options[:ssl] ||= {})[:verify_mode] = value
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.process_options(options)
|
138
|
+
if typhoeus_options = options.delete(:typhoeus_options)
|
139
|
+
Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
|
140
|
+
options = options.merge(typhoeus_options)
|
141
|
+
end
|
142
|
+
|
143
|
+
if ca_file = options.delete(:ca_file)
|
144
|
+
Koala::Utils.deprecate("http_options[:ca_file] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_file]. Support for this key will be removed in a future version.")
|
145
|
+
(options[:ssl] ||= {})[:ca_file] = ca_file
|
146
|
+
end
|
147
|
+
|
148
|
+
if ca_path = options.delete(:ca_path)
|
149
|
+
Koala::Utils.deprecate("http_options[:ca_path] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_path]. Support for this key will be removed in a future version.")
|
150
|
+
(options[:ssl] ||= {})[:ca_path] = ca_path
|
151
|
+
end
|
152
|
+
|
153
|
+
if verify_mode = options.delete(:verify_mode)
|
154
|
+
Koala::Utils.deprecate("http_options[:verify_mode] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:verify_mode]. Support for this key will be removed in a future version.")
|
155
|
+
(options[:ssl] ||= {})[:verify_mode] = verify_mode
|
156
|
+
end
|
157
|
+
|
158
|
+
options
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/koala/oauth.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
class OAuth
|
4
|
+
attr_reader :app_id, :app_secret, :oauth_callback_url
|
5
|
+
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
6
|
+
@app_id = app_id
|
7
|
+
@app_secret = app_secret
|
8
|
+
@oauth_callback_url = oauth_callback_url
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_user_info_from_cookie(cookie_hash)
|
12
|
+
# Parses the cookie set by the official Facebook JavaScript SDK.
|
13
|
+
#
|
14
|
+
# cookies should be a Hash, like the one Rails provides
|
15
|
+
#
|
16
|
+
# If the user is logged in via Facebook, we return a dictionary with the
|
17
|
+
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
18
|
+
# and the latter can be used to make authenticated requests to the Graph API.
|
19
|
+
# If the user is not logged in, we return None.
|
20
|
+
#
|
21
|
+
# Download the official Facebook JavaScript SDK at
|
22
|
+
# http://github.com/facebook/connect-js/. Read more about Facebook
|
23
|
+
# authentication at http://developers.facebook.com/docs/authentication/.
|
24
|
+
|
25
|
+
if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
|
26
|
+
# remove the opening/closing quote
|
27
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
28
|
+
|
29
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
30
|
+
components = {}
|
31
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
32
|
+
|
33
|
+
# generate the signature and make sure it matches what we expect
|
34
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
35
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
36
|
+
sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
|
40
|
+
|
41
|
+
def get_user_from_cookie(cookies)
|
42
|
+
if info = get_user_info_from_cookies(cookies)
|
43
|
+
string = info["uid"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias_method :get_user_from_cookies, :get_user_from_cookie
|
47
|
+
|
48
|
+
# URLs
|
49
|
+
|
50
|
+
def url_for_oauth_code(options = {})
|
51
|
+
# for permissions, see http://developers.facebook.com/docs/authentication/permissions
|
52
|
+
permissions = options[:permissions]
|
53
|
+
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
54
|
+
display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
|
55
|
+
|
56
|
+
callback = options[:callback] || @oauth_callback_url
|
57
|
+
raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
|
58
|
+
|
59
|
+
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
60
|
+
"https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def url_for_access_token(code, options = {})
|
64
|
+
# Creates the URL for the token corresponding to a given code generated by Facebook
|
65
|
+
callback = options[:callback] || @oauth_callback_url
|
66
|
+
raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
|
67
|
+
"https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_access_token_info(code, options = {})
|
71
|
+
# convenience method to get a parsed token from Facebook for a given code
|
72
|
+
# should this require an OAuth callback URL?
|
73
|
+
get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_access_token(code, options = {})
|
77
|
+
# upstream methods will throw errors if needed
|
78
|
+
if info = get_access_token_info(code, options)
|
79
|
+
string = info["access_token"]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_app_access_token_info(options = {})
|
84
|
+
# convenience method to get a the application's sessionless access token
|
85
|
+
get_token_from_server({:type => 'client_cred'}, true, options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_app_access_token(options = {})
|
89
|
+
if info = get_app_access_token_info(options)
|
90
|
+
string = info["access_token"]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Originally provided directly by Facebook, however this has changed
|
95
|
+
# as their concept of crypto changed. For historic purposes, this is their proposal:
|
96
|
+
# https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
|
97
|
+
# Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
|
98
|
+
# for a more accurate reference implementation strategy.
|
99
|
+
def parse_signed_request(input)
|
100
|
+
encoded_sig, encoded_envelope = input.split('.', 2)
|
101
|
+
signature = base64_url_decode(encoded_sig).unpack("H*").first
|
102
|
+
envelope = MultiJson.decode(base64_url_decode(encoded_envelope))
|
103
|
+
|
104
|
+
raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
|
105
|
+
|
106
|
+
# now see if the signature is valid (digest, key, data)
|
107
|
+
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope.tr("-_", "+/"))
|
108
|
+
raise 'SignedRequest: Invalid signature' if (signature != hmac)
|
109
|
+
|
110
|
+
return envelope
|
111
|
+
end
|
112
|
+
|
113
|
+
# from session keys
|
114
|
+
def get_token_info_from_session_keys(sessions, options = {})
|
115
|
+
# fetch the OAuth tokens from Facebook
|
116
|
+
response = fetch_token_string({
|
117
|
+
:type => 'client_cred',
|
118
|
+
:sessions => sessions.join(",")
|
119
|
+
}, true, "exchange_sessions", options)
|
120
|
+
|
121
|
+
# Facebook returns an empty body in certain error conditions
|
122
|
+
if response == ""
|
123
|
+
raise APIError.new({
|
124
|
+
"type" => "ArgumentError",
|
125
|
+
"message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
|
126
|
+
})
|
127
|
+
end
|
128
|
+
|
129
|
+
MultiJson.decode(response)
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_tokens_from_session_keys(sessions, options = {})
|
133
|
+
# get the original hash results
|
134
|
+
results = get_token_info_from_session_keys(sessions, options)
|
135
|
+
# now recollect them as just the access tokens
|
136
|
+
results.collect { |r| r ? r["access_token"] : nil }
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_token_from_session_key(session, options = {})
|
140
|
+
# convenience method for a single key
|
141
|
+
# gets the overlaoded strings automatically
|
142
|
+
get_tokens_from_session_keys([session], options)[0]
|
143
|
+
end
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
def get_token_from_server(args, post = false, options = {})
|
148
|
+
# fetch the result from Facebook's servers
|
149
|
+
result = fetch_token_string(args, post, "access_token", options)
|
150
|
+
|
151
|
+
# if we have an error, parse the error JSON and raise an error
|
152
|
+
raise APIError.new((MultiJson.decode(result)["error"] rescue nil) || {}) if result =~ /error/
|
153
|
+
|
154
|
+
# otherwise, parse the access token
|
155
|
+
parse_access_token(result)
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_access_token(response_text)
|
159
|
+
components = response_text.split("&").inject({}) do |hash, bit|
|
160
|
+
key, value = bit.split("=")
|
161
|
+
hash.merge!(key => value)
|
162
|
+
end
|
163
|
+
components
|
164
|
+
end
|
165
|
+
|
166
|
+
def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
|
167
|
+
Koala.make_request("/oauth/#{endpoint}", {
|
168
|
+
:client_id => @app_id,
|
169
|
+
:client_secret => @app_secret
|
170
|
+
}.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
|
171
|
+
end
|
172
|
+
|
173
|
+
# base 64
|
174
|
+
# directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
|
175
|
+
def base64_url_decode(str)
|
176
|
+
str += '=' * (4 - str.length.modulo(4))
|
177
|
+
Base64.decode64(str.tr('-_', '+/'))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
module RealtimeUpdateMethods
|
4
|
+
# note: to subscribe to real-time updates, you must have an application access token
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
# make the attributes readable
|
8
|
+
base.class_eval do
|
9
|
+
attr_reader :api, :app_id, :app_access_token, :secret
|
10
|
+
|
11
|
+
# parses the challenge params and makes sure the call is legitimate
|
12
|
+
# returns the challenge string to be sent back to facebook if true
|
13
|
+
# returns false otherwise
|
14
|
+
# this is a class method, since you don't need to know anything about the app
|
15
|
+
# saves a potential trip fetching the app access token
|
16
|
+
def self.meet_challenge(params, verify_token = nil, &verification_block)
|
17
|
+
if params["hub.mode"] == "subscribe" &&
|
18
|
+
# you can make sure this is legitimate through two ways
|
19
|
+
# if your store the token across the calls, you can pass in the token value
|
20
|
+
# and we'll make sure it matches
|
21
|
+
(verify_token && params["hub.verify_token"] == verify_token) ||
|
22
|
+
# alternately, if you sent a specially-constructed value (such as a hash of various secret values)
|
23
|
+
# you can pass in a block, which we'll call with the verify_token sent by Facebook
|
24
|
+
# if it's legit, return anything that evaluates to true; otherwise, return nil or false
|
25
|
+
(verification_block && yield(params["hub.verify_token"]))
|
26
|
+
params["hub.challenge"]
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
@app_id = options[:app_id]
|
36
|
+
@app_access_token = options[:app_access_token]
|
37
|
+
@secret = options[:secret]
|
38
|
+
unless @app_id && (@app_access_token || @secret) # make sure we have what we need
|
39
|
+
raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
|
40
|
+
end
|
41
|
+
|
42
|
+
# fetch the access token if we're provided a secret
|
43
|
+
if @secret && !@app_access_token
|
44
|
+
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
45
|
+
@app_access_token = oauth.get_app_access_token
|
46
|
+
end
|
47
|
+
|
48
|
+
@graph_api = API.new(@app_access_token)
|
49
|
+
end
|
50
|
+
|
51
|
+
# subscribes for realtime updates
|
52
|
+
# your callback_url must be set up to handle the verification request or the subscription will not be set up
|
53
|
+
# http://developers.facebook.com/docs/api/realtime
|
54
|
+
def subscribe(object, fields, callback_url, verify_token)
|
55
|
+
args = {
|
56
|
+
:object => object,
|
57
|
+
:fields => fields,
|
58
|
+
:callback_url => callback_url,
|
59
|
+
:verify_token => verify_token
|
60
|
+
}
|
61
|
+
# a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
|
62
|
+
@graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
|
63
|
+
end
|
64
|
+
|
65
|
+
# removes subscription for object
|
66
|
+
# if object is nil, it will remove all subscriptions
|
67
|
+
def unsubscribe(object = nil)
|
68
|
+
args = {}
|
69
|
+
args[:object] = object if object
|
70
|
+
@graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
|
71
|
+
end
|
72
|
+
|
73
|
+
def list_subscriptions
|
74
|
+
@graph_api.graph_call(subscription_path)["data"]
|
75
|
+
end
|
76
|
+
|
77
|
+
def graph_api
|
78
|
+
Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
|
79
|
+
@api
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def subscription_path
|
85
|
+
@subscription_path ||= "#{@app_id}/subscriptions"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
REST_SERVER = "api.facebook.com"
|
4
|
+
|
5
|
+
module RestAPIMethods
|
6
|
+
def fql_query(fql, args = {}, options = {})
|
7
|
+
rest_call('fql.query', args.merge(:query => fql), options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fql_multiquery(queries = {}, args = {}, options = {})
|
11
|
+
if results = rest_call('fql.multiquery', args.merge(:queries => MultiJson.encode(queries)), options)
|
12
|
+
# simplify the multiquery result format
|
13
|
+
results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def rest_call(fb_method, args = {}, options = {}, method = "get")
|
18
|
+
options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(fb_method.to_s))
|
19
|
+
|
20
|
+
api("method/#{fb_method}", args.merge('format' => 'json'), method, options) do |response|
|
21
|
+
# check for REST API-specific errors
|
22
|
+
if response.is_a?(Hash) && response["error_code"]
|
23
|
+
raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# read-only methods for which we can use API-read
|
29
|
+
# taken directly from the FB PHP library (https://github.com/facebook/php-sdk/blob/master/src/facebook.php)
|
30
|
+
READ_ONLY_METHODS = [
|
31
|
+
'admin.getallocation',
|
32
|
+
'admin.getappproperties',
|
33
|
+
'admin.getbannedusers',
|
34
|
+
'admin.getlivestreamvialink',
|
35
|
+
'admin.getmetrics',
|
36
|
+
'admin.getrestrictioninfo',
|
37
|
+
'application.getpublicinfo',
|
38
|
+
'auth.getapppublickey',
|
39
|
+
'auth.getsession',
|
40
|
+
'auth.getsignedpublicsessiondata',
|
41
|
+
'comments.get',
|
42
|
+
'connect.getunconnectedfriendscount',
|
43
|
+
'dashboard.getactivity',
|
44
|
+
'dashboard.getcount',
|
45
|
+
'dashboard.getglobalnews',
|
46
|
+
'dashboard.getnews',
|
47
|
+
'dashboard.multigetcount',
|
48
|
+
'dashboard.multigetnews',
|
49
|
+
'data.getcookies',
|
50
|
+
'events.get',
|
51
|
+
'events.getmembers',
|
52
|
+
'fbml.getcustomtags',
|
53
|
+
'feed.getappfriendstories',
|
54
|
+
'feed.getregisteredtemplatebundlebyid',
|
55
|
+
'feed.getregisteredtemplatebundles',
|
56
|
+
'fql.multiquery',
|
57
|
+
'fql.query',
|
58
|
+
'friends.arefriends',
|
59
|
+
'friends.get',
|
60
|
+
'friends.getappusers',
|
61
|
+
'friends.getlists',
|
62
|
+
'friends.getmutualfriends',
|
63
|
+
'gifts.get',
|
64
|
+
'groups.get',
|
65
|
+
'groups.getmembers',
|
66
|
+
'intl.gettranslations',
|
67
|
+
'links.get',
|
68
|
+
'notes.get',
|
69
|
+
'notifications.get',
|
70
|
+
'pages.getinfo',
|
71
|
+
'pages.isadmin',
|
72
|
+
'pages.isappadded',
|
73
|
+
'pages.isfan',
|
74
|
+
'permissions.checkavailableapiaccess',
|
75
|
+
'permissions.checkgrantedapiaccess',
|
76
|
+
'photos.get',
|
77
|
+
'photos.getalbums',
|
78
|
+
'photos.gettags',
|
79
|
+
'profile.getinfo',
|
80
|
+
'profile.getinfooptions',
|
81
|
+
'stream.get',
|
82
|
+
'stream.getcomments',
|
83
|
+
'stream.getfilters',
|
84
|
+
'users.getinfo',
|
85
|
+
'users.getloggedinuser',
|
86
|
+
'users.getstandardinfo',
|
87
|
+
'users.hasapppermission',
|
88
|
+
'users.isappuser',
|
89
|
+
'users.isverified',
|
90
|
+
'video.getuploadlimits'
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
94
|
+
end # module Facebook
|
95
|
+
end # module Koala
|