koala 1.1.0 → 1.2.0beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.travis.yml +2 -1
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +6 -2
  4. data/Rakefile +0 -1
  5. data/koala.gemspec +8 -8
  6. data/lib/koala.rb +42 -45
  7. data/lib/koala/batch_operation.rb +15 -15
  8. data/lib/koala/graph_api.rb +81 -58
  9. data/lib/koala/graph_batch_api.rb +10 -10
  10. data/lib/koala/graph_collection.rb +6 -6
  11. data/lib/koala/http_service.rb +177 -0
  12. data/lib/koala/oauth.rb +2 -2
  13. data/lib/koala/realtime_updates.rb +20 -17
  14. data/lib/koala/rest_api.rb +1 -1
  15. data/lib/koala/test_users.rb +33 -16
  16. data/lib/koala/uploadable_io.rb +47 -42
  17. data/lib/koala/utils.rb +11 -0
  18. data/readme.md +38 -38
  19. data/spec/cases/api_base_spec.rb +2 -2
  20. data/spec/cases/error_spec.rb +32 -0
  21. data/spec/cases/graph_and_rest_api_spec.rb +20 -3
  22. data/spec/cases/graph_api_batch_spec.rb +88 -97
  23. data/spec/cases/graph_api_spec.rb +21 -4
  24. data/spec/cases/http_service_spec.rb +446 -0
  25. data/spec/cases/koala_spec.rb +33 -38
  26. data/spec/cases/oauth_spec.rb +219 -200
  27. data/spec/cases/realtime_updates_spec.rb +45 -31
  28. data/spec/cases/rest_api_spec.rb +23 -7
  29. data/spec/cases/test_users_spec.rb +112 -52
  30. data/spec/cases/uploadable_io_spec.rb +49 -36
  31. data/spec/cases/utils_spec.rb +10 -0
  32. data/spec/fixtures/facebook_data.yml +23 -22
  33. data/spec/fixtures/mock_facebook_responses.yml +126 -96
  34. data/spec/spec_helper.rb +29 -5
  35. data/spec/support/graph_api_shared_examples.rb +59 -52
  36. data/spec/support/json_testing_fix.rb +35 -11
  37. data/spec/support/koala_test.rb +163 -0
  38. data/spec/support/mock_http_service.rb +6 -4
  39. data/spec/support/ordered_hash.rb +205 -0
  40. data/spec/support/rest_api_shared_examples.rb +37 -37
  41. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  42. metadata +78 -79
  43. data/lib/koala/http_services.rb +0 -46
  44. data/lib/koala/http_services/net_http_service.rb +0 -92
  45. data/lib/koala/http_services/typhoeus_service.rb +0 -37
  46. data/spec/cases/http_services/http_service_spec.rb +0 -129
  47. data/spec/cases/http_services/net_http_service_spec.rb +0 -532
  48. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -152
  49. data/spec/support/live_testing_data_helper.rb +0 -40
  50. data/spec/support/setup_mocks_or_live.rb +0 -51
data/.travis.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  rvm:
2
2
  - 1.8.7 # (current default)
3
3
  - 1.9.2
4
+ - 1.9.3
5
+ - ruby-head
4
6
  - rbx
5
7
  - rbx-2.0
6
8
  - ree
7
9
  - jruby
8
- - ruby-head
data/CHANGELOG CHANGED
@@ -1,3 +1,29 @@
1
+ v1.2
2
+ New methods:
3
+ -- API is now the main API class, contains both Graph and REST methods
4
+ -- Old classes are aliased with deprecation warnings (non-breaking change)
5
+ -- TestUsers#update lets you update the name or password of an existing test user
6
+ -- API.get_page_access_token lets you easily fetch the access token for a page you manage (thanks, marcgg!)
7
+ Updated methods:
8
+ -- API.put_picture now accepts URLs to images (thanks, marcgg!)
9
+ Internal improvements:
10
+ -- Koala now uses Faraday to make requests, replacing the HTTPServices (see wiki)
11
+ -- Koala::HTTPService.http_options allows specification of default Faraday connection options
12
+ -- Koala::HTTPService.faraday_middleware allows custom middleware configurations
13
+ -- Koala now defaults to Net::HTTP rather than Typhoeus
14
+ -- Koala::NetHTTPService and Koala::TyphoeusService modules no longer exist
15
+ -- Koala no longer automatically switches to Net::HTTP when uploading IO objects to Facebook
16
+ -- RealTimeUpdates and TestUsers are no longer subclasses of API, but have their own .api objects
17
+ -- The old .graph_api accessor is aliases to .api with a deprecation warning
18
+ Testing improvements:
19
+ -- Live test suites now run against test users by default
20
+ -- Test suite can be repeatedly run live without having to update facebook_data.yml
21
+ -- OAuth code and session key tests cannot be run against test users
22
+ -- Faraday adapter for live tests can be specified with ADAPTER=[your adapter] in the shell command
23
+ -- Tests now pass against all rubies on Travis CI
24
+ -- Expanded test coverage
25
+ -- Fixed bug with YAML parsing in Ruby 1.9
26
+
1
27
  v1.1
