koala 0.9.0 → 1.0.0.beta

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.
Files changed (46) hide show
  1. data/CHANGELOG +33 -7
  2. data/Manifest +6 -14
  3. data/Rakefile +4 -3
  4. data/koala.gemspec +13 -8
  5. data/lib/koala/graph_api.rb +16 -3
  6. data/lib/koala/http_services.rb +100 -16
  7. data/lib/koala/rest_api.rb +73 -6
  8. data/lib/koala/test_users.rb +72 -0
  9. data/lib/koala.rb +101 -71
  10. data/readme.md +14 -13
  11. data/spec/facebook_data.yml +14 -8
  12. data/spec/koala/api_base_tests.rb +7 -0
  13. data/spec/koala/assets/beach.jpg +0 -0
  14. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +5 -1
  15. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +9 -4
  16. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +10 -61
  17. data/spec/koala/graph_api/graph_api_tests.rb +86 -0
  18. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +130 -126
  19. data/spec/koala/graph_api/graph_collection_tests.rb +2 -2
  20. data/spec/koala/live_testing_data_helper.rb +40 -12
  21. data/spec/koala/net_http_service_tests.rb +401 -152
  22. data/spec/koala/oauth/oauth_tests.rb +41 -72
  23. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +7 -76
  24. data/spec/koala/rest_api/rest_api_tests.rb +118 -0
  25. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +6 -4
  26. data/spec/koala/test_users/test_users_tests.rb +208 -0
  27. data/spec/koala/typhoeus_service_tests.rb +156 -0
  28. data/spec/koala_spec_helper.rb +43 -4
  29. data/spec/koala_spec_without_mocks.rb +4 -4
  30. data/spec/mock_facebook_responses.yml +69 -3
  31. data/spec/mock_http_service.rb +16 -1
  32. metadata +49 -23
  33. data/examples/oauth_playground/Capfile +0 -2
  34. data/examples/oauth_playground/LICENSE +0 -22
  35. data/examples/oauth_playground/Rakefile +0 -4
  36. data/examples/oauth_playground/config/deploy.rb +0 -39
  37. data/examples/oauth_playground/config/facebook.yml +0 -13
  38. data/examples/oauth_playground/config.ru +0 -27
  39. data/examples/oauth_playground/lib/load_facebook.rb +0 -3
  40. data/examples/oauth_playground/lib/oauth_playground.rb +0 -187
  41. data/examples/oauth_playground/readme.md +0 -8
  42. data/examples/oauth_playground/spec/oauth_playground_spec.rb +0 -35
  43. data/examples/oauth_playground/spec/spec_helper.rb +0 -36
  44. data/examples/oauth_playground/tmp/restart.txt +0 -0
  45. data/examples/oauth_playground/views/index.erb +0 -206
  46. data/examples/oauth_playground/views/layout.erb +0 -39
data/CHANGELOG CHANGED
@@ -1,10 +1,36 @@
1
+ v1.0
2
+ New methods:
3
+ -- Photo and file upload now supported through #put_picture and general support for file hashes (see documentation)
4
+ -- Added a delete_like method (thanks to wassem)
5
+ Internal improvements:
6
+ -- For public requests, Koala now uses http by default (instead of https) to improve speed
7
+ -- This can be overridden through Koala.always_use_ssl= or by passing :use_ssl => true in the options hash for an api call
8
+ -- Read-only REST API requests now go through the faster api-read server
9
+ -- Replaced parse_signed_request with a version from Facebook that also supports the new signed params proposal
10
+ -- Note: invalid requests will now raise exceptions rather than return nil, in keeping with other SDKs
11
+ -- delete_object will now raise an error if there's no access token (like put_object and delete_like)
12
+ Test improvements:
13
+ -- Expanded HTTP service tests (added Typhoeus test suite and additional Net::HTTP test cases)
14
+ -- Live tests now verify that the access token has the necessary permissions before starting
15
+ -- Replaced the 50-person network test, which often took 15+ minutes to run live, with a 5-person test
16
+
17
+ v0.10.0
18
+ -- Added test user module
19
+ -- Fixed bug when raising APIError after Facebook fails to exchange session keys
20
+ -- Made access_token accessible via the readonly access_token property on all our API classes
21
+
22
+ v0.9.1
23
+ -- Tests are now compatible with Ruby 1.9.2
24
+ -- Added JSON to runtime dependencies
25
+ -- Removed examples directory (referenced from github instead)
26
+
1
27
  v0.9.0
