koala 1.5.0 → 1.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,4 +4,5 @@ Gemfile.lock
4
4
  .rvmrc
5
5
  *.rbc
6
6
  *~
7
- .yardoc/
7
+ .yardoc/
8
+ .DS_Store
data/.travis.yml CHANGED
@@ -7,4 +7,7 @@ rvm:
7
7
  - jruby-19mode # JRuby in 1.9 mode
8
8
  - rbx-18mode
9
9
  - rbx-19mode
10
- - ree
10
+ - ree
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
data/Gemfile CHANGED
@@ -15,7 +15,7 @@ group :development, :test do
15
15
  if RUBY_PLATFORM =~ /darwin/
16
16
  # OS X integration
17
17
  gem "ruby_gntp"
18
- gem "rb-fsevent", "~> 0.4.3.1"
18
+ gem "rb-fsevent"
19
19
  end
20
20
  end
21
21
 
data/changelog.md ADDED
@@ -0,0 +1,293 @@
1
+ v1.6
2
+ New methods:
3
+ * RealtimeUpdates#validate_update to validate the signature of a Facebook call (thanks, gaffo!)
4
+ Updated methods:
5
+ * Graph API methods now accepts a post processing block, see readme for examples (thanks, wolframarnold!)
6
+ _Internal improvements:_
7
+ * Switched URL parsing to addressable, which can handle unusual FB URLs (thanks, bnorton!)
8
+ * Fixed Batch API bug that seems to have broken calls requiring post-processing
9
+ * Bump Faraday requirement to 0.8 (thanks, jarthod!)
10
+ * Picture and video URLs now support unicode characters (thanks, jayeff!)
11
+ Testing improvements:
12
+ * Cleaned up some test suites (thanks, bnorton!)
13
+ Documentation:
14
+ * Changelog is now markdown
15
+ * Code highlighting in readme (thanks, sfate!)
16
+ * Added Graph API explorer link in readme (thanks, jch!)
17
+ * Added permissions example for OAuth (thanks, sebastiandeutsch!)
18
+
19
+ v1.5
20
+ New methods:
21
+ * Added Koala::Utils.logger to enable debugging (thanks, KentonWhite!)
22
+ * Expose fb_error_message and fb_error_code directly in APIError
23
+ Updated methods:
24
+ * GraphCollection.parse_page_url now uses the URI library and can parse any address (thanks, bnorton!)
25
+ Internal improvements:
26
+ * Update MultiJson dependency to support the Oj library (thanks, eckz and zinenko!)
27
+ * Loosened Faraday dependency (thanks, rewritten and romanbsd!)
28
+ * Fixed typos (thanks, nathanbertram!)
29
+ * Switched uses of put_object to the more semantically accurate put_connections
30
+ * Cleaned up gemspec
31
+ * Handle invalid batch API responses better
32
+ Documentation:
33
+ * Added HTTP Services description for Faraday 0.8/persistent connections (thanks, romanbsd!)
34
+ * Remove documentation of the old pre-1.2 HTTP Service options
35
+
36
+ v.1.4.1
37
+ * Update MultiJson to 1.3 and change syntax to silence warnings (thanks, eckz and masterkain!)
38
+
39
+ v1.4
40
+ New methods:
41
+ * OAuth#exchange_access_token(_info) allows you to extend access tokens you receive (thanks, etiennebarrie!)
42
+ Updated methods:
43
+ * HTTPServices#encode_params sorts parameters to aid in URL comparison (thanks, sholden!)
44
+ * get_connections is now aliased as get_connection (use whichever makes sense to you)
45
+ Internal improvements:
46
+ * Fixed typos (thanks, brycethornton and jpemberthy!)
47
+ * RealtimeUpdates will no longer execute challenge block unnecessarily (thanks, iainbeeston!)
48
+ Testing improvements:
49
+ * Added parallel_tests to development gem file
50
+ * Fixed failing live tests
51
+ * Koala now tests against JRuby and Rubinius in 1.9 mode on Travis-CI
52
+
53
+ v1.3
54
+ New methods:
55
+ * OAuth#url_for_dialog creates URLs for Facebook dialog pages
56
+ * API#set_app_restrictions handles JSON-encoding app restrictions
57
+ * GraphCollection.parse_page_url now exposes useful functionality for non-Rails apps
58
+ * RealtimeUpdates#subscription_path and TestUsers#test_user_accounts_path are now public
59
+ Updated methods:
60
+ * REST API methods are now deprecated (see http://developers.facebook.com/blog/post/616/)
61
+ * OAuth#url_for_access_token and #url_for_oauth_code now include any provided options as URL parameters
62
+ * APIError#raw_response allows access to the raw error response received from Facebook
63
+ * Utils.deprecate only prints each message once (no more spamming)
64
+ * API#get_page_access_token now accepts additional arguments and HTTP options (like other calls)
65
+ * TestUsers and RealtimeUpdates methods now take http_options arguments
66
+ * All methods with http_options can now take :http_component => :response for the complete response
67
+ * OAuth#get_user_info_from_cookies returns nil rather than an error if the cookies are expired (thanks, herzio)
68
+ * TestUsers#delete_all now uses the Batch API and is much faster
69
+ Internal improvements:
70
+ * FQL queries now use the Graph API behind-the-scenes
71
+ * Cleaned up file and class organization, with aliases for backward compatibility
72
+ * Added YARD documentation throughout
73
+ * Fixed bugs in RealtimeUpdates, TestUsers, elsewhere
74
+ * Reorganized file and class structure non-destructively
75
+ Testing improvements:
76
+ * Expanded/improved test coverage
77
+ * The test suite no longer users any hard-coded user IDs
78
+ * KoalaTest.test_user_api allows access to the TestUsers instance
79
+ * Configured tests to run in random order using RSpec 2.8.0rc1
80
+
81
+ v1.2.1
82
+ New methods:
83
+ * RestAPI.set_app_properties handles JSON-encoding application properties
84
+ Updated methods:
85
+ * OAuth.get_user_from_cookie works with the new signed cookie format (thanks, gmccreight!)
86
+ * Beta server URLs are now correct
87
+ * OAuth.parse_signed_request now raises an informative error if the signed_request is malformed
88
+ Internal improvements:
89
+ * Koala::Multipart middleware properly encoding nested parameters (hashes) in POSTs
90
+ * Updated readme, changelog, etc.
91
+ Testing improvements:
92
+ * Live tests with test users now clean up all fake users they create
93
+ * Removed duplicate test cases
94
+ * Live tests with test users no longer delete each object they create, speeding things up
95
+
96
+ v1.2
97
+ New methods:
98
+ * API is now the main API class, contains both Graph and REST methods
99
+ * Old classes are aliased with deprecation warnings (non-breaking change)
100
+ * TestUsers#update lets you update the name or password of an existing test user
101
+ * API.get_page_access_token lets you easily fetch the access token for a page you manage (thanks, marcgg!)
102
+ * Added version.rb (Koala::VERSION)
103
+ Updated methods:
104
+ * OAuth now parses Facebook's new signed cookie format
105
+ * API.put_picture now accepts URLs to images (thanks, marcgg!)
106
+ * Bug fixes to put_picture, parse_signed_request, and the test suite (thanks, johnbhall and Will S.!)
107
+ * Smarter GraphCollection use
108
+ * Any pageable result will now become a GraphCollection
109
+ * Non-pageable results from get_connections no longer error
110
+ * GraphCollection.raw_results allows access to original result data
111
+ * Koala no longer enforces any limits on the number of test users you create at once
112
+ Internal improvements:
113
+ * Koala now uses Faraday to make requests, replacing the HTTPServices (see wiki)
114
+ * Koala::HTTPService.http_options allows specification of default Faraday connection options
115
+ * Koala::HTTPService.faraday_middleware allows custom middleware configurations
116
+ * Koala now defaults to Net::HTTP rather than Typhoeus
117
+ * Koala::NetHTTPService and Koala::TyphoeusService modules no longer exist
118
+ * Koala no longer automatically switches to Net::HTTP when uploading IO objects to Facebook
119
+ * RealTimeUpdates and TestUsers are no longer subclasses of API, but have their own .api objects
120
+ * The old .graph_api accessor is aliases to .api with a deprecation warning
121
+ * Removed deprecation warnings for pre-1.1 batch interface
122
+ Testing improvements:
123
+ * Live test suites now run against test users by default
124
+ * Test suite can be repeatedly run live without having to update facebook_data.yml
125
+ * OAuth code and session key tests cannot be run against test users
126
+ * Faraday adapter for live tests can be specified with ADAPTER=[your adapter] in the rspec command
127
+ * Live tests can be run against the beta server by specifying BETA=true in the rspec command
128
+ * Tests now pass against all rubies on Travis CI
129
+ * Expanded and refactored test coverage
130
+ * Fixed bug with YAML parsing in Ruby 1.9
131
+
132
+ v1.1
133
+ New methods:
134
+ * Added Batch API support (thanks, seejohnrun and spiegela!)
135
+ * includes file uploads, error handling, and FQL
136
+ * Added GraphAPI#put_video
137
+ * Added GraphAPI#get_comments_for_urls (thanks, amrnt!)
138
+ * Added RestAPI#fql_multiquery, which simplifies the results (thanks, amrnt!)
139
+ * HTTP services support global proxy and timeout settings (thanks, itchy!)
140
+ * Net::HTTP supports global ca_path, ca_file, and verify_mode settings (thanks, spiegela!)
141
+ Updated methods:
142
+ * RealtimeUpdates now uses a GraphAPI object instead of its own API
143
+ * RestAPI#rest_call now has an optional last argument for method, for calls requiring POST, DELETE, etc. (thanks, sshilo!)
144
+ * Filename can now be specified when uploading (e.g. for Ads API) (thanks, sshilo!)
145
+ * get_objects([]) returns [] instead of a Facebook error in non-batch mode (thanks, aselder!)
146
+ Internal improvements:
147
+ * Koala is now more compatible with other Rubies (JRuby, Rubinius, etc.)
148
+ * HTTP services are more modular and can be changed on the fly (thanks, chadk!)
149
+ * Includes support for uploading StringIOs and other non-files via Net::HTTP even when using TyphoeusService
150
+ * Koala now uses multi_json to improve compatibility with Rubinius and other Ruby versions
151
+ * Koala now uses the modern Typhoeus API (thanks, aselder!)
152
+ * Koala now uses the current modern Net::HTTP interface (thanks, romanbsd!)
153
+ * Fixed bugs and typos (thanks, waynn, mokevnin, and tikh!)
154
+
155
+ v1.0
156
+ New methods:
157
+ * Photo and file upload now supported through #put_picture
158
+ * Added UploadableIO class to manage file uploads
159
+ * Added a delete_like method (thanks to waseem)
160
+ * Added put_connection and delete_connection convenience methods
161
+ Updated methods:
162
+ * Search can now search places, checkins, etc. (thanks, rickyc!)
163
+ * You can now pass :beta => true in the http options to use Facebook's beta tier
164
+ * TestUser#befriend now requires user info hashes (id and access token) due to Facebook API changes (thanks, pulsd and kbighorse!)
165
+ * All methods now accept an http_options hash as their optional last parameter (thanks, spiegela!)
166
+ * url_for_oauth_code can now take a :display option (thanks, netbe!)
167
+ * Net::HTTP can now accept :timeout and :proxy options (thanks, gilles!)
168
+ * Test users now supports using test accounts across multiple apps
169
+ Internal improvements:
170
+ * For public requests, Koala now uses http by default (instead of https) to improve speed
171
+ * This can be overridden through Koala.always_use_ssl= or by passing :use_ssl => true in the options hash for an api call
172
+ * Read-only REST API requests now go through the faster api-read server
173
+ * Replaced parse_signed_request with a version from Facebook that also supports the new signed params proposal
174
+ * Note: invalid requests will now raise exceptions rather than return nil, in keeping with other SDKs
175
+ * Delete methods will now raise an error if there's no access token (like put_object and delete_like)
176
+ * Updated parse_signed_request to match Facebook's current implementation (thanks, imajes!)
177
+ * APIError is now < StandardError, not Exception
178
+ * Added KoalaError for non-API errors
179
+ * Net::HTTP's SSL verification is no longer disabled by default
180
+ Test improvements:
181
+ * Incorporated joshk's awesome rewrite of the entire Koala test suite (thanks, joshk!)
182
+ * Expanded HTTP service tests (added Typhoeus test suite and additional Net::HTTP test cases)
183
+ * Live tests now verify that the access token has the necessary permissions before starting
184
+ * Replaced the 50-person network test, which often took 15+ minutes to run live, with a 5-person test
185
+
186
+ v0.10.0
187
+ * Added test user module
188
+ * Fixed bug when raising APIError after Facebook fails to exchange session keys
189
+ * Made access_token accessible via the readonly access_token property on all our API classes
190
+
191
+ v0.9.1
192
+ * Tests are now compatible with Ruby 1.9.2
193
+ * Added JSON to runtime dependencies
194
+ * Removed examples directory (referenced from github instead)
195
+
196
+ v0.9.0
197
+ * Added parse_signed_request to handle Facebook's new authentication scheme
198
+ * note: creates dependency on OpenSSL (OpenSSL::HMAC) for decryption
199
+ * Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
200
+ * Added get_page method to easily fetch pages of results from GraphCollections
201
+ * Exchanging sessions for tokens now works properly when provided invalid/expired session keys
202
+ * You can now include a :typhoeus_options key in TyphoeusService#make_request's options hash to control the Typhoeus call (for example, to set :disable_ssl_peer_verification => true)
203
+ * All paths provided to HTTP services start with leading / to improve compatibility with stubbing libraries
204
+ * If Facebook returns nil for search or get_connections requests, Koala now returns nil rather than throwing an exception
205
+
206
+ v0.8.0
207
+ * Breaking interface changes
208
+ * Removed string overloading for the methods, per 0.7.3, which caused Marshaling issues
209
+ * Removed ability to provide a string as the second argument to url_for_access_token, per 0.5.0
210
+
211
+ v0.7.4
212
+ * Fixed bug with get_user_from_cookies
213
+
214
+ v0.7.3
215
+ * Added support for picture sizes -- thanks thhermansen for the patch!
216
+ * Adjusted the return values for several methods (get_access_token, get_app_access_token, get_token_from_session_key, get_tokens_from_session_keys, get_user_from_cookies)
217
+ * These methods now return strings, rather than hashes, which makes more sense
218
+ * The strings are overloaded with an [] method for backwards compatibility (Ruby is truly amazing)
219
+ * Using those methods triggers a deprecation warning
220
+ * This will be removed by 1.0
221
+ * There are new info methods (get_access_token_info, get_app_access_token_info, get_token_info_from_session_keys, and get_user_info_from_cookies) that natively return hashes, for when you want the expiration date
222
+ * Responses with HTTP status 500+ now properly throw errors under Net::HTTP
223
+ * Updated changelog
224
+ * Added license
225
+
226
+ v0.7.2
227
+ * Added support for exchanging session keys for OAuth access tokens (get_token_from_session_key for single keys, get_tokens_from_session_keys for multiple)
228
+ * Moved Koala files into a koala/ subdirectory to minimize risk of name collisions
229
+ * Added OAuth Playground git submodule as an example
230
+ * Updated tests, readme, and changelog
231
+
232
+ v0.7.1
233
+ * Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
234
+ array of results directly (rather than a hash with one key)
235
+ * Fixed a bug with Net::HTTP-based HTTP service in which the headers hash was improperly formatted
236
+ * Updated readme
237
+
238
+ v0.7.0
239
+ * Added RealtimeUpdates class, which can be used to manage subscriptions for user updates (see http://developers.facebook.com/docs/api/realtime)
240
+ * Added picture method to graph API, which fetches an object's picture from the redirect headers.
241
+ * Added _greatly_ improved testing with result mocking, which is now the default set of tests
242
+ * Renamed live testing spec to koala_spec_without_mocks.rb
243
+ * Added Koala::Response class, which encapsulates HTTP results since Facebook sometimes sends data in the status or headers
244
+ * Much internal refactoring
245
+ * Updated readme, changelog, etc.
246
+
247
+
248
+ v0.6.0
249
+ * Added support for the old REST API thanks to cbaclig's great work
250
+ * Updated tests to conform to RSpec standards
251
+ * Updated changelog, readme, etc.
252
+
253
+ v0.5.1
254
+ * Documentation is now on the wiki, updated readme accordingly.
255
+
256
+ v0.5.0
257
+ * Added several new OAuth methods for making and parsing access token requests
258
+ * Added test suite for the OAuth class
259
+ * Made second argument to url_for_access_token a hash (strings still work but trigger a deprecation warning)
260
+ * Added fields to facebook_data.yml
261
+ * Updated readme
262
+
263
+ v0.4.1
264
+ * Encapsulated GraphAPI and OAuth classes in the Koala::Facebook module for clarity (and to avoid claiming the global Facebook class)
265
+ * Moved make_request method to Koala class from GraphAPI instance (for use by future OAuth class functionality)
266
+ * Renamed request method to api for consistancy with Javascript library
267
+ * Updated tests and readme
268
+
269
+ v0.4.0
270
+ * Adopted the Koala name
271
+ * Updated readme and tests
272
+ * Fixed cookie verification bug for non-expiring OAuth tokens
273
+
274
+ v0.3.1
275
+ * Bug fixes.
276
+
277
+ v0.3
278
+ * Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
279
+ * Created FacebookGraph::OAuth class for tokens and OAuth URLs
280
+ * Updated method for including HTTP service (think we've got it this time)
281
+ * Updated tests
282
+ * Added CHANGELOG and gemspec
283
+
284
+ v0.2
285
+ * Gemified the project
286
+ * Split out HTTP services into their own file, and adjusted inclusion method
287
+
288
+ v0.1
289
+ * Added modular support for Typhoeus
290
+ * Added tests
291
+
292
+ v0.0
293
+ * Hi from F8! Basic read/write from the graph is working
data/koala.gemspec CHANGED
@@ -17,11 +17,12 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
 
20
- gem.extra_rdoc_files = ["readme.md", "CHANGELOG"]
20
+ gem.extra_rdoc_files = ["readme.md", "changelog.md"]
21
21
  gem.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala"]
22
22
 
23
23
  gem.add_runtime_dependency(%q<multi_json>, ["~> 1.3"])
24
- gem.add_runtime_dependency(%q<faraday>, ["~> 0.7"])
24
+ gem.add_runtime_dependency(%q<faraday>, ["~> 0.8"])
25
+ gem.add_runtime_dependency(%q<addressable>, ["~> 2.2"])
25
26
  gem.add_development_dependency(%q<rspec>, ["~> 2.8"])
26
27
  gem.add_development_dependency(%q<rake>, ["~> 0.8"])
27
28
  end
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/lib/koala/api.rb CHANGED
@@ -35,9 +35,9 @@ module Koala
35
35
  # (All API requests with access tokens use SSL.)
36
36
  # @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
37
37
  #
38
- # @yield The response body for evaluation
38
+ # @yield The response for evaluation
39
39
  #
40
- # @raise [Koala::Facebook::APIError] if Facebook returns an error (response status >= 500)
40
+ # @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
41
41
  #
42
42
  # @return the body of the response from Facebook (unless another http_component is requested)
43
43
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
@@ -50,43 +50,23 @@ module Koala
50
50
  # make the request via the provided service
51
51
  result = Koala.make_request(path, args, verb, options)
52
52
 
53
- # Check for any 500 errors before parsing the body
54
- # since we're not guaranteed that the body is valid JSON
55
- # in the case of a server error
56
- raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
57
-
58
- # parse the body as JSON and run it through the error checker (if provided)
59
- # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
60
- # and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
61
- body = MultiJson.load("[#{result.body.to_s}]")[0]
62
- yield body if error_checking_block
53
+ if result.status.to_i >= 500
54
+ raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
55
+ end
56
+
57
+ yield result if error_checking_block
63
58
 
64
59
  # if we want a component other than the body (e.g. redirect header for images), return that
65
60
  if component = options[:http_component]
66
61
  component == :response ? result : result.send(options[:http_component])
67
62
  else
68
- body
63
+ # parse the body as JSON and run it through the error checker (if provided)
64
+ # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
65
+ # and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
66
+ MultiJson.load("[#{result.body.to_s}]")[0]
69
67
  end
70
68
  end
71
69
  end
72
-
73
- class APIError < StandardError
74
- attr_accessor :fb_error_type, :fb_error_code, :fb_error_message, :raw_response
75
-
76
- # Creates a new APIError.
77
- #
78
- # Assigns the error type (as reported by Facebook) to #fb_error_type
79
- # and the raw error response available to #raw_response.
80
- #
81
- # @param details error details containing "type" and "message" keys.
82
- def initialize(details = {})
83
- self.raw_response = details
84
- self.fb_error_type = details["type"]
85
- self.fb_error_code = details["code"]
86
- self.fb_error_message = details["message"]
87
- super("#{fb_error_type}: #{details["message"]}")
88
- end
89
- end
90
70
  end
91
71
  end
92
72
 
@@ -25,7 +25,7 @@ module Koala
25
25
 
26
26
  process_binary_args
27
27
 
28
- raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
28
+ raise AuthenticationError.new(nil, nil, "Batch operations require an access token, none provided.") unless @access_token
29
29
  end
30
30
 
31
31
  def to_batch_params(main_access_token)
@@ -1,3 +1,5 @@
1
+ require 'addressable/uri'
2
+
1
3
  require 'koala/api/graph_collection'
2
4
  require 'koala/http_service/uploadable_io'
3
5
 
@@ -40,13 +42,20 @@ module Koala
40
42
  # @param args any additional arguments
41
43
  # (fields, metadata, etc. -- see {http://developers.facebook.com/docs/reference/api/ Facebook's documentation})
42
44
  # @param options (see Koala::Facebook::API#api)
45
+ # @param block for post-processing. It receives the result data; the
46
+ # return value of the method is the result of the block, if
47
+ # provided. (see Koala::Facebook::API#api)
43
48
  #
44
49
  # @raise [Koala::Facebook::APIError] if the ID is invalid or you don't have access to that object
45
50
  #
51
+ # @example
52
+ # get_object("me") # => {"id" => ..., "name" => ...}
53
+ # get_object("me") {|data| data['education']} # => only education section of profile
54
+ #
46
55
  # @return a hash of object data
47
- def get_object(id, args = {}, options = {})
48
- # Fetchs the given object from the graph.
49
- graph_call(id, args, "get", options)
56
+ def get_object(id, args = {}, options = {}, &block)
57
+ # Fetches the given object from the graph.
58
+ graph_call(id, args, "get", options, &block)
50
59
  end
51
60
 
52
61
  # Get information about multiple Facebook objects in one call.
@@ -54,15 +63,16 @@ module Koala
54
63
  # @param ids an array or comma-separated string of object IDs
55
64
  # @param args (see #get_object)
56
65
  # @param options (see Koala::Facebook::API#api)
66
+ # @param block (see Koala::Facebook::API#api)
57
67
  #
58
68
  # @raise [Koala::Facebook::APIError] if any ID is invalid or you don't have access to that object
59
69
  #
60
70
  # @return an array of object data hashes
61
- def get_objects(ids, args = {}, options = {})
62
- # Fetchs all of the given objects from the graph.
71
+ def get_objects(ids, args = {}, options = {}, &block)
72
+ # Fetches all of the given objects from the graph.
63
73
  # If any of the IDs are invalid, they'll raise an exception.
64
74
  return [] if ids.empty?
65
- graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
75
+ graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options, &block)
66
76
  end
67
77
 
68
78
  # Write an object to the Graph for a specific user.
@@ -71,20 +81,21 @@ module Koala
71
81
  # @note put_object is (for historical reasons) the same as put_connections.
72
82
  # Please use put_connections; in a future version of Koala (2.0?),
73
83
  # put_object will issue a POST directly to an individual object, not to a connection.
74
- def put_object(parent_object, connection_name, args = {}, options = {})
75
- put_connections(parent_object, connection_name, args, options)
84
+ def put_object(parent_object, connection_name, args = {}, options = {}, &block)
85
+ put_connections(parent_object, connection_name, args, options, &block)
76
86
  end
77
87
 
78
88
  # Delete an object from the Graph if you have appropriate permissions.
79
89
  #
80
90
  # @param id (see #get_object)
81
91
  # @param options (see #get_object)
92
+ # @param block (see Koala::Facebook::API#api)
82
93
  #
83
94
  # @return true if successful, false (or an APIError) if not
84
- def delete_object(id, options = {})
95
+ def delete_object(id, options = {}, &block)
85
96
  # Deletes the object with the given ID from the graph.
86
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
87
- graph_call(id, {}, "delete", options)
97
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
98
+ graph_call(id, {}, "delete", options, &block)
88
99
  end
89
100
 
90
101
  # Fetch information about a given connection (e.g. type of activity -- feed, events, photos, etc.)
@@ -98,11 +109,12 @@ module Koala
98
109
  # @param connection_name what
99
110
  # @param args any additional arguments
100
111
  # @param options (see #get_object)
112
+ # @param block (see Koala::Facebook::API#api)
101
113
  #
102
114
  # @return [Koala::Facebook::API::GraphCollection] an array of object hashes (in most cases)
103
- def get_connection(id, connection_name, args = {}, options = {})
104
- # Fetchs the connections for given object.
105
- graph_call("#{id}/#{connection_name}", args, "get", options)
115
+ def get_connection(id, connection_name, args = {}, options = {}, &block)
116
+ # Fetches the connections for given object.
117
+ graph_call("#{id}/#{connection_name}", args, "get", options, &block)
106
118
  end
107
119
  alias_method :get_connections, :get_connection
108
120
 
@@ -126,12 +138,13 @@ module Koala
126
138
  # @param connection_name (see #get_connection)
127
139
  # @param args (see #get_connection)
128
140
  # @param options (see #get_object)
141
+ # @param block (see Koala::Facebook::API#api)
129
142
  #
130
143
  # @return a hash containing the new object's id
131
- def put_connections(id, connection_name, args = {}, options = {})
144
+ def put_connections(id, connection_name, args = {}, options = {}, &block)
132
145
  # Posts a certain connection
133
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
134
- graph_call("#{id}/#{connection_name}", args, "post", options)
146
+ raise AuthenticationError.new(nil, nil, "Write operations require an access token") unless @access_token
147
+ graph_call("#{id}/#{connection_name}", args, "post", options, &block)
135
148
  end
136
149
 
137
150
  # Delete an object's connection (for instance, unliking the object).
@@ -142,12 +155,13 @@ module Koala
142
155
  # @param connection_name (see #get_connection)
143
156
  # @args (see #get_connection)
144
157
  # @param options (see #get_object)
158
+ # @param block (see Koala::Facebook::API#api)
145
159
  #
146
160
  # @return (see #delete_object)
147
- def delete_connections(id, connection_name, args = {}, options = {})
161
+ def delete_connections(id, connection_name, args = {}, options = {}, &block)
148
162
  # Deletes a given connection
149
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
150
- graph_call("#{id}/#{connection_name}", args, "delete", options)
163
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
164
+ graph_call("#{id}/#{connection_name}", args, "delete", options, &block)
151
165
  end
152
166
 
153
167
  # Fetches a photo.
@@ -156,15 +170,17 @@ module Koala
156
170
  #
157
171
  # @param options options for Facebook (see #get_object).
158
172
  # To get a different size photo, pass :type => size (small, normal, large, square).
173
+ # @param block (see Koala::Facebook::API#api)
159
174
  #
160
175
  # @note to delete photos or videos, use delete_object(id)
161
176
  #
162
177
  # @return the URL to the image
163
- def get_picture(object, args = {}, options = {})
178
+ def get_picture(object, args = {}, options = {}, &block)
164
179
  # Gets a picture object, returning the URL (which Facebook sends as a header)
165
- graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
180
+ resolved_result = graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
166
181
  result["Location"]
167
182
  end
183
+ block ? block.call(resolved_result) : resolved_result
168
184
  end
169
185
 
170
186
  # Upload a photo.
@@ -180,6 +196,7 @@ module Koala
180
196
  # @param args (see #get_object)
181
197
  # @param target_id the Facebook object to which to post the picture (default: "me")
182
198
  # @param options (see #get_object)
199
+ # @param block (see Koala::Facebook::API#api)
183
200
  #
184
201
  # @example
185
202
  # put_picture(file, content_type, {:message => "Message"}, 01234560)
@@ -190,16 +207,16 @@ module Koala
190
207
  # @note to access the media after upload, you'll need the user_photos or user_videos permission as appropriate.
191
208
  #
192
209
  # @return (see #put_connections)
193
- def put_picture(*picture_args)
194
- put_connections(*parse_media_args(picture_args, "photos"))
210
+ def put_picture(*picture_args, &block)
211
+ put_connections(*parse_media_args(picture_args, "photos"), &block)
195
212
  end
196
213
 
197
214
  # Upload a video. Functions exactly the same as put_picture.
198
215
  # @see #put_picture
199
- def put_video(*video_args)
216
+ def put_video(*video_args, &block)
200
217
  args = parse_media_args(video_args, "videos")
201
218
  args.last[:video] = true
202
- put_connections(*args)
219
+ put_connections(*args, &block)
203
220
  end
204
221
 
205
222
  # Write directly to the user's wall.
@@ -213,6 +230,7 @@ module Koala
213
230
  # (see the {https://developers.facebook.com/docs/guides/attachments/ stream attachments} documentation.)
214
231
  # @param target_id the target wall
215
232
  # @param options (see #get_object)
233
+ # @param block (see Koala::Facebook::API#api)
216
234
  #
217
235
  # @example
218
236
  # @api.put_wall_post("Hello there!", {
@@ -225,8 +243,8 @@ module Koala
225
243
  #
226
244
  # @see #put_connections
227
245
  # @return (see #put_connections)
228
- def put_wall_post(message, attachment = {}, target_id = "me", options = {})
229
- put_connections(target_id, "feed", attachment.merge({:message => message}), options)
246
+ def put_wall_post(message, attachment = {}, target_id = "me", options = {}, &block)
247
+ put_connections(target_id, "feed", attachment.merge({:message => message}), options, &block)
230
248
  end
231
249
 
232
250
  # Comment on a given object.
@@ -238,11 +256,12 @@ module Koala
238
256
  # @param id (see #get_object)
239
257
  # @param message the comment to write
240
258
  # @param options (see #get_object)
259
+ # @param block (see Koala::Facebook::API#api)
241
260
  #
242
261
  # @return (see #put_connections)
243
- def put_comment(id, message, options = {})
262
+ def put_comment(id, message, options = {}, &block)
244
263
  # Writes the given comment on the given post.
245
- put_connections(id, "comments", {:message => message}, options)
264
+ put_connections(id, "comments", {:message => message}, options, &block)
246
265
  end
247
266
 
248
267
  # Like a given object.
@@ -252,11 +271,12 @@ module Koala
252
271
  #
253
272
  # @param id (see #get_object)
254
273
  # @param options (see #get_object)
274
+ # @param block (see Koala::Facebook::API#api)
255
275
  #
256
276
  # @return (see #put_connections)
257
- def put_like(id, options = {})
277
+ def put_like(id, options = {}, &block)
258
278
  # Likes the given post.
259
- put_connections(id, "likes", {}, options)
279
+ put_connections(id, "likes", {}, options, &block)
260
280
  end
261
281
 
262
282
  # Unlike a given object.
@@ -264,12 +284,13 @@ module Koala
264
284
  #
265
285
  # @param id (see #get_object)
266
286
  # @param options (see #get_object)
287
+ # @param block (see Koala::Facebook::API#api)
267
288
  #
268
289
  # @return (see #delete_object)
269
- def delete_like(id, options = {})
290
+ def delete_like(id, options = {}, &block)
270
291
  # Unlikes a given object for the logged-in user
271
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
272
- graph_call("#{id}/likes", {}, "delete", options)
292
+ raise AuthenticationError.new(nil, nil, "Unliking requires an access token") unless @access_token
293
+ graph_call("#{id}/likes", {}, "delete", options, &block)
273
294
  end
274
295
 
275
296
  # Search for a given query among visible Facebook objects.
@@ -278,11 +299,12 @@ module Koala
278
299
  # @param search_terms the query to search for
279
300
  # @param args additional arguments, such as type, fields, etc.
280
301
  # @param options (see #get_object)
302
+ # @param block (see Koala::Facebook::API#api)
281
303
  #
282
304
  # @return [Koala::Facebook::API::GraphCollection] an array of search results
283
- def search(search_terms, args = {}, options = {})
305
+ def search(search_terms, args = {}, options = {}, &block)
284
306
  args.merge!({:q => search_terms}) unless search_terms.nil?
285
- graph_call("search", args, "get", options)
307
+ graph_call("search", args, "get", options, &block)
286
308
  end
287
309
 
288
310
  # Convenience Methods
@@ -296,8 +318,11 @@ module Koala
296
318
  # @param query the FQL query to perform
297
319
  # @param args (see #get_object)
298
320
  # @param options (see #get_object)
299
- def fql_query(query, args = {}, options = {})
300
- get_object("fql", args.merge(:q => query), options)
321
+ # @param block (see Koala::Facebook::API#api)
322
+ #
323
+ # @return the result of the FQL query.
324
+ def fql_query(query, args = {}, options = {}, &block)
325
+ get_object("fql", args.merge(:q => query), options, &block)
301
326
  end
302
327
 
303
328
  # Make an FQL multiquery.
@@ -306,6 +331,7 @@ module Koala
306
331
  # @param queries a hash of query names => FQL queries
307
332
  # @param args (see #get_object)
308
333
  # @param options (see #get_object)
334
+ # @param block (see Koala::Facebook::API#api)
309
335
  #
310
336
  # @example
311
337
  # @api.fql_multiquery({
@@ -316,11 +342,13 @@ module Koala
316
342
  # # instead of [{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]}]
317
343
  #
318
344
  # @return a hash of FQL results keyed to the appropriate query
319
- def fql_multiquery(queries = {}, args = {}, options = {})
320
- if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
345
+ def fql_multiquery(queries = {}, args = {}, options = {}, &block)
346
+ resolved_results = if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
321
347
  # simplify the multiquery result format
322
348
  results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
323
349
  end
350
+
351
+ block ? block.call(resolved_results) : resolved_results
324
352
  end
325
353
 
326
354
  # Get a page's access token, allowing you to act as the page.
@@ -329,30 +357,34 @@ module Koala
329
357
  # @param id the page ID
330
358
  # @param args (see #get_object)
331
359
  # @param options (see #get_object)
360
+ # @param block (see Koala::Facebook::API#api)
332
361
  #
333
362
  # @return the page's access token (discarding expiration and any other information)
334
- def get_page_access_token(id, args = {}, options = {})
335
- result = get_object(id, args.merge(:fields => "access_token"), options) do
363
+ def get_page_access_token(id, args = {}, options = {}, &block)
364
+ access_token = get_object(id, args.merge(:fields => "access_token"), options) do |result|
336
365
  result ? result["access_token"] : nil
337
366
  end
367
+
368
+ block ? block.call(access_token) : access_token
338
369
  end
339
370
 
340
- # Fetchs the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
371
+ # Fetches the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
341
372
  # See https://developers.facebook.com/blog/post/490.
342
373
  #
343
374
  # @param urls the URLs for which you want comments
344
375
  # @param args (see #get_object)
345
376
  # @param options (see #get_object)
377
+ # @param block (see Koala::Facebook::API#api)
346
378
  #
347
379
  # @returns a hash of urls => comment arrays
348
- def get_comments_for_urls(urls = [], args = {}, options = {})
380
+ def get_comments_for_urls(urls = [], args = {}, options = {}, &block)
349
381
  return [] if urls.empty?
350
382
  args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
351
- get_object("comments", args, options)
383
+ get_object("comments", args, options, &block)
352
384
  end
353
385
 
354
- def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {})
355
- graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options)
386
+ def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {}, &block)
387
+ graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options, &block)
356
388
  end
