tyler_koala 1.2.0beta
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 +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
|