2
28
  New methods:
3
29
  -- Added Batch API support (thanks, seejohnrun and spiegela!)
data/Gemfile CHANGED
@@ -1,7 +1,11 @@
1
1
  source :rubygems
2
2
 
3
- gemspec
3
+ group :development, :test do
4
+ gem "typhoeus"
5
+ end
4
6
 
5
7
  if defined? JRUBY_VERSION
6
8
  gem "jruby-openssl"
7
- end
9
+ end
10
+
11
+ gemspec
data/Rakefile CHANGED
@@ -7,7 +7,6 @@ rescue LoadError
7
7
  puts 'although not required, bundler is recommened for running the tests'
8
8
  end
9
9
 
10
-
11
10
  task :default => :spec
12
11
 
13
12
  require 'rspec/core/rake_task'
data/koala.gemspec CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{koala}
5
- s.version = "1.1.0"
6
- s.date = %q{2011-07-18}
5
+ s.version = "1.2.0beta1"
6
+ s.date = %q{2011-08-17}
7
7
 
8
8
  s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the REST API, realtime updates, and OAuth authentication.}
9
9
  s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph and REST APIs, 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.}
@@ -29,22 +29,22 @@ Gem::Specification.new do |s|
29
29
 
30
30
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
31
31
  s.add_runtime_dependency(%q<multi_json>, ["~> 1.0"])
32
- s.add_runtime_dependency(%q<multipart-post>, ["~> 1.0"])
32
+ s.add_runtime_dependency(%q<faraday>, ["~> 0.7.4"])
33
+ s.add_runtime_dependency(%q<faraday-stack>, ["~> 0.1.3"])
33
34
  s.add_development_dependency(%q<rspec>, ["~> 2.5"])
34
35
  s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
35
- s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
36
36
  else
37
37
  s.add_dependency(%q<multi_json>, ["~> 1.0"])
38
- s.add_dependency(%q<multipart-post>, ["~> 1.0"])
39
38
  s.add_dependency(%q<rspec>, ["~> 2.5"])
40
39
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
41
- s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
40
+ s.add_dependency(%q<faraday>, ["~> 0.7.4"])
41
+ s.add_dependency(%q<faraday-stack>, ["~> 0.1.3"])
42
42
  end
43
43
  else
44
44
  s.add_dependency(%q<multi_json>, ["~> 1.0"])
45
- s.add_dependency(%q<multipart-post>, ["~> 1.0"])
46
45
  s.add_dependency(%q<rspec>, ["~> 2.5"])
47
46
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
48
- s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
47
+ s.add_dependency(%q<faraday>, ["~> 0.7.4"])
48
+ s.add_dependency(%q<faraday-stack>, ["~> 0.1.3"])
49
49
  end
50
50
  end
data/lib/koala.rb CHANGED
@@ -8,8 +8,7 @@ require 'openssl'
8
8
  require 'base64'
9
9
 
10
10
  # include koala modules
11
- require 'koala/http_services'
12
- require 'koala/http_services/net_http_service'
11
+ require 'koala/http_service'
13
12
  require 'koala/oauth'
14
13
  require 'koala/graph_api'
15
14
  require 'koala/graph_batch_api'
@@ -18,7 +17,7 @@ require 'koala/graph_collection'
18
17
  require 'koala/rest_api'
19
18
  require 'koala/realtime_updates'
20
19
  require 'koala/test_users'
21
- require 'koala/http_services'
20
+ require 'koala/utils'
22
21
 
23
22
  # add KoalaIO class
24
23
  require 'koala/uploadable_io'
