koala 1.1.0 → 1.2.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/.travis.yml +2 -1
- data/CHANGELOG +36 -0
- data/Gemfile +6 -2
- data/Rakefile +0 -1
- data/koala.gemspec +7 -8
- data/lib/koala/batch_operation.rb +15 -15
- data/lib/koala/graph_api.rb +85 -73
- data/lib/koala/graph_batch_api.rb +21 -11
- data/lib/koala/graph_collection.rb +13 -8
- data/lib/koala/http_service.rb +176 -0
- data/lib/koala/oauth.rb +34 -24
- data/lib/koala/realtime_updates.rb +21 -18
- data/lib/koala/rest_api.rb +1 -1
- data/lib/koala/test_users.rb +33 -17
- data/lib/koala/uploadable_io.rb +49 -43
- data/lib/koala/utils.rb +11 -0
- data/lib/koala/version.rb +3 -0
- data/lib/koala.rb +47 -45
- data/readme.md +58 -38
- data/spec/cases/{api_base_spec.rb → api_spec.rb} +27 -2
- data/spec/cases/error_spec.rb +32 -0
- data/spec/cases/graph_and_rest_api_spec.rb +12 -21
- data/spec/cases/graph_api_batch_spec.rb +92 -119
- data/spec/cases/graph_api_spec.rb +11 -14
- data/spec/cases/graph_collection_spec.rb +116 -0
- data/spec/cases/http_service_spec.rb +446 -0
- data/spec/cases/koala_spec.rb +36 -37
- data/spec/cases/oauth_spec.rb +318 -212
- data/spec/cases/realtime_updates_spec.rb +45 -31
- data/spec/cases/rest_api_spec.rb +23 -7
- data/spec/cases/test_users_spec.rb +123 -75
- data/spec/cases/uploadable_io_spec.rb +77 -36
- data/spec/cases/utils_spec.rb +10 -0
- data/spec/fixtures/facebook_data.yml +26 -24
- data/spec/fixtures/mock_facebook_responses.yml +131 -101
- data/spec/spec_helper.rb +29 -5
- data/spec/support/graph_api_shared_examples.rb +80 -120
- data/spec/support/json_testing_fix.rb +35 -11
- data/spec/support/koala_test.rb +187 -0
- data/spec/support/mock_http_service.rb +8 -5
- data/spec/support/ordered_hash.rb +205 -0
- data/spec/support/rest_api_shared_examples.rb +37 -37
- data/spec/support/uploadable_io_shared_examples.rb +2 -8
- metadata +72 -83
- data/lib/koala/http_services/net_http_service.rb +0 -92
- data/lib/koala/http_services/typhoeus_service.rb +0 -37
- data/lib/koala/http_services.rb +0 -46
- data/spec/cases/http_services/http_service_spec.rb +0 -129
- data/spec/cases/http_services/net_http_service_spec.rb +0 -532
- data/spec/cases/http_services/typhoeus_service_spec.rb +0 -152
- data/spec/support/live_testing_data_helper.rb +0 -40
- data/spec/support/setup_mocks_or_live.rb +0 -51
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :status, :body, :headers
|
|
6
|
+
def initialize(status, body, headers)
|
|
7
|
+
@status = status
|
|
8
|
+
@body = body
|
|
9
|
+
@headers = headers
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module HTTPService
|
|
14
|
+
# common functionality for all HTTP services
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :faraday_middleware, :http_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@http_options ||= {}
|
|
21
|
+
|
|
22
|
+
DEFAULT_MIDDLEWARE = Proc.new do |builder|
|
|
23
|
+
builder.request :multipart
|
|
24
|
+
builder.request :url_encoded
|
|
25
|
+
builder.adapter Faraday.default_adapter
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.server(options = {})
|
|
29
|
+
server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
|
|
30
|
+
server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
|
|
31
|
+
"#{options[:use_ssl] ? "https" : "http"}://#{options[:beta] ? "beta." : ""}#{server}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.make_request(path, args, verb, options = {})
|
|
35
|
+
# if the verb isn't get or post, send it as a post argument
|
|
36
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
37
|
+
|
|
38
|
+
# turn all the keys to strings (Faraday has issues with symbols under 1.8.7) and resolve UploadableIOs
|
|
39
|
+
params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last.is_a?(UploadableIO) ? kv.last.to_upload_io : kv.last; hash}
|
|
40
|
+
|
|
41
|
+
# figure out our options for this request
|
|
42
|
+
request_options = {:params => (verb == "get" ? params : {})}.merge(http_options || {}).merge(process_options(options))
|
|
43
|
+
request_options[:use_ssl] = true if args["access_token"] # require http if there's a token
|
|
44
|
+
|
|
45
|
+
# set up our Faraday connection
|
|
46
|
+
# we have to manually assign params to the URL or the
|
|
47
|
+
conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
|
|
48
|
+
|
|
49
|
+
response = conn.send(verb, path, (verb == "post" ? params : {}))
|
|
50
|
+
Koala::Response.new(response.status.to_i, response.body, response.headers)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.encode_params(param_hash)
|
|
54
|
+
# unfortunately, we can't use to_query because that's Rails, not Ruby
|
|
55
|
+
# if no hash (e.g. no auth token) return empty string
|
|
56
|
+
# this is used mainly by the Batch API nowadays
|
|
57
|
+
((param_hash || {}).collect do |key_and_value|
|
|
58
|
+
key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
|
|
59
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
|
60
|
+
end).join("&")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# deprecations
|
|
64
|
+
# not elegant or compact code, but temporary
|
|
65
|
+
|
|
66
|
+
def self.always_use_ssl
|
|
67
|
+
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
|
68
|
+
http_options[:use_ssl]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.always_use_ssl=(value)
|
|
72
|
+
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
|
73
|
+
http_options[:use_ssl] = value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.timeout
|
|
77
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
78
|
+
http_options[:timeout]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.timeout=(value)
|
|
82
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
83
|
+
http_options[:timeout] = value
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.timeout
|
|
87
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
88
|
+
http_options[:timeout]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.timeout=(value)
|
|
92
|
+
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
93
|
+
http_options[:timeout] = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.proxy
|
|
97
|
+
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
|
|
98
|
+
http_options[:proxy]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.proxy=(value)
|
|
102
|
+
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
|
|
103
|
+
http_options[:proxy] = value
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.ca_path
|
|
107
|
+
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
|
108
|
+
(http_options[:ssl] || {})[:ca_path]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.ca_path=(value)
|
|
112
|
+
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
|
113
|
+
(http_options[:ssl] ||= {})[:ca_path] = value
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.ca_file
|
|
117
|
+
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
|
118
|
+
(http_options[:ssl] || {})[:ca_file]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.ca_file=(value)
|
|
122
|
+
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
|
123
|
+
(http_options[:ssl] ||= {})[:ca_file] = value
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.verify_mode
|
|
127
|
+
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
|
128
|
+
(http_options[:ssl] || {})[:verify_mode]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.verify_mode=(value)
|
|
132
|
+
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
|
133
|
+
(http_options[:ssl] ||= {})[:verify_mode] = value
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def self.process_options(options)
|
|
137
|
+
if typhoeus_options = options.delete(:typhoeus_options)
|
|
138
|
+
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.")
|
|
139
|
+
options = options.merge(typhoeus_options)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if ca_file = options.delete(:ca_file)
|
|
143
|
+
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.")
|
|
144
|
+
(options[:ssl] ||= {})[:ca_file] = ca_file
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if ca_path = options.delete(:ca_path)
|
|
148
|
+
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.")
|
|
149
|
+
(options[:ssl] ||= {})[:ca_path] = ca_path
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if verify_mode = options.delete(:verify_mode)
|
|
153
|
+
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.")
|
|
154
|
+
(options[:ssl] ||= {})[:verify_mode] = verify_mode
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
options
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
module TyphoeusService
|
|
162
|
+
def self.deprecated_interface
|
|
163
|
+
# support old-style interface with a warning
|
|
164
|
+
Koala::Utils.deprecate("the TyphoeusService module is deprecated; to use Typhoeus, set Faraday.default_adapter = :typhoeus. Enabling Typhoeus for all Faraday connections.")
|
|
165
|
+
Faraday.default_adapter = :typhoeus
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
module NetHTTPService
|
|
170
|
+
def self.deprecated_interface
|
|
171
|
+
# support old-style interface with a warning
|
|
172
|
+
Koala::Utils.deprecate("the NetHTTPService module is deprecated; to use Net::HTTP, set Faraday.default_adapter = :net_http. Enabling Net::HTTP for all Faraday connections.")
|
|
173
|
+
Faraday.default_adapter = :net_http
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
data/lib/koala/oauth.rb
CHANGED
|
@@ -9,31 +9,18 @@ module Koala
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def get_user_info_from_cookie(cookie_hash)
|
|
12
|
-
# Parses the cookie set
|
|
13
|
-
#
|
|
14
|
-
# cookies should be a Hash, like the one Rails provides
|
|
12
|
+
# Parses the cookie set Facebook's JavaScript SDK.
|
|
13
|
+
# You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
|
|
15
14
|
#
|
|
16
15
|
# If the user is logged in via Facebook, we return a dictionary with the
|
|
17
16
|
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
|
18
17
|
# and the latter can be used to make authenticated requests to the Graph API.
|
|
19
18
|
# If the user is not logged in, we return None.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
19
|
+
|
|
20
|
+
if signed_cookie = cookie_hash["fbsr_#{@app_id}"]
|
|
21
|
+
parse_signed_cookie(signed_cookie)
|
|
22
|
+
elsif unsigned_cookie = cookie_hash["fbs_#{@app_id}"]
|
|
23
|
+
parse_unsigned_cookie(unsigned_cookie)
|
|
37
24
|
end
|
|
38
25
|
end
|
|
39
26
|
alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
|
|
@@ -52,7 +39,7 @@ module Koala
|
|
|
52
39
|
permissions = options[:permissions]
|
|
53
40
|
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
|
54
41
|
display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
|
|
55
|
-
|
|
42
|
+
|
|
56
43
|
callback = options[:callback] || @oauth_callback_url
|
|
57
44
|
raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
|
|
58
45
|
|
|
@@ -70,7 +57,7 @@ module Koala
|
|
|
70
57
|
def get_access_token_info(code, options = {})
|
|
71
58
|
# convenience method to get a parsed token from Facebook for a given code
|
|
72
59
|
# should this require an OAuth callback URL?
|
|
73
|
-
get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
|
|
60
|
+
get_token_from_server({:code => code, :redirect_uri => options[:redirect_uri] || @oauth_callback_url}, false, options)
|
|
74
61
|
end
|
|
75
62
|
|
|
76
63
|
def get_access_token(code, options = {})
|
|
@@ -104,7 +91,7 @@ module Koala
|
|
|
104
91
|
raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
|
|
105
92
|
|
|
106
93
|
# now see if the signature is valid (digest, key, data)
|
|
107
|
-
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope
|
|
94
|
+
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
|
|
108
95
|
raise 'SignedRequest: Invalid signature' if (signature != hmac)
|
|
109
96
|
|
|
110
97
|
return envelope
|
|
@@ -162,6 +149,29 @@ module Koala
|
|
|
162
149
|
end
|
|
163
150
|
components
|
|
164
151
|
end
|
|
152
|
+
|
|
153
|
+
def parse_unsigned_cookie(fb_cookie)
|
|
154
|
+
# remove the opening/closing quote
|
|
155
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
|
156
|
+
|
|
157
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
|
158
|
+
components = {}
|
|
159
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
|
160
|
+
|
|
161
|
+
# generate the signature and make sure it matches what we expect
|
|
162
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
|
163
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
|
164
|
+
sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def parse_signed_cookie(fb_cookie)
|
|
168
|
+
components = parse_signed_request(fb_cookie)
|
|
169
|
+
if (code = components["code"]) && token_info = get_access_token_info(code, :redirect_uri => '')
|
|
170
|
+
components.merge(token_info)
|
|
171
|
+
else
|
|
172
|
+
nil
|
|
173
|
+
end
|
|
174
|
+
end
|
|
165
175
|
|
|
166
176
|
def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
|
|
167
177
|
Koala.make_request("/oauth/#{endpoint}", {
|
|
@@ -178,4 +188,4 @@ module Koala
|
|
|
178
188
|
end
|
|
179
189
|
end
|
|
180
190
|
end
|
|
181
|
-
end
|
|
191
|
+
end
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
require 'koala'
|
|
2
|
-
|
|
3
1
|
module Koala
|
|
4
2
|
module Facebook
|
|
5
3
|
module RealtimeUpdateMethods
|
|
6
4
|
# note: to subscribe to real-time updates, you must have an application access token
|
|
7
|
-
|
|
5
|
+
|
|
8
6
|
def self.included(base)
|
|
9
7
|
# make the attributes readable
|
|
10
8
|
base.class_eval do
|
|
11
|
-
attr_reader :app_id, :app_access_token, :secret
|
|
12
|
-
|
|
9
|
+
attr_reader :api, :app_id, :app_access_token, :secret
|
|
10
|
+
|
|
13
11
|
# parses the challenge params and makes sure the call is legitimate
|
|
14
12
|
# returns the challenge string to be sent back to facebook if true
|
|
15
13
|
# returns false otherwise
|
|
@@ -20,7 +18,7 @@ module Koala
|
|
|
20
18
|
# you can make sure this is legitimate through two ways
|
|
21
19
|
# if your store the token across the calls, you can pass in the token value
|
|
22
20
|
# and we'll make sure it matches
|
|
23
|
-
(verify_token && params["hub.verify_token"] == verify_token) ||
|
|
21
|
+
(verify_token && params["hub.verify_token"] == verify_token) ||
|
|
24
22
|
# alternately, if you sent a specially-constructed value (such as a hash of various secret values)
|
|
25
23
|
# you can pass in a block, which we'll call with the verify_token sent by Facebook
|
|
26
24
|
# if it's legit, return anything that evaluates to true; otherwise, return nil or false
|
|
@@ -32,7 +30,7 @@ module Koala
|
|
|
32
30
|
end
|
|
33
31
|
end
|
|
34
32
|
end
|
|
35
|
-
|
|
33
|
+
|
|
36
34
|
def initialize(options = {})
|
|
37
35
|
@app_id = options[:app_id]
|
|
38
36
|
@app_access_token = options[:app_access_token]
|
|
@@ -40,22 +38,22 @@ module Koala
|
|
|
40
38
|
unless @app_id && (@app_access_token || @secret) # make sure we have what we need
|
|
41
39
|
raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
|
|
42
40
|
end
|
|
43
|
-
|
|
41
|
+
|
|
44
42
|
# fetch the access token if we're provided a secret
|
|
45
43
|
if @secret && !@app_access_token
|
|
46
44
|
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
|
47
45
|
@app_access_token = oauth.get_app_access_token
|
|
48
46
|
end
|
|
49
|
-
|
|
50
|
-
@graph_api =
|
|
47
|
+
|
|
48
|
+
@graph_api = API.new(@app_access_token)
|
|
51
49
|
end
|
|
52
|
-
|
|
50
|
+
|
|
53
51
|
# subscribes for realtime updates
|
|
54
52
|
# your callback_url must be set up to handle the verification request or the subscription will not be set up
|
|
55
53
|
# http://developers.facebook.com/docs/api/realtime
|
|
56
54
|
def subscribe(object, fields, callback_url, verify_token)
|
|
57
55
|
args = {
|
|
58
|
-
:object => object,
|
|
56
|
+
:object => object,
|
|
59
57
|
:fields => fields,
|
|
60
58
|
:callback_url => callback_url,
|
|
61
59
|
:verify_token => verify_token
|
|
@@ -63,7 +61,7 @@ module Koala
|
|
|
63
61
|
# a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
|
|
64
62
|
@graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
|
|
65
63
|
end
|
|
66
|
-
|
|
64
|
+
|
|
67
65
|
# removes subscription for object
|
|
68
66
|
# if object is nil, it will remove all subscriptions
|
|
69
67
|
def unsubscribe(object = nil)
|
|
@@ -71,16 +69,21 @@ module Koala
|
|
|
71
69
|
args[:object] = object if object
|
|
72
70
|
@graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
|
|
73
71
|
end
|
|
74
|
-
|
|
72
|
+
|
|
75
73
|
def list_subscriptions
|
|
76
|
-
@graph_api.graph_call(subscription_path)
|
|
74
|
+
@graph_api.graph_call(subscription_path)
|
|
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
|
|
77
80
|
end
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
protected
|
|
80
|
-
|
|
83
|
+
|
|
81
84
|
def subscription_path
|
|
82
85
|
@subscription_path ||= "#{@app_id}/subscriptions"
|
|
83
86
|
end
|
|
84
87
|
end
|
|
85
88
|
end
|
|
86
|
-
end
|
|
89
|
+
end
|
data/lib/koala/rest_api.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Koala
|
|
|
4
4
|
|
|
5
5
|
module RestAPIMethods
|
|
6
6
|
def fql_query(fql, args = {}, options = {})
|
|
7
|
-
rest_call('fql.query', args.merge(:query => fql), options)
|
|
7
|
+
rest_call('fql.query', args.merge(:query => fql), options)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def fql_multiquery(queries = {}, args = {}, options = {})
|
data/lib/koala/test_users.rb
CHANGED
|
@@ -3,7 +3,14 @@ require 'koala'
|
|
|
3
3
|
module Koala
|
|
4
4
|
module Facebook
|
|
5
5
|
module TestUserMethods
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.class_eval do
|
|
9
|
+
# make the Graph API accessible in case someone wants to make other calls to interact with their users
|
|
10
|
+
attr_reader :api, :app_id, :app_access_token, :secret
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def initialize(options = {})
|
|
8
15
|
@app_id = options[:app_id]
|
|
9
16
|
@app_access_token = options[:app_access_token]
|
|
@@ -17,29 +24,34 @@ module Koala
|
|
|
17
24
|
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
|
18
25
|
@app_access_token = oauth.get_app_access_token
|
|
19
26
|
end
|
|
20
|
-
@
|
|
27
|
+
@api = API.new(@app_access_token)
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def create(installed, permissions = nil, args = {}, options = {})
|
|
24
31
|
# Creates and returns a test user
|
|
25
32
|
args['installed'] = installed
|
|
26
33
|
args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
|
|
27
|
-
result = @
|
|
34
|
+
result = @api.graph_call(accounts_path, args, "post", options)
|
|
28
35
|
end
|
|
29
|
-
|
|
36
|
+
|
|
30
37
|
def list
|
|
31
|
-
@
|
|
38
|
+
@api.graph_call(accounts_path)
|
|
32
39
|
end
|
|
33
|
-
|
|
40
|
+
|
|
34
41
|
def delete(test_user)
|
|
35
42
|
test_user = test_user["id"] if test_user.is_a?(Hash)
|
|
36
|
-
@
|
|
43
|
+
@api.delete_object(test_user)
|
|
37
44
|
end
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
def delete_all
|
|
40
|
-
list.each {|u| delete u
|
|
47
|
+
list.each {|u| delete u}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update(test_user, args = {}, http_options = {})
|
|
51
|
+
test_user = test_user["id"] if test_user.is_a?(Hash)
|
|
52
|
+
@api.graph_call(test_user, args, "post", http_options)
|
|
41
53
|
end
|
|
42
|
-
|
|
54
|
+
|
|
43
55
|
def befriend(user1_hash, user2_hash)
|
|
44
56
|
user1_id = user1_hash["id"] || user1_hash[:id]
|
|
45
57
|
user2_id = user2_hash["id"] || user2_hash[:id]
|
|
@@ -51,16 +63,15 @@ module Koala
|
|
|
51
63
|
# but the Facebook call would fail
|
|
52
64
|
raise ArgumentError, "TestUsers#befriend requires hash arguments for both users with id and access_token"
|
|
53
65
|
end
|
|
54
|
-
|
|
55
|
-
u1_graph_api = GraphAPI.new(user1_token)
|
|
56
|
-
u2_graph_api = GraphAPI.new(user2_token)
|
|
57
66
|
|
|
58
|
-
u1_graph_api.
|
|
67
|
+
u1_graph_api = API.new(user1_token)
|
|
68
|
+
u2_graph_api = API.new(user2_token)
|
|
69
|
+
|
|
70
|
+
u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
|
|
59
71
|
u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post")
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
def create_network(network_size, installed = true, permissions = '')
|
|
63
|
-
network_size = 50 if network_size > 50 # FB's max is 50
|
|
64
75
|
users = (0...network_size).collect { create(installed, permissions) }
|
|
65
76
|
friends = users.clone
|
|
66
77
|
users.each do |user|
|
|
@@ -73,9 +84,14 @@ module Koala
|
|
|
73
84
|
end
|
|
74
85
|
return users
|
|
75
86
|
end
|
|
76
|
-
|
|
87
|
+
|
|
88
|
+
def graph_api
|
|
89
|
+
Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
|
|
90
|
+
@api
|
|
91
|
+
end
|
|
92
|
+
|
|
77
93
|
protected
|
|
78
|
-
|
|
94
|
+
|
|
79
95
|
def accounts_path
|
|
80
96
|
@accounts_path ||= "/#{@app_id}/accounts/test-users"
|
|
81
97
|
end
|
data/lib/koala/uploadable_io.rb
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "net/http/post/multipart"
|
|
2
2
|
|
|
3
3
|
module Koala
|
|
4
4
|
class UploadableIO
|
|
5
|
-
attr_reader :io_or_path, :content_type, :
|
|
5
|
+
attr_reader :io_or_path, :content_type, :filename
|
|
6
6
|
|
|
7
7
|
def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)
|
|
8
8
|
# see if we got the right inputs
|
|
9
|
-
|
|
10
|
-
parse_init_mixed_param io_or_path_or_mixed
|
|
11
|
-
elsif !content_type.nil? && (io_or_path_or_mixed.respond_to?(:read) or io_or_path_or_mixed.kind_of?(String))
|
|
12
|
-
@io_or_path = io_or_path_or_mixed
|
|
13
|
-
@content_type = content_type
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Probably a StringIO or similar object, which won't work with Typhoeus
|
|
17
|
-
@requires_base_http_service = @io_or_path.respond_to?(:read) && !@io_or_path.kind_of?(File)
|
|
9
|
+
parse_init_mixed_param io_or_path_or_mixed, content_type
|
|
18
10
|
|
|
19
11
|
# filename is used in the Ads API
|
|
20
|
-
|
|
12
|
+
# if it's provided, take precedence over the detected filename
|
|
13
|
+
# otherwise, fall back to a dummy name
|
|
14
|
+
@filename = filename || @filename || "koala-io-file.dum"
|
|
21
15
|
|
|
22
16
|
raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
|
|
23
|
-
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
|
17
|
+
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
|
24
18
|
end
|
|
25
|
-
|
|
19
|
+
|
|
26
20
|
def to_upload_io
|
|
27
21
|
UploadIO.new(@io_or_path, @content_type, @filename)
|
|
28
22
|
end
|
|
29
|
-
|
|
23
|
+
|
|
30
24
|
def to_file
|
|
31
25
|
@io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
|
|
32
26
|
end
|
|
33
|
-
|
|
27
|
+
|
|
34
28
|
def self.binary_content?(content)
|
|
35
29
|
content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
|
|
36
30
|
end
|
|
@@ -41,69 +35,81 @@ module Koala
|
|
|
41
35
|
:rails_3_param?,
|
|
42
36
|
:file_param?
|
|
43
37
|
]
|
|
44
|
-
|
|
38
|
+
|
|
45
39
|
PARSE_STRATEGIES = [
|
|
46
40
|
:parse_rails_3_param,
|
|
47
41
|
:parse_sinatra_param,
|
|
48
42
|
:parse_file_object,
|
|
49
|
-
:parse_string_path
|
|
43
|
+
:parse_string_path,
|
|
44
|
+
:parse_io
|
|
50
45
|
]
|
|
51
|
-
|
|
52
|
-
def parse_init_mixed_param(mixed)
|
|
46
|
+
|
|
47
|
+
def parse_init_mixed_param(mixed, content_type = nil)
|
|
53
48
|
PARSE_STRATEGIES.each do |method|
|
|
54
|
-
send(method, mixed)
|
|
49
|
+
send(method, mixed, content_type)
|
|
55
50
|
return if @io_or_path && @content_type
|
|
56
51
|
end
|
|
57
52
|
end
|
|
58
|
-
|
|
53
|
+
|
|
59
54
|
# Expects a parameter of type ActionDispatch::Http::UploadedFile
|
|
60
55
|
def self.rails_3_param?(uploaded_file)
|
|
61
56
|
uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
|
|
62
57
|
end
|
|
63
|
-
|
|
64
|
-
def parse_rails_3_param(uploaded_file)
|
|
65
|
-
if UploadableIO.rails_3_param?(uploaded_file)
|
|
58
|
+
|
|
59
|
+
def parse_rails_3_param(uploaded_file, content_type = nil)
|
|
60
|
+
if UploadableIO.rails_3_param?(uploaded_file)
|
|
66
61
|
@io_or_path = uploaded_file.tempfile.path
|
|
67
|
-
@content_type = uploaded_file.content_type
|
|
62
|
+
@content_type = content_type || uploaded_file.content_type
|
|
63
|
+
@filename = uploaded_file.original_filename
|
|
68
64
|
end
|
|
69
65
|
end
|
|
70
|
-
|
|
66
|
+
|
|
71
67
|
# Expects a Sinatra hash of file info
|
|
72
68
|
def self.sinatra_param?(file_hash)
|
|
73
69
|
file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
|
|
74
70
|
end
|
|
75
|
-
|
|
76
|
-
def parse_sinatra_param(file_hash)
|
|
71
|
+
|
|
72
|
+
def parse_sinatra_param(file_hash, content_type = nil)
|
|
77
73
|
if UploadableIO.sinatra_param?(file_hash)
|
|
78
74
|
@io_or_path = file_hash[:tempfile]
|
|
79
|
-
@content_type = file_hash[:type] || detect_mime_type(tempfile)
|
|
75
|
+
@content_type = content_type || file_hash[:type] || detect_mime_type(tempfile)
|
|
76
|
+
@filename = file_hash[:filename]
|
|
80
77
|
end
|
|
81
78
|
end
|
|
82
|
-
|
|
79
|
+
|
|
83
80
|
# takes a file object
|
|
84
81
|
def self.file_param?(file)
|
|
85
82
|
file.kind_of?(File)
|
|
86
83
|
end
|
|
87
|
-
|
|
88
|
-
def parse_file_object(file)
|
|
84
|
+
|
|
85
|
+
def parse_file_object(file, content_type = nil)
|
|
89
86
|
if UploadableIO.file_param?(file)
|
|
90
87
|
@io_or_path = file
|
|
91
|
-
@content_type = detect_mime_type(file.path)
|
|
88
|
+
@content_type = content_type || detect_mime_type(file.path)
|
|
89
|
+
@filename = File.basename(file.path)
|
|
92
90
|
end
|
|
93
91
|
end
|
|
94
|
-
|
|
95
|
-
def parse_string_path(path)
|
|
92
|
+
|
|
93
|
+
def parse_string_path(path, content_type = nil)
|
|
96
94
|
if path.kind_of?(String)
|
|
97
95
|
@io_or_path = path
|
|
98
|
-
@content_type = detect_mime_type(path)
|
|
96
|
+
@content_type = content_type || detect_mime_type(path)
|
|
97
|
+
@filename = File.basename(path)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_io(io, content_type = nil)
|
|
102
|
+
if io.respond_to?(:read)
|
|
103
|
+
@io_or_path = io
|
|
104
|
+
@content_type = content_type
|
|
99
105
|
end
|
|
100
106
|
end
|
|
101
|
-
|
|
107
|
+
|
|
102
108
|
MIME_TYPE_STRATEGIES = [
|
|
103
109
|
:use_mime_module,
|
|
104
110
|
:use_simple_detection
|
|
105
111
|
]
|
|
106
|
-
|
|
112
|
+
|
|
107
113
|
def detect_mime_type(filename)
|
|
108
114
|
if filename
|
|
109
115
|
MIME_TYPE_STRATEGIES.each do |method|
|
|
@@ -113,7 +119,7 @@ module Koala
|
|
|
113
119
|
end
|
|
114
120
|
nil # if we can't find anything
|
|
115
121
|
end
|
|
116
|
-
|
|
122
|
+
|
|
117
123
|
def use_mime_module(filename)
|
|
118
124
|
# if the user has installed mime/types, we can use that
|
|
119
125
|
# if not, rescue and return nil
|
|
@@ -124,12 +130,12 @@ module Koala
|
|
|
124
130
|
nil
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
|
-
|
|
133
|
+
|
|
128
134
|
def use_simple_detection(filename)
|
|
129
135
|
# very rudimentary extension analysis for images
|
|
130
136
|
# first, get the downcased extension, or an empty string if it doesn't exist
|
|
131
137
|
extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
|
|
132
|
-
case extension
|
|
138
|
+
case extension
|
|
133
139
|
when ""
|
|
134
140
|
nil
|
|
135
141
|
# images
|
|
@@ -169,7 +175,7 @@ module Koala
|
|
|
169
175
|
"video/ogg"
|
|
170
176
|
when "wmv"
|
|
171
177
|
"video/x-ms-wmv"
|
|
172
|
-
end
|
|
178
|
+
end
|
|
173
179
|
end
|
|
174
180
|
end
|
|
175
181
|
end
|
data/lib/koala/utils.rb
ADDED