2
28
  -- Added parse_signed_request to handle Facebook's new authentication scheme
3
29
  -- note: creates dependency on OpenSSL (OpenSSL::HMAC) for decryption
4
- -- Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
30
+ -- Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
5
31
  -- Added get_page method to easily fetch pages of results from GraphCollections
6
32
  -- Exchanging sessions for tokens now works properly when provided invalid/expired session keys
7
- -- 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)
33
+ -- 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)
8
34
  -- All paths provided to HTTP services start with leading / to improve compatibility with stubbing libraries
9
35
  -- If Facebook returns nil for search or get_connections requests, Koala now returns nil rather than throwing an exception
10
36
 
@@ -13,7 +39,7 @@ v0.8.0
13
39
  -- Removed string overloading for the methods, per 0.7.3, which caused Marshaling issues
14
40
  -- Removed ability to provide a string as the second argument to url_for_access_token, per 0.5.0
15
41
 
16
- v0.7.4
42
+ v0.7.4
17
43
  -- Fixed bug with get_user_from_cookies
18
44
 
19
45
  v0.7.3
@@ -24,18 +50,18 @@ v0.7.3
24
50
  -- Using those methods triggers a deprecation warning
25
51
  -- This will be removed by 1.0
26
52
  -- 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
27
- -- Responses with HTTP status 500+ now properly throw errors under Net::HTTP
53
+ -- Responses with HTTP status 500+ now properly throw errors under Net::HTTP
28
54
  -- Updated changelog
29
55
  -- Added license
30
56
 
31
57
  v0.7.2
32
58
  -- 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)
33
59
  -- Moved Koala files into a koala/ subdirectory to minimize risk of name collisions
34
- -- Added OAuth Playground git submodule as an example
60
+ -- Added OAuth Playground git submodule as an example
35
61
  -- Updated tests, readme, and changelog
36
62
 
37
63
  v0.7.1
38
- -- Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
64
+ -- Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
39
65
  array of results directly (rather than a hash with one key)
40
66
  -- Fixed a bug with Net::HTTP-based HTTP service in which the headers hash was improperly formatted
41
67
  -- Updated readme
@@ -82,7 +108,7 @@ v0.3.1
82
108
  v0.3
83
109
  -- Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
84
110
  -- Created FacebookGraph::OAuth class for tokens and OAuth URLs