@@ -31,6 +30,7 @@ module Koala
31
30
  # Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
32
31
  # http://github.com/arsduo/koala
33
32
 
33
+ # APIs
34
34
  class API
35
35
  # initialize with an access token
36
36
  def initialize(access_token = nil)
@@ -38,10 +38,13 @@ module Koala
38
38
  end
39
39
  attr_reader :access_token
40
40
 
41
+ include GraphAPIMethods
42
+ include RestAPIMethods
43
+
41
44
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
42
45
  # Fetches the given path in the Graph API.
43
46
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
44
-
47
+
45
48
  # add a leading /
46
49
  path = "/#{path}" unless path =~ /^\//
47
50
 
@@ -64,35 +67,30 @@ module Koala
64
67
  end
65
68
  end
66
69
 
67
- # APIs
68
-
69
- class GraphAPI < API
70
- include GraphAPIMethods
71
- end
72
-
73
- class GraphBatchAPI < GraphAPI
74
- include GraphBatchAPIMethods
75
- end
76
-
77
- class RestAPI < API
78
- include RestAPIMethods
79
- end
80
-
81
- class GraphAndRestAPI < API
82
- include GraphAPIMethods
83
- include RestAPIMethods
70
+ # special enhanced APIs
71
+ class GraphBatchAPI < API
72
+ include GraphBatchAPIMethods
84
73
  end
85
74
 
86
- class RealtimeUpdates < API
75
+ class RealtimeUpdates
87
76
  include RealtimeUpdateMethods
88
77
  end
89
78
 
90
- class TestUsers < API
79
+ class TestUsers
91
80
  include TestUserMethods
92
- # make the Graph API accessible in case someone wants to make other calls to interact with their users
93
- attr_reader :graph_api
94
81
  end
95
82
 
83
+ # legacy support for old APIs
84
+ class OldAPI < API;
85
+ def initialize(*args)
86
+ Koala::Utils.deprecate("#{self.class.name} is deprecated and will be removed in a future version; please use the API class instead.")
87
+ super
88
+ end
89
+ end
90
+ class GraphAPI < OldAPI; end
91
+ class RestAPI < OldAPI; end
92
+ class GraphAndRestAPI < OldAPI; end
93
+
96
94
  # Errors
97
95
 
98
96
  class APIError < StandardError
@@ -106,29 +104,28 @@ module Koala
106
104
 
107
105
  class KoalaError < StandardError; end
108
106
 
109
- # Make an api request using the provided api service or one passed by the caller
110
- def self.make_request(path, args, verb, options = {})
111
- http_service = options.delete(:http_service) || Koala.http_service
112
- options = options.merge(:use_ssl => true) if @always_use_ssl
113
- http_service.make_request(path, args, verb, options)
114
- end
115
107
 
116
- # finally, set up the http service Koala methods used to make requests
117
- # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
108
+ # finally, the few things defined on the Koala module itself
118
109
  class << self
119
110
  attr_accessor :http_service
120
- attr_accessor :always_use_ssl
121
- attr_accessor :base_http_service
122
111
  end
123
- Koala.base_http_service = NetHTTPService
124
-
125
- # by default, try requiring Typhoeus -- if that works, use it
126
- # if you have Typheous and don't want to use it (or want another service),
127
- # you can run Koala.http_service = NetHTTPService (or MyHTTPService)
128
- begin
129
- require 'koala/http_services/typhoeus_service'
130
- Koala.http_service = TyphoeusService
131
- rescue LoadError
132
- Koala.http_service = Koala.base_http_service
112
+
113
+ def self.http_service=(service)
114
+ if service.respond_to?(:deprecated_interface)
115
+ # if this is a deprecated module, support the old interface
116
+ # by changing the default adapter so the right library is used
117
+ # we continue to use the single HTTPService module for everything
118
+ service.deprecated_interface
119
+ else
120
+ # if it's a real http_service, use it
121
+ @http_service = service
122
+ end
123
+ end
124
+
125
+ def self.make_request(path, args, verb, options = {})
126
+ http_service.make_request(path, args, verb, options)
133
127
  end
128
+
129
+ # we use Faraday as our main service, with mock as the other main one
130
+ self.http_service = HTTPService
134
131
  end
@@ -18,25 +18,25 @@ module Koala
18
18
  @url = options[:url]
19
19
  @method = options[:method].to_sym
20
20
  @post_processing = options[:post_processing]
21
-
21
+
22
22
  process_binary_args
23
-
23
+
24
24
  raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
25
25
  end
26
-
26
+
27
27
  def to_batch_params(main_access_token)
28
28
  # set up the arguments
29
29
  args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
30
-
30
+
31
31
  response = {
32
- :method => @method.to_s,
32
+ :method => @method.to_s,
33
33
  :relative_url => @url,
34
34
  }
35
-
35
+
36
36
  # handle batch-level arguments, such as name, depends_on, and attached_files
37
37
  @batch_args[:attached_files] = @files.keys.join(",") if @files
38
38
  response.merge!(@batch_args) if @batch_args
39
-
39
+
40
40
  # for get and delete, we append args to the URL string
41
41
  # otherwise, they go in the body
42
42
  if args_string.length > 0
@@ -46,15 +46,15 @@ module Koala
46
46
  response[:body] = args_string if args_string.length > 0
47
47
  end
48
48
  end
49
-
49
+
50
50
  response
51
51
  end
52
-
52
+
53
53
  protected
54
-
54
+
55
55
  def process_binary_args
56
56
  # collect binary files
57
- @args.each_pair do |key, value|
57
+ @args.each_pair do |key, value|
58
58
  if UploadableIO.binary_content?(value)
59
59
  @files ||= {}
60
60
  # we use a class-level counter to ensure unique file identifiers across multiple batch operations
@@ -63,12 +63,12 @@ module Koala
63
63
  id = "op#{identifier}_file#{@files.keys.length}"
64
64
  @files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
65
65
  end
66
- end
66
+ end
67
67
  end
68
-
68
+
69
69
  def args_in_url?
70
- @method == :get || @method == :delete
71
- end
70
+ @method == :get || @method == :delete
71
+ end
72
72
  end
73
73
  end
74
74
  end
@@ -1,30 +1,30 @@
1
1
  module Koala
2
2
  module Facebook
3
- GRAPH_SERVER = "graph.facebook.com"
3
+ GRAPH_SERVER = "graph.facebook.com"
4
4
 
5
5
  module GraphAPIMethods
6
6
  # A client for the Facebook Graph API.
7
- #
7
+ #
8
8
  # See http://github.com/arsduo/koala for Ruby/Koala documentation
9
9
  # and http://developers.facebook.com/docs/api for Facebook API documentation
10
- #
10
+ #
11
11
  # The Graph API is made up of the objects in Facebook (e.g., people, pages,
12
12
  # events, photos) and the connections between them (e.g., friends,
13
13
  # photo tags, and event RSVPs). This client provides access to those
14
14
  # primitive types in a generic way. For example, given an OAuth access
15
15
  # token, this will fetch the profile of the active user and the list
16
16
  # of the user's friends:
17
- #
18
- # graph = Koala::Facebook::GraphAPI.new(access_token)
17
+ #
18
+ # graph = Koala::Facebook::API.new(access_token)
19
19
  # user = graph.get_object("me")
20
20
  # friends = graph.get_connections(user["id"], "friends")
21
- #
21
+ #
22
22
  # You can see a list of all of the objects and connections supported
23
23
  # by the API at http://developers.facebook.com/docs/reference/api/.
24
- #
24
+ #
25
25
  # You can obtain an access token via OAuth or by using the Facebook
26
26
  # JavaScript SDK. See the Koala and Facebook documentation for more information.
27
- #
27
+ #
28
28
  # If you are using the JavaScript SDK, you can use the
29
29
  # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
30
30
  # for the active user from the cookie saved by the SDK.
@@ -35,19 +35,19 @@ module Koala
35
35
  # Fetchs the given object from the graph.
36
36
  graph_call(id, args, "get", options)
37
37
  end
38
-
38
+
39
39
  def get_objects(ids, args = {}, options = {})
40
40
  # Fetchs all of the given objects from the graph.
41
41
  # If any of the IDs are invalid, they'll raise an exception.
42
42
  return [] if ids.empty?
43
43
  graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
44
44
  end
45
-
45
+
46
46
  def put_object(parent_object, connection_name, args = {}, options = {})
47
47
  # Writes the given object to the graph, connected to the given parent.
48
48
  # See http://developers.facebook.com/docs/api#publishing for all of