357
389
 
358
390
  # Certain calls such as {#get_connections} return an array of results which you can page through
@@ -364,10 +396,11 @@ module Koala
364
396
  #
365
397
  # @param params an array of arguments to graph_call
366
398
  # as returned by {Koala::Facebook::GraphCollection.parse_page_url}.
399
+ # @param block (see Koala::Facebook::API#api)
367
400
  #
368
401
  # @return Koala::Facebook::GraphCollection the appropriate page of results (an empty array if there are none)
369
- def get_page(params)
370
- graph_call(*params)
402
+ def get_page(params, &block)
403
+ graph_call(*params, &block)
371
404
  end
372
405
 
373
406
  # Execute a set of Graph API calls as a batch.
@@ -386,10 +419,21 @@ module Koala
386
419
  #
387
420
  # @example
388
421
  # results = @api.batch do |batch_api|
389
- # batch_api.get_object('me')
390
- # batch_api.get_object(KoalaTest.user1)
422
+ # batch_api.get_object('me')
423
+ # batch_api.get_object(KoalaTest.user1)
424
+ # end
425
+ # # => [{'id' => my_id, ...}, {'id' => koppel_id, ...}]
426
+ #
427
+ # # You can also provide blocks to your operations to process the
428
+ # # results, which is often useful if you're constructing batch
429
+ # # requests in various locations and want to keep the code
430
+ # # together in logical places.
431
+ # # See readme.md and the wiki for more examples.
432
+ # @api.batch do |batch_api|
433
+ # batch_api.get_object('me') {|data| data["id"] }
434
+ # batch_api.get_object(KoalaTest.user1) {|data| data["name"] }
391
435
  # end