85
- -- Updated method for including HTTP service (think we've got it this time)
111
+ -- Updated method for including HTTP service (think we've got it this time)
86
112
  -- Updated tests
87
113
  -- Added CHANGELOG and gemspec
88
114
 
data/Manifest CHANGED
@@ -2,20 +2,6 @@ CHANGELOG
2
2
  LICENSE
3
3
  Manifest
4
4
  Rakefile
5
- examples/oauth_playground/Capfile
6
- examples/oauth_playground/LICENSE
7
- examples/oauth_playground/Rakefile
8
- examples/oauth_playground/config.ru
9
- examples/oauth_playground/config/deploy.rb
10
- examples/oauth_playground/config/facebook.yml
11
- examples/oauth_playground/lib/load_facebook.rb
12
- examples/oauth_playground/lib/oauth_playground.rb
13
- examples/oauth_playground/readme.md
14
- examples/oauth_playground/spec/oauth_playground_spec.rb
15
- examples/oauth_playground/spec/spec_helper.rb
16
- examples/oauth_playground/tmp/restart.txt
17
- examples/oauth_playground/views/index.erb
18
- examples/oauth_playground/views/layout.erb
19
5
  init.rb
20
6
  koala.gemspec
21
7
  lib/koala.rb
@@ -23,12 +9,15 @@ lib/koala/graph_api.rb
23
9
  lib/koala/http_services.rb
24
10
  lib/koala/realtime_updates.rb
25
11
  lib/koala/rest_api.rb
12
+ lib/koala/test_users.rb
26
13
  readme.md
27
14
  spec/facebook_data.yml
28
15
  spec/koala/api_base_tests.rb
16
+ spec/koala/assets/beach.jpg
29
17
  spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb
30
18
  spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb
31
19
  spec/koala/graph_api/graph_api_no_access_token_tests.rb
20
+ spec/koala/graph_api/graph_api_tests.rb
32
21
  spec/koala/graph_api/graph_api_with_access_token_tests.rb
33
22
  spec/koala/graph_api/graph_collection_tests.rb
34
23
  spec/koala/live_testing_data_helper.rb
@@ -36,7 +25,10 @@ spec/koala/net_http_service_tests.rb
36
25
  spec/koala/oauth/oauth_tests.rb
37
26
  spec/koala/realtime_updates/realtime_updates_tests.rb
38
27
  spec/koala/rest_api/rest_api_no_access_token_tests.rb
28
+ spec/koala/rest_api/rest_api_tests.rb
39
29
  spec/koala/rest_api/rest_api_with_access_token_tests.rb
30
+ spec/koala/test_users/test_users_tests.rb
31
+ spec/koala/typhoeus_service_tests.rb
40
32
  spec/koala_spec.rb
41
33
  spec/koala_spec_helper.rb
42
34
  spec/koala_spec_without_mocks.rb
data/Rakefile CHANGED
@@ -4,13 +4,14 @@ require 'rake'
4
4
  require 'echoe'
5
5
 
6
6
  # gem management
7
- Echoe.new('koala', '0.9.0') do |p|
8
- p.summary = "A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation."
9
- p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services."
7
+ Echoe.new('koala', '1.0.0.beta') do |p|
8
+ p.summary = "A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation."
9
+ p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services."
10
10
  p.url = "http://github.com/arsduo/koala"
11
11
  p.author = ["Alex Koppel", "Chris Baclig", "Rafi Jacoby", "Context Optional"]
12
12
  p.email = "alex@alexkoppel.com"
13
13
  p.ignore_pattern = ["tmp/*", "script/*", "pkg/*"]
14
+ p.runtime_dependencies = ["json >=1.0", "multipart-post >=1.0"]
14
15
  p.development_dependencies = []
15
16
  p.retain_gemspec = true
16
17
  end
data/koala.gemspec CHANGED
@@ -2,29 +2,34 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{koala}
5
- s.version = "0.9.0"
5
+ s.version = "1.0.0.beta"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional"]
9
- s.date = %q{2010-09-30}
9
+ s.date = %q{2011-01-26}
10
10
  s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
11
11
  s.email = %q{alex@alexkoppel.com}
12
- s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb"]
13
- s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "examples/oauth_playground/Capfile", "examples/oauth_playground/LICENSE", "examples/oauth_playground/Rakefile", "examples/oauth_playground/config.ru", "examples/oauth_playground/config/deploy.rb", "examples/oauth_playground/config/facebook.yml", "examples/oauth_playground/lib/load_facebook.rb", "examples/oauth_playground/lib/oauth_playground.rb", "examples/oauth_playground/readme.md", "examples/oauth_playground/spec/oauth_playground_spec.rb", "examples/oauth_playground/spec/spec_helper.rb", "examples/oauth_playground/tmp/restart.txt", "examples/oauth_playground/views/index.erb", "examples/oauth_playground/views/layout.erb", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/graph_api/graph_collection_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "lib/koala/test_users.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "lib/koala/test_users.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/assets/beach.jpg", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/graph_api/graph_collection_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala/test_users/test_users_tests.rb", "spec/koala/typhoeus_service_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
14
14
  s.homepage = %q{http://github.com/arsduo/koala}
15
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala"]
16
16
  s.require_paths = ["lib"]
17
17
  s.rubyforge_project = %q{koala}
18
- s.rubygems_version = %q{1.3.6}
18
+ s.rubygems_version = %q{1.4.2}
19
19
  s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation.}
20
20
 
21
21
  if s.respond_to? :specification_version then
22
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
22
  s.specification_version = 3
24
23
 
25
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<json>, [">= 1.0"])
26
+ s.add_runtime_dependency(%q<multipart-post>, [">= 1.0"])
26
27
  else
28
+ s.add_dependency(%q<json>, [">= 1.0"])
29
+ s.add_dependency(%q<multipart-post>, [">= 1.0"])
27
30
  end
28
31
  else
32
+ s.add_dependency(%q<json>, [">= 1.0"])
33
+ s.add_dependency(%q<multipart-post>, [">= 1.0"])
29
34
  end
30
35
  end
@@ -142,7 +142,7 @@ module Koala
142
142
  # Writes a wall post to the given profile's wall.
143
143
  #
144
144
  # We default to writing to the authenticated user's wall if no
145
- # profile_id is specified.
145
+ # profile_id is specified.
146
146
  #
147
147
  # attachment adds a structured attachment to the status message being
148
148
  # posted to the Wall. It should be a dictionary of the form:
@@ -155,7 +155,14 @@ module Koala
155
155
 
156
156
  self.put_object(profile_id, "feed", attachment.merge({:message => message}))
157
157
  end
158
-
158
+
159
+ def put_picture(file_hash, args = {}, target_id = "me")
160
+ raise APIError.new("type" => "KoalaInvaliFileFormat", "message" => "put_picture requires a hash describing the File to upload (see HTTPService::is_valid_file_hash?") unless file_hash.kind_of?(Hash)
161
+
162
+ args["source"] = file_hash
163
+ self.put_object(target_id, "photos", args)
164
+ end
165
+
159
166
  def put_comment(object_id, message)
160
167
  # Writes the given comment on the given post.
161
168
  self.put_object(object_id, "comments", {:message => message})
@@ -165,9 +172,15 @@ module Koala
165
172
  # Likes the given post.
166
173
  self.put_object(object_id, "likes")
167
174
  end
175
+
176
+ def delete_like(object_id)
177
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
178
+ graph_call("#{object_id}/likes", {}, "delete")
179
+ end
168
180
 
169
181
  def delete_object(id)
170
182
  # Deletes the object with the given ID from the graph.
183
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
171
184
  graph_call(id, {}, "delete")
172
185
  end
173
186
 
@@ -189,4 +202,4 @@ module Koala
189
202
  end
190
203
  end
191
204
  end
192
- end
205
+ end
@@ -8,62 +8,146 @@ module Koala
8
8
  end
9
9
  end
10
10
 
11
+ module HTTPService
12
+ # common functionality for all HTTP services
13
+ def self.included(base)
14
+ base.class_eval do
15
+ class << self
16
+ attr_accessor :always_use_ssl
17
+ end
18
+
19
+ protected
20
+ def self.params_require_multipart?(param_hash)
21
+ param_hash.any? { |key, value| is_valid_file_hash?(value) }
22
+ end
23
+
24
+ # A file hash can take two forms:
25
+ # - A hash with "content_type" and "path" keys where "path" is the local path
26
+ # to the file to be uploaded.
27
+ # - A hash with the "file" key containing an already-opened IO that responds to "read"
28
+ # as well as "content_type" and the "path" key to the original file
29
+ # ("path"" is required by multipart-post even for opened files)
30
+ #
31
+ # Valid inputs for a file to be posted via multipart/form-data
32
+ # are based on the criteria for an UploadIO to be created
33
+ # See : https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
34
+ def self.is_valid_file_hash?(value)
35
+ value.kind_of?(Hash) and value.key?("content_type") and value.key?("path") and (
36
+ !value.key?("file") or value["file"].respond_to?(:read)
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+
11
43
  module NetHTTPService
12
44
  # this service uses Net::HTTP to send requests to the graph
13
45
  def self.included(base)
14
46
  base.class_eval do
15
- require 'net/http' unless defined?(Net::HTTP)
16
- require 'net/https'
47
+ require "net/http" unless defined?(Net::HTTP)
48
+ require "net/https"
49
+ require "net/http/post/multipart"
50
+
51
+ include Koala::HTTPService
17
52
 
18
53
  def self.make_request(path, args, verb, options = {})
19
54
  # We translate args to a valid query string. If post is specified,
20
55
  # we send a POST request to the given path with the given arguments.
21
56
 
57
+ # by default, we use SSL only for private requests
58
+ # this makes public requests faster
59
+ private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
60
+
22
61
  # if the verb isn't get or post, send it as a post argument
23
62
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
24
63
 
25
64
  server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
26
- http = Net::HTTP.new(server, 443)
27
- http.use_ssl = true
28
- # we turn off certificate validation to avoid the
65
+ http = Net::HTTP.new(server, private_request ? 443 : nil)
66
+ http.use_ssl = true if private_request
67
+
68
+ # we turn off certificate validation to avoid the
29
69
  # "warning: peer certificate won't be verified in this SSL session" warning
30
70
  # not sure if this is the right way to handle it
31
71
  # see http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
32
72
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
33
73
 
34
- result = http.start { |http|
35
- response, body = (verb == "post" ? http.post(path, encode_params(args)) : http.get("#{path}?#{encode_params(args)}"))
74
+ result = http.start do |http|
75
+ response, body = if verb == "post"
76
+ if params_require_multipart? args
77
+ http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
78
+ else
79
+ http.post(path, encode_params(args))
80
+ end
81
+ else
82
+ http.get("#{path}?#{encode_params(args)}")
83
+ end
84
+
36
85
  Koala::Response.new(response.code.to_i, body, response)
37
- }
86
+ end
38
87
  end
39
88
 
40
89
  protected
41
90
  def self.encode_params(param_hash)
42
91
  # unfortunately, we can't use to_query because that's Rails, not Ruby
43
92
  # if no hash (e.g. no auth token) return empty string
44
- ((param_hash || {}).collect do |key_and_value|
93
+ ((param_hash || {}).collect do |key_and_value|
45
94
  key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
46
95
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
47
96
  end).join("&")
48
97
  end
98
+
99
+ def self.encode_multipart_params(param_hash)
100
+ Hash[*param_hash.collect do |key, value|
101
+ if is_valid_file_hash?(value)
102
+ if value.key?("file")
103
+ value = UploadIO.new(value["file"], value["content_type"], value["path"])
104
+ else
105
+ value = UploadIO.new(value["path"], value['content_type'])
106
+ end
107
+ end
108
+
109
+ [key, value]
110
+ end.flatten]
111
+ end
49
112
  end
50
113
  end
51
114
  end
52
115
 
53
116
  module TyphoeusService
54
117
  # this service uses Typhoeus to send requests to the graph
118
+
119
+ # used for multipart file uploads (see below)
120
+ class NetHTTPInterface
121
+ include NetHTTPService
122
+ end
123
+
55
124
  def self.included(base)
56
125
  base.class_eval do
57
- require 'typhoeus' unless defined?(Typhoeus)
126
+ require "typhoeus" unless defined?(Typhoeus)
58
127
  include Typhoeus
128
+
129
+ include Koala::HTTPService
59
130
 
60
131
  def self.make_request(path, args, verb, options = {})
61
- # if the verb isn't get or post, send it as a post argument
62
- args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
63
- server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
64
- typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
65
- response = self.send(verb, "https://#{server}#{path}", typhoeus_options)
66
- Koala::Response.new(response.code, response.body, response.headers_hash)
132
+ unless params_require_multipart?(args)
133
+ # if the verb isn't get or post, send it as a post argument
134
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
135
+ server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
136
+
137
+ # you can pass arguments directly to Typhoeus using the :typhoeus_options key
138
+ typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
139
+
140
+ # by default, we use SSL only for private requests (e.g. with access token)
141
+ # this makes public requests faster
142
+ prefix = (args["access_token"] || @always_use_ssl || options[:use_ssl]) ? "https" : "http"
143
+
144
+ response = self.send(verb, "#{prefix}://#{server}#{path}", typhoeus_options)
145
+ Koala::Response.new(response.code, response.body, response.headers_hash)
146
+ else
147
+ # we have to use NetHTTPService for multipart for file uploads
148
+ # until Typhoeus integrates support
149
+ Koala::TyphoeusService::NetHTTPInterface.make_request(path, args, verb, options)
150
+ end
67
151
  end
68
152
  end # class_eval
69
153
  end
@@ -1,23 +1,90 @@
1
1
  module Koala
2
2
  module Facebook
3
3
  REST_SERVER = "api.facebook.com"
4
-
4
+
5
5
  module RestAPIMethods
6
6
  def fql_query(fql)
7
7
  rest_call('fql.query', 'query' => fql)
8
8
  end
9
-
10
- def rest_call(method, args = {})
11
- response = api("method/#{method}", args.merge('format' => 'json'), 'get', :rest_api => true) do |response|
9
+
10
+ def rest_call(method, args = {}, options = {})
11
+ options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(method))
12
+
13
+ response = api("method/#{method}", args.merge('format' => 'json'), 'get', options) do |response|
12
14
  # check for REST API-specific errors
13
15
  if response.is_a?(Hash) && response["error_code"]
14
16
  raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
15
17
  end
16
18
  end
17
-
19
+
18
20
  response
19
21
  end
22
+
23
+ # read-only methods for which we can use API-read
24
+ # taken directly from the FB PHP library (https://github.com/facebook/php-sdk/blob/master/src/facebook.php)
25
+ READ_ONLY_METHODS = [
26
+ 'admin.getallocation',
27
+ 'admin.getappproperties',
28
+ 'admin.getbannedusers',
29
+ 'admin.getlivestreamvialink',
30
+ 'admin.getmetrics',
31
+ 'admin.getrestrictioninfo',
32
+ 'application.getpublicinfo',
33
+ 'auth.getapppublickey',
34
+ 'auth.getsession',
35
+ 'auth.getsignedpublicsessiondata',
36
+ 'comments.get',
37
+ 'connect.getunconnectedfriendscount',
38
+ 'dashboard.getactivity',
39
+ 'dashboard.getcount',
40
+ 'dashboard.getglobalnews',
41
+ 'dashboard.getnews',
42
+ 'dashboard.multigetcount',
43
+ 'dashboard.multigetnews',
44
+ 'data.getcookies',
45
+ 'events.get',
46
+ 'events.getmembers',
47
+ 'fbml.getcustomtags',
48
+ 'feed.getappfriendstories',
49
+ 'feed.getregisteredtemplatebundlebyid',
50
+ 'feed.getregisteredtemplatebundles',
51
+ 'fql.multiquery',
52
+ 'fql.query',
53
+ 'friends.arefriends',
54
+ 'friends.get',
55
+ 'friends.getappusers',
56
+ 'friends.getlists',
57
+ 'friends.getmutualfriends',
58
+ 'gifts.get',
59
+ 'groups.get',
60
+ 'groups.getmembers',
61
+ 'intl.gettranslations',
62
+ 'links.get',
63
+ 'notes.get',
64
+ 'notifications.get',
65
+ 'pages.getinfo',
66
+ 'pages.isadmin',
67
+ 'pages.isappadded',
68
+ 'pages.isfan',
69
+ 'permissions.checkavailableapiaccess',
70
+ 'permissions.checkgrantedapiaccess',
71
+ 'photos.get',
72
+ 'photos.getalbums',
73
+ 'photos.gettags',
74
+ 'profile.getinfo',
75
+ 'profile.getinfooptions',
76
+ 'stream.get',
77
+ 'stream.getcomments',
78
+ 'stream.getfilters',
79
+ 'users.getinfo',
80
+ 'users.getloggedinuser',
81
+ 'users.getstandardinfo',
82
+ 'users.hasapppermission',
83
+ 'users.isappuser',
84
+ 'users.isverified',
85
+ 'video.getuploadlimits'
86
+ ]
20
87
  end
21
-
88
+
22
89
  end # module Facebook
23
90
  end # module Koala
@@ -0,0 +1,72 @@
1
+ require 'koala'
2
+
3
+ module Koala
4
+ module Facebook
5
+ module TestUserMethods
6
+
7
+ def initialize(options = {})
8
+ @app_id = options[:app_id]
9
+ @app_access_token = options[:app_access_token]
10
+ @secret = options[:secret]
11
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
12
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
13
+ end
14
+
15
+ # fetch the access token if we're provided a secret
16
+ if @secret && !@app_access_token
17
+ oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
18
+ @app_access_token = oauth.get_app_access_token
19
+ end
20
+ @graph_api = GraphAPI.new(@app_access_token)
21
+ end
22
+
23
+ def create(installed, permissions = nil)
24
+ # Creates and returns a test user
25
+ args = {'installed' => installed}
26
+ args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
27
+ result = @graph_api.graph_call(accounts_path, args, "post")
28
+ end
29
+
30
+ def list
31
+ @graph_api.graph_call(accounts_path)["data"]
32
+ end
33
+
34
+ def delete(test_user)
35
+ test_user = test_user["id"] if test_user.is_a?(Hash)
36
+ @graph_api.delete_object(test_user)
37
+ end
38
+
39
+ def delete_all
40
+ list.each {|u| delete u }
41
+ end
42
+
43
+ def befriend(user1, user2)
44
+ user1 = user1["id"] if user1.is_a?(Hash)
45
+ user2 = user2["id"] if user2.is_a?(Hash)
46
+ @graph_api.graph_call("/#{user1}/friends/#{user2}") && @graph_api.graph_call("/#{user2}/friends/#{user1}")
47
+ end
48
+
49
+ def create_network(network_size, installed = true, permissions = '')
50
+ network_size = 50 if network_size > 50 # FB's max is 50
51
+ users = (0...network_size).collect { create(installed, permissions) }
52
+ friends = users.clone
53
+ users.each do |user|
54
+ # Remove this user from list of friends
55
+ friends.delete_at(0)
56
+ # befriend all the others
57
+ friends.each do |friend|
58
+ befriend(user, friend)
59
+ end
60
+ end
61
+ return users
62
+ end
63
+
64
+ protected
65
+
66
+ def accounts_path
67
+ @accounts_path ||= "/#{@app_id}/accounts/test-users"
68
+ end
69
+
70
+ end # TestUserMethods
71
+ end # Facebook
72
+ end # Koala