49
49
  # the supported writeable objects.
50
- #
50
+ #
51
51
  # For example,
52
52
  # graph.put_object("me", "feed", :message => "Hello, world")
53
53
  # writes "Hello, world" to the active user's wall.
@@ -56,7 +56,7 @@ module Koala
56
56
  # publishing wall posts requires the "publish_stream" permission. See
57
57
  # http://developers.facebook.com/docs/authentication/ for details about
58
58
  # extended permissions.
59
-
59
+
60
60
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
61
61
  graph_call("#{parent_object}/#{connection_name}", args, "post", options)
62
62
  end
@@ -66,9 +66,9 @@ module Koala
66
66
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
67
67
  graph_call(id, {}, "delete", options)
68
68
  end
69
-
69
+
70
70
  # Connections
71
-
71
+
72
72
  def get_connections(id, connection_name, args = {}, options = {})
73
73
  # Fetchs the connections for given object.
74
74
  graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
@@ -76,14 +76,6 @@ module Koala
76
76
  end
77
77
  end
78
78
 
79
- def get_comments_for_urls(urls = [], args = {}, options = {})
80
- # Fetchs the comments for given URLs (array or comma-separated string)
81
- # see https://developers.facebook.com/blog/post/490
82
- return [] if urls.empty?
83
- args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
84
- get_object("comments", args, options)
85
- end
86
-
87
79
  def put_connections(id, connection_name, args = {}, options = {})
88
80
  # Posts a certain connection
89
81
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
@@ -98,19 +90,20 @@ module Koala
98
90
 
99
91
  # Media (photos and videos)
100
92
  # to delete photos or videos, use delete_object(object_id)
101
- # note: you'll need the user_photos or user_videos permissions to actually access media after upload
102
-
93
+ # note: you'll need the user_photos or user_videos permissions to actually access media after upload
94
+
103
95
  def get_picture(object, args = {}, options = {})
104
96
  # Gets a picture object, returning the URL (which Facebook sends as a header)
105
97
  graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
106
98
  result["Location"]
107
99
  end
108
- end
109
-
100
+ end
101
+
110
102
  # Can be called in multiple ways:
111
103
  #
112
104
  # put_picture(file, [content_type], ...)
113
105
  # put_picture(path_to_file, [content_type], ...)
106
+ # put_picture(picture_url, ...)
114
107
  #
115
108
  # You can pass in uploaded files directly from Rails or Sinatra.
116
109
  # (See lib/koala/uploadable_io.rb for supported frameworks)
@@ -119,29 +112,32 @@ module Koala
119
112
  # - args: a hash of request parameters (default: {})
120
113
  # - target_id: ID of the target where to post the picture (default: "me")
121
114
  # - options: a hash of http options passed to the HTTPService module
122
- #
115
+ #
123
116
  # put_picture(file, content_type, {:message => "Message"}, 01234560)
124
117
  # put_picture(params[:file], {:message => "Message"})
125
-
118
+ #
119
+ # (Note that with URLs, there's no optional content type field)
120
+ # put_picture(picture_url, {:message => "Message"}, my_page_id)
121
+
126
122
  def put_picture(*picture_args)
127
123
  put_object(*parse_media_args(picture_args, "photos"))
128
124
  end
129
-
125
+
130
126
  def put_video(*video_args)
131
127
  args = parse_media_args(video_args, "videos")
132
128
  args.last[:video] = true
133
129
  put_object(*args)
134
130
  end
135
-
131
+
136
132
  # Wall posts
137
133
  # To get wall posts, use get_connections(user, "feed")
138
134
  # To delete a wall post, just use delete_object(post_id)
139
-
135
+
140
136
  def put_wall_post(message, attachment = {}, profile_id = "me", options = {})
141
137
  # attachment is a hash describing the wall post
142
138
  # (see X for more details)
143
- # For instance,
144
- #
139
+ # For instance,
140
+ #
145
141
  # {"name" => "Link name"
146
142
  # "link" => "http://www.example.com/",
147
143
  # "caption" => "{*actor*} posted a new review",
@@ -150,19 +146,19 @@ module Koala
150
146
 
151
147
  self.put_object(profile_id, "feed", attachment.merge({:message => message}), options)
152
148
  end
153
-
149
+
154
150
  # Comments
155
151
  # to delete comments, use delete_object(comment_id)