392
- # # => [{"id" => my_id, ...}, {"id"" => koppel_id, ...}]
436
+ # # => [my_id, "Alex Koppel"]
393
437
  #
394
438
  # @return an array of results from your batch calls (as if you'd made them individually),
395
439
  # arranged in the same order they're made.
@@ -422,7 +466,7 @@ module Koala
422
466
  # @return the result from Facebook
423
467
  def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
424
468
  result = api(path, args, verb, options) do |response|
425
- error = check_response(response)
469
+ error = check_response(response.status, response.body)
426
470
  raise error if error
427
471
  end
428
472
 
@@ -435,12 +479,38 @@ module Koala
435
479
 
436
480
  private
437
481
 
438
- def check_response(response)
439
- # check for Graph API-specific errors
440
- # this returns an error, which is immediately raised (non-batch)
441
- # or added to the list of batch results (batch)
442
- if response.is_a?(Hash) && error_details = response["error"]
443
- APIError.new(error_details)
482
+ def check_response(http_status, response_body)
483
+ # Check for Graph API-specific errors. This returns an error of the appropriate type
484
+ # which is immediately raised (non-batch) or added to the list of batch results (batch)
485
+ http_status = http_status.to_i
486
+
487
+ if http_status >= 400
488
+ begin
489
+ response_hash = MultiJson.load(response_body)
490
+ rescue MultiJson::DecodeError
491
+ response_hash = {}
492
+ end
493
+
494
+ if response_hash['error_code']
495
+ # Old batch api error format. This can be removed on July 5, 2012.
496
+ # See https://developers.facebook.com/roadmap/#graph-batch-api-exception-format
497
+ error_info = {
498
+ 'code' => response_hash['error_code'],
499
+ 'message' => response_hash['error_description']
500
+ }
501
+ else
502
+ error_info = response_hash['error'] || {}
503
+ end
504
+
505
+ if error_info['type'] == 'OAuthException' &&
506
+ ( !error_info['code'] || [102, 190, 450, 452, 2500].include?(error_info['code'].to_i))
507
+
508
+ # See: https://developers.facebook.com/docs/authentication/access-token-expiration/
509
+ # https://developers.facebook.com/bugs/319643234746794?browse=search_4fa075c0bd9117b20604672
510
+ AuthenticationError.new(http_status, response_body, error_info)
511
+ else
512
+ ClientError.new(http_status, response_body, error_info)
513
+ end
444
514
  end
445
515
  end
446
516
 
@@ -468,9 +538,9 @@ module Koala
468
538
  def url?(data)
469
539
  return false unless data.is_a? String
470
540
  begin
471
- uri = URI.parse(data)
541
+ uri = Addressable::URI.parse(data)
472
542
  %w( http https ).include?(uri.scheme)
473
- rescue URI::BadURIError
543
+ rescue Addressable::URI::InvalidURIError
474
544
  false
475
545
  end
476
546
  end