156
152
  # to get comments, use get_connections(object, "likes")
157
-
153
+
158
154
  def put_comment(object_id, message, options = {})
159
155
  # Writes the given comment on the given post.
160
156
  self.put_object(object_id, "comments", {:message => message}, options)
161
157
  end
162
-
158
+
163
159
  # Likes
164
160
  # to get likes, use get_connections(user, "likes")
165
-
161
+
166
162
  def put_like(object_id, options = {})
167
163
  # Likes the given post.
168
164
  self.put_object(object_id, "likes", {}, options)
@@ -175,15 +171,30 @@ module Koala
175
171
  end
176
172
 
177
173
  # Search
178
-
174
+
179
175
  def search(search_terms, args = {}, options = {})
180
176
  args.merge!({:q => search_terms}) unless search_terms.nil?
181
177
  graph_call("search", args, "get", options) do |result|
182
178
  result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
183
179
  end
184
- end
185
-
186
-
180
+ end
181
+
182
+ # Convenience Methods
183
+
184
+ def get_page_access_token(object_id)
185
+ result = get_object(object_id, :fields => "access_token") do
186
+ result ? result["access_token"] : nil
187
+ end
188
+ end
189
+
190
+ def get_comments_for_urls(urls = [], args = {}, options = {})
191
+ # Fetchs the comments for given URLs (array or comma-separated string)
192
+ # see https://developers.facebook.com/blog/post/490
193
+ return [] if urls.empty?
194
+ args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
195
+ get_object("comments", args, options)
196
+ end
197
+
187
198
  # GraphCollection support
188
199
  def get_page(params)
189
200
  # Pages through a set of results stored in a GraphCollection
@@ -192,8 +203,7 @@ module Koala
192
203
  result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
193
204
  end
194
205
  end
195
-
196
-
206
+
197
207
  # Batch API
198
208
  def batch(http_options = {}, &block)
199
209
  batch_client = GraphBatchAPI.new(access_token)
@@ -203,8 +213,8 @@ module Koala
203
213
  else
204
214
  batch_client
205
215
  end
206
- end
207
-
216
+ end
217
+
208
218
  def self.included(base)
209
219
  base.class_eval do
210
220
  def self.batch
@@ -212,7 +222,7 @@ module Koala
212
222
  end
213
223
  end
214
224
  end
215
-
225
+
216
226
  # Direct access to the Facebook API
217
227
  # see any of the above methods for example invocations
218
228
  def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
@@ -224,35 +234,48 @@ module Koala
224
234
  # now process as appropriate (get picture header, make GraphCollection, etc.)
225
235
  post_processing ? post_processing.call(result) : result
226
236
  end
227
-
237
+
228
238
  def check_response(response)
229
239
  # check for Graph API-specific errors
230
240
  # this returns an error, which is immediately raised (non-batch)
231
241
  # or added to the list of batch results (batch)
232
242
  if response.is_a?(Hash) && error_details = response["error"]
233
- APIError.new(error_details)
243
+ APIError.new(error_details)
234
244
  end
235
245
  end
236
-
246
+
237
247
  private
238
-
248
+
239
249
  def parse_media_args(media_args, method)
240
250
  # photo and video uploads can accept different types of arguments (see above)
241
251
  # so here, we parse the arguments into a form directly usable in put_object
242
252
  raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
243
-
253
+
244
254
  args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
245
-
255
+
246
256
  args = media_args[1 + args_offset] || {}
247
257
  target_id = media_args[2 + args_offset] || "me"
248
- options = media_args[3 + args_offset] || {}
249
-
250
- args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
258
+ options = media_args[3 + args_offset] || {}
251
259
 
252
- options[:http_service] = Koala.base_http_service if args["source"].requires_base_http_service
260
+ if url?(media_args.first)
261
+ # If media_args is a URL, we can upload without UploadableIO
262
+ args.merge!(:url => media_args.first)
263
+ else
264
+ args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
265
+ end
253
266
 
254
267
  [target_id, method, args, options]
255
- end
268
+ end
269
+
270
+ def url?(data)
271
+ return false unless data.is_a? String
272
+ begin
273
+ uri = URI.parse(data)
274
+ %w( http https ).include?(uri.scheme)
275
+ rescue URI::BadURIError
276
+ false
277
+ end
278
+ end
256
279
  end
257
280
  end
258
- end
281
+ end