koala 1.3.0rc1 → 1.3.0rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -1
- data/.travis.yml +4 -0
- data/.yardopts +3 -0
- data/CHANGELOG +7 -0
- data/Gemfile +14 -0
- data/Guardfile +6 -0
- data/lib/koala.rb +16 -97
- data/lib/koala/api.rb +93 -0
- data/lib/koala/api/batch_operation.rb +83 -0
- data/lib/koala/api/graph_api.rb +476 -0
- data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +20 -16
- data/lib/koala/api/graph_collection.rb +107 -0
- data/lib/koala/api/legacy.rb +26 -0
- data/lib/koala/{rest_api.rb → api/rest_api.rb} +33 -3
- data/lib/koala/http_service.rb +69 -19
- data/lib/koala/http_service/multipart_request.rb +41 -0
- data/lib/koala/http_service/response.rb +18 -0
- data/lib/koala/http_service/uploadable_io.rb +187 -0
- data/lib/koala/oauth.rb +117 -14
- data/lib/koala/realtime_updates.rb +89 -51
- data/lib/koala/test_users.rb +109 -33
- data/lib/koala/utils.rb +4 -0
- data/lib/koala/version.rb +1 -1
- data/spec/cases/api_spec.rb +19 -12
- data/spec/cases/graph_api_batch_spec.rb +41 -41
- data/spec/cases/http_service_spec.rb +1 -22
- data/spec/cases/legacy_spec.rb +107 -0
- data/spec/cases/multipart_request_spec.rb +5 -5
- data/spec/cases/oauth_spec.rb +9 -9
- data/spec/cases/realtime_updates_spec.rb +154 -47
- data/spec/cases/test_users_spec.rb +268 -219
- data/spec/fixtures/mock_facebook_responses.yml +10 -6
- data/spec/support/graph_api_shared_examples.rb +17 -12
- data/spec/support/koala_test.rb +1 -1
- data/spec/support/mock_http_service.rb +2 -2
- data/spec/support/rest_api_shared_examples.rb +1 -1
- metadata +82 -104
- data/lib/koala/batch_operation.rb +0 -74
- data/lib/koala/graph_api.rb +0 -289
- data/lib/koala/graph_collection.rb +0 -63
- data/lib/koala/multipart_request.rb +0 -35
- data/lib/koala/uploadable_io.rb +0 -181
- data/spec/cases/graph_and_rest_api_spec.rb +0 -22
- data/spec/cases/graph_api_spec.rb +0 -22
- data/spec/cases/rest_api_spec.rb +0 -22
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/CHANGELOG
CHANGED
@@ -3,13 +3,20 @@ New methods:
|
|
3
3
|
-- OAuth#url_for_dialog creates URLs for Facebook dialog pages
|
4
4
|
-- API#set_app_restrictions handles JSON-encoding app restrictions
|
5
5
|
-- GraphCollection.parse_page_url now exposes useful functionality for non-Rails apps
|
6
|
+
-- RealtimeUpdates#subscription_path and TestUsers#test_user_accounts_path are now public
|
6
7
|
Updated methods:
|
7
8
|
-- OAuth#url_for_access_token and #url_for_oauth_code now include any provided options as URL parameters
|
8
9
|
-- APIError#raw_response allows access to the raw error response received from Facebook
|
9
10
|
-- Utils.deprecate only prints each message once (no more spamming)
|
10
11
|
-- API#get_page_access_token now accepts additional arguments and HTTP options (like other calls)
|
12
|
+
-- TestUsers and RealtimeUpdates methods now take http_options arguments
|
13
|
+
-- All methods with http_options can now take :http_component => :response for the complete response
|
11
14
|
Internal improvements:
|
12
15
|
-- FQL queries now use the Graph API behind-the-scenes
|
16
|
+
-- Cleaned up file and class organization, with aliases for backward compatibility
|
17
|
+
-- Added YARD documentation throughout
|
18
|
+
-- Fixed bugs in RealtimeUpdates, TestUsers, elsewhere
|
19
|
+
-- Reorganized file and class structure non-destructively
|
13
20
|
Testing improvements:
|
14
21
|
-- Expanded/improved test coverage
|
15
22
|
-- The test suite no longer users any hard-coded user IDs
|
data/Gemfile
CHANGED
@@ -1,7 +1,21 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
+
group :development do
|
4
|
+
gem "yard"
|
5
|
+
end
|
6
|
+
|
3
7
|
group :development, :test do
|
4
8
|
gem "typhoeus"
|
9
|
+
|
10
|
+
# Testing infrastructure
|
11
|
+
gem 'guard'
|
12
|
+
gem 'guard-rspec'
|
13
|
+
|
14
|
+
if RUBY_PLATFORM =~ /darwin/
|
15
|
+
# OS X integration
|
16
|
+
gem "ruby_gntp"
|
17
|
+
gem "rb-fsevent", "~> 0.4.3.1"
|
18
|
+
end
|
5
19
|
end
|
6
20
|
|
7
21
|
if defined? JRUBY_VERSION
|
data/Guardfile
ADDED
data/lib/koala.rb
CHANGED
@@ -1,122 +1,40 @@
|
|
1
|
-
|
1
|
+
# useful tools
|
2
2
|
require 'digest/md5'
|
3
|
-
|
4
3
|
require 'multi_json'
|
5
4
|
|
6
|
-
# OpenSSL and Base64 are required to support signed_request
|
7
|
-
require 'openssl'
|
8
|
-
require 'base64'
|
9
|
-
|
10
5
|
# include koala modules
|
6
|
+
require 'koala/api'
|
11
7
|
require 'koala/oauth'
|
12
|
-
require 'koala/graph_api'
|
13
|
-
require 'koala/graph_batch_api'
|
14
|
-
require 'koala/batch_operation'
|
15
|
-
require 'koala/graph_collection'
|
16
|
-
require 'koala/rest_api'
|
17
8
|
require 'koala/realtime_updates'
|
18
9
|
require 'koala/test_users'
|
19
10
|
|
20
11
|
# HTTP module so we can communicate with Facebook
|
21
12
|
require 'koala/http_service'
|
22
|
-
require 'koala/multipart_request'
|
23
|
-
|
24
|
-
# add KoalaIO class
|
25
|
-
require 'koala/uploadable_io'
|
26
13
|
|
27
14
|
# miscellaneous
|
28
15
|
require 'koala/utils'
|
29
16
|
require 'koala/version'
|
30
17
|
|
31
18
|
module Koala
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
|
37
|
-
# http://github.com/arsduo/koala
|
38
|
-
|
39
|
-
# APIs
|
40
|
-
class API
|
41
|
-
# initialize with an access token
|
42
|
-
def initialize(access_token = nil)
|
43
|
-
@access_token = access_token
|
44
|
-
end
|
45
|
-
attr_reader :access_token
|
46
|
-
|
47
|
-
include GraphAPIMethods
|
48
|
-
include RestAPIMethods
|
49
|
-
|
50
|
-
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
51
|
-
# Fetches the given path in the Graph API.
|
52
|
-
args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
|
53
|
-
|
54
|
-
# add a leading /
|
55
|
-
path = "/#{path}" unless path =~ /^\//
|
56
|
-
|
57
|
-
# make the request via the provided service
|
58
|
-
result = Koala.make_request(path, args, verb, options)
|
59
|
-
|
60
|
-
# Check for any 500 errors before parsing the body
|
61
|
-
# since we're not guaranteed that the body is valid JSON
|
62
|
-
# in the case of a server error
|
63
|
-
raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
|
64
|
-
|
65
|
-
# parse the body as JSON and run it through the error checker (if provided)
|
66
|
-
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
67
|
-
# and cause MultiJson.decode to fail -- so we account for that by wrapping the result in []
|
68
|
-
body = MultiJson.decode("[#{result.body.to_s}]")[0]
|
69
|
-
yield body if error_checking_block
|
70
|
-
|
71
|
-
# if we want a component other than the body (e.g. redirect header for images), return that
|
72
|
-
options[:http_component] ? result.send(options[:http_component]) : body
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# special enhanced APIs
|
77
|
-
class GraphBatchAPI < API
|
78
|
-
include GraphBatchAPIMethods
|
79
|
-
end
|
80
|
-
|
81
|
-
class RealtimeUpdates
|
82
|
-
include RealtimeUpdateMethods
|
83
|
-
end
|
84
|
-
|
85
|
-
class TestUsers
|
86
|
-
include TestUserMethods
|
87
|
-
end
|
88
|
-
|
89
|
-
# legacy support for old APIs
|
90
|
-
class OldAPI < API;
|
91
|
-
def initialize(*args)
|
92
|
-
Koala::Utils.deprecate("#{self.class.name} is deprecated and will be removed in a future version; please use the API class instead.")
|
93
|
-
super
|
94
|
-
end
|
95
|
-
end
|
96
|
-
class GraphAPI < OldAPI; end
|
97
|
-
class RestAPI < OldAPI; end
|
98
|
-
class GraphAndRestAPI < OldAPI; end
|
99
|
-
|
100
|
-
# Errors
|
101
|
-
|
102
|
-
class APIError < StandardError
|
103
|
-
attr_accessor :fb_error_type, :raw_response
|
104
|
-
def initialize(details = {})
|
105
|
-
self.raw_response = details
|
106
|
-
self.fb_error_type = details["type"]
|
107
|
-
super("#{fb_error_type}: #{details["message"]}")
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
19
|
+
# A Ruby client library for the Facebook Platform.
|
20
|
+
# See http://github.com/arsduo/koala/wiki for a general introduction to Koala
|
21
|
+
# and the Graph API.
|
22
|
+
|
112
23
|
class KoalaError < StandardError; end
|
113
24
|
|
114
|
-
|
115
|
-
# finally, the few things defined on the Koala module itself
|
25
|
+
# Making HTTP requests
|
116
26
|
class << self
|
27
|
+
# Control which HTTP service framework Koala uses.
|
28
|
+
# Primarily used to switch between the mock-request framework used in testing
|
29
|
+
# and the live framework used in real life (and live testing).
|
30
|
+
# In theory, you could write your own HTTPService module if you need different functionality,
|
31
|
+
# but since the switch to {https://github.com/arsduo/koala/wiki/HTTP-Services Faraday} almost all such goals can be accomplished with middleware.
|
117
32
|
attr_accessor :http_service
|
118
33
|
end
|
119
34
|
|
35
|
+
# @private
|
36
|
+
# For current HTTPServices, switch the service as expected.
|
37
|
+
# For deprecated services (Typhoeus and Net::HTTP), print a warning and set the default Faraday adapter appropriately.
|
120
38
|
def self.http_service=(service)
|
121
39
|
if service.respond_to?(:deprecated_interface)
|
122
40
|
# if this is a deprecated module, support the old interface
|
@@ -129,6 +47,7 @@ module Koala
|
|
129
47
|
end
|
130
48
|
end
|
131
49
|
|
50
|
+
# An convenenient alias to Koala.http_service.make_request.
|
132
51
|
def self.make_request(path, args, verb, options = {})
|
133
52
|
http_service.make_request(path, args, verb, options)
|
134
53
|
end
|
data/lib/koala/api.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# graph_batch_api and legacy are required at the bottom, since they depend on API being defined
|
2
|
+
require 'koala/api/graph_api'
|
3
|
+
require 'koala/api/rest_api'
|
4
|
+
|
5
|
+
module Koala
|
6
|
+
module Facebook
|
7
|
+
class API
|
8
|
+
# Creates a new API client.
|
9
|
+
# @param [String] access_token access token
|
10
|
+
# @note If no access token is provided, you can only access some public information.
|
11
|
+
# @return [Koala::Facebook::API] the API client
|
12
|
+
def initialize(access_token = nil)
|
13
|
+
@access_token = access_token
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :access_token
|
17
|
+
|
18
|
+
include GraphAPIMethods
|
19
|
+
include RestAPIMethods
|
20
|
+
|
21
|
+
# Makes a request to the appropriate Facebook API.
|
22
|
+
# @note You'll rarely need to call this method directly.
|
23
|
+
#
|
24
|
+
# @see GraphAPIMethods#graph_call
|
25
|
+
# @see RestAPIMethods#rest_call
|
26
|
+
#
|
27
|
+
# @param path the server path for this request (leading / is prepended if not present)
|
28
|
+
# @param args arguments to be sent to Facebook
|
29
|
+
# @param verb the HTTP method to use
|
30
|
+
# @param options request-related options for Koala and Faraday.
|
31
|
+
# See https://github.com/arsduo/koala/wiki/HTTP-Services for additional options.
|
32
|
+
# @option options [Symbol] :http_component which part of the response (headers, body, or status) to return
|
33
|
+
# @option options [Boolean] :beta use Facebook's beta tier
|
34
|
+
# @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
|
35
|
+
# (All API requests with access tokens use SSL.)
|
36
|
+
# @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
|
37
|
+
#
|
38
|
+
# @yield The response body for evaluation
|
39
|
+
#
|
40
|
+
# @raise [Koala::Facebook::APIError] if Facebook returns an error (response status >= 500)
|
41
|
+
#
|
42
|
+
# @return the body of the response from Facebook (unless another http_component is requested)
|
43
|
+
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
44
|
+
# Fetches the given path in the Graph API.
|
45
|
+
args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
|
46
|
+
|
47
|
+
# add a leading /
|
48
|
+
path = "/#{path}" unless path =~ /^\//
|
49
|
+
|
50
|
+
# make the request via the provided service
|
51
|
+
result = Koala.make_request(path, args, verb, options)
|
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.decode to fail -- so we account for that by wrapping the result in []
|
61
|
+
body = MultiJson.decode("[#{result.body.to_s}]")[0]
|
62
|
+
yield body if error_checking_block
|
63
|
+
|
64
|
+
# if we want a component other than the body (e.g. redirect header for images), return that
|
65
|
+
if component = options[:http_component]
|
66
|
+
component == :response ? result : result.send(options[:http_component])
|
67
|
+
else
|
68
|
+
body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class APIError < StandardError
|
74
|
+
attr_accessor :fb_error_type, :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
|
+
super("#{fb_error_type}: #{details["message"]}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
require 'koala/api/graph_batch_api'
|
92
|
+
# legacy support for old pre-1.2 API interfaces
|
93
|
+
require 'koala/api/legacy'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'koala/api'
|
2
|
+
|
3
|
+
module Koala
|
4
|
+
module Facebook
|
5
|
+
class GraphBatchAPI < API
|
6
|
+
# @private
|
7
|
+
class BatchOperation
|
8
|
+
attr_reader :access_token, :http_options, :post_processing, :files, :batch_api, :identifier
|
9
|
+
|
10
|
+
@identifier = 0
|
11
|
+
|
12
|
+
def self.next_identifier
|
13
|
+
@identifier += 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
@identifier = self.class.next_identifier
|
18
|
+
@args = (options[:args] || {}).dup # because we modify it below
|
19
|
+
@access_token = options[:access_token]
|
20
|
+
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
21
|
+
@batch_args = @http_options.delete(:batch_args) || {}
|
22
|
+
@url = options[:url]
|
23
|
+
@method = options[:method].to_sym
|
24
|
+
@post_processing = options[:post_processing]
|
25
|
+
|
26
|
+
process_binary_args
|
27
|
+
|
28
|
+
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_batch_params(main_access_token)
|
32
|
+
# set up the arguments
|
33
|
+
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
34
|
+
|
35
|
+
response = {
|
36
|
+
:method => @method.to_s,
|
37
|
+
:relative_url => @url,
|
38
|
+
}
|
39
|
+
|
40
|
+
# handle batch-level arguments, such as name, depends_on, and attached_files
|
41
|
+
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
42
|
+
response.merge!(@batch_args) if @batch_args
|
43
|
+
|
44
|
+
# for get and delete, we append args to the URL string
|
45
|
+
# otherwise, they go in the body
|
46
|
+
if args_string.length > 0
|
47
|
+
if args_in_url?
|
48
|
+
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
49
|
+
else
|
50
|
+
response[:body] = args_string if args_string.length > 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def process_binary_args
|
60
|
+
# collect binary files
|
61
|
+
@args.each_pair do |key, value|
|
62
|
+
if UploadableIO.binary_content?(value)
|
63
|
+
@files ||= {}
|
64
|
+
# we use a class-level counter to ensure unique file identifiers across multiple batch operations
|
65
|
+
# (this is thread safe, since we just care about uniqueness)
|
66
|
+
# so remove the file from the original hash and add it to the file store
|
67
|
+
id = "op#{identifier}_file#{@files.keys.length}"
|
68
|
+
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def args_in_url?
|
74
|
+
@method == :get || @method == :delete
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
# legacy support for when BatchOperation lived directly under Koala::Facebook
|
81
|
+
BatchOperation = GraphBatchAPI::BatchOperation
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,476 @@
|
|
1
|
+
require 'koala/api/graph_collection'
|
2
|
+
require 'koala/http_service/uploadable_io'
|
3
|
+
|
4
|
+
module Koala
|
5
|
+
module Facebook
|
6
|
+
GRAPH_SERVER = "graph.facebook.com"
|
7
|
+
|
8
|
+
# Methods used to interact with the Facebook Graph API.
|
9
|
+
#
|
10
|
+
# See https://github.com/arsduo/koala/wiki/Graph-API for a general introduction to Koala
|
11
|
+
# and the Graph API.
|
12
|
+
#
|
13
|
+
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
14
|
+
# events, photos, etc.) and the connections between them (e.g., friends,
|
15
|
+
# photo tags, event RSVPs, etc.). Koala provides access to those
|
16
|
+
# objects types in a generic way. For example, given an OAuth access
|
17
|
+
# token, this will fetch the profile of the active user and the list
|
18
|
+
# of the user's friends:
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# graph = Koala::Facebook::API.new(access_token)
|
22
|
+
# user = graph.get_object("me")
|
23
|
+
# friends = graph.get_connections(user["id"], "friends")
|
24
|
+
#
|
25
|
+
# You can see a list of all of the objects and connections supported
|
26
|
+
# by the API at http://developers.facebook.com/docs/reference/api/.
|
27
|
+
#
|
28
|
+
# You can obtain an access token via OAuth or by using the Facebook JavaScript SDK.
|
29
|
+
# If you're using the JavaScript SDK, you can use the
|
30
|
+
# {Koala::Facebook::OAuth#get_user_from_cookie} method to get the OAuth access token
|
31
|
+
# for the active user from the cookie provided by Facebook.
|
32
|
+
# See the Koala and Facebook documentation for more information.
|
33
|
+
module GraphAPIMethods
|
34
|
+
|
35
|
+
# Objects
|
36
|
+
|
37
|
+
# Get information about a Facebook object.
|
38
|
+
#
|
39
|
+
# @param id the object ID (string or number)
|
40
|
+
# @param args any additional arguments
|
41
|
+
# (fields, metadata, etc. -- see {http://developers.facebook.com/docs/reference/api/ Facebook's documentation})
|
42
|
+
# @param options (see Koala::Facebook::API#api)
|
43
|
+
#
|
44
|
+
# @raise [Koala::Facebook::APIError] if the ID is invalid or you don't have access to that object
|
45
|
+
#
|
46
|
+
# @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)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get information about multiple Facebook objects in one call.
|
53
|
+
#
|
54
|
+
# @param ids an array or comma-separated string of object IDs
|
55
|
+
# @param args (see #get_object)
|
56
|
+
# @param options (see Koala::Facebook::API#api)
|
57
|
+
#
|
58
|
+
# @raise [Koala::Facebook::APIError] if any ID is invalid or you don't have access to that object
|
59
|
+
#
|
60
|
+
# @return an array of object data hashes
|
61
|
+
def get_objects(ids, args = {}, options = {})
|
62
|
+
# Fetchs all of the given objects from the graph.
|
63
|
+
# If any of the IDs are invalid, they'll raise an exception.
|
64
|
+
return [] if ids.empty?
|
65
|
+
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Write an object to the Graph for a specific user.
|
69
|
+
# @see #put_connections
|
70
|
+
#
|
71
|
+
# @note put_object is (for historical reasons) the same as put_connections.
|
72
|
+
# Please use put_connections; in a future version of Koala (2.0?),
|
73
|
+
# 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
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
76
|
+
graph_call("#{parent_object}/#{connection_name}", args, "post", options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Delete an object from the Graph if you have appropriate permissions.
|
80
|
+
#
|
81
|
+
# @param id (see #get_object)
|
82
|
+
# @param options (see #get_object)
|
83
|
+
#
|
84
|
+
# @return true if successful, false (or an APIError) if not
|
85
|
+
def delete_object(id, options = {})
|
86
|
+
# Deletes the object with the given ID from the graph.
|
87
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
|
88
|
+
graph_call(id, {}, "delete", options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Fetch information about a given connection (e.g. type of activity -- feed, events, photos, etc.)
|
92
|
+
# for a specific user.
|
93
|
+
# See {http://developers.facebook.com/docs/api Facebook's documentation} for a complete list of connections.
|
94
|
+
#
|
95
|
+
# @note to access connections like /user_id/CONNECTION/other_user_id,
|
96
|
+
# simply pass "CONNECTION/other_user_id" as the connection_name
|
97
|
+
#
|
98
|
+
# @param id (see #get_object)
|
99
|
+
# @param connection_name what
|
100
|
+
# @param args any additional arguments
|
101
|
+
# @param options (see #get_object)
|
102
|
+
#
|
103
|
+
# @return [Koala::Facebook::API::GraphCollection] an array of object hashes (in most cases)
|
104
|
+
def get_connections(id, connection_name, args = {}, options = {})
|
105
|
+
# Fetchs the connections for given object.
|
106
|
+
graph_call("#{id}/#{connection_name}", args, "get", options)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Write an object to the Graph for a specific user.
|
111
|
+
# See {http://developers.facebook.com/docs/api#publishing Facebook's documentation}
|
112
|
+
# for all the supported writeable objects.
|
113
|
+
#
|
114
|
+
# @note (see #get_connections)
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# graph.put_object("me", "feed", :message => "Hello, world")
|
118
|
+
# => writes "Hello, world" to the active user's wall
|
119
|
+
#
|
120
|
+
# Most write operations require extended permissions. For example,
|
121
|
+
# publishing wall posts requires the "publish_stream" permission. See
|
122
|
+
# http://developers.facebook.com/docs/authentication/ for details about
|
123
|
+
# extended permissions.
|
124
|
+
#
|
125
|
+
# @param id (see #get_object)
|
126
|
+
# @param connection_name (see #get_connections)
|
127
|
+
# @param args (see #get_connections)
|
128
|
+
# @param options (see #get_object)
|
129
|
+
#
|
130
|
+
# @return a hash containing the new object's id
|
131
|
+
def put_connections(id, connection_name, args = {}, options = {})
|
132
|
+
# 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)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Delete an object's connection (for instance, unliking the object).
|
138
|
+
#
|
139
|
+
# @note (see #get_connections)
|
140
|
+
#
|
141
|
+
# @param id (see #get_object)
|
142
|
+
# @param connection_name (see #get_connections)
|
143
|
+
# @args (see #get_connections)
|
144
|
+
# @param options (see #get_object)
|
145
|
+
#
|
146
|
+
# @return (see #delete_object)
|
147
|
+
def delete_connections(id, connection_name, args = {}, options = {})
|
148
|
+
# 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)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Fetches a photo.
|
154
|
+
# (Facebook returns the src of the photo as a response header; this method parses that properly,
|
155
|
+
# unlike using get_connections("photo").)
|
156
|
+
#
|
157
|
+
# @note to delete photos or videos, use delete_object(id)
|
158
|
+
#
|
159
|
+
# @return the URL to the image
|
160
|
+
def get_picture(object, args = {}, options = {})
|
161
|
+
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
162
|
+
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
163
|
+
result["Location"]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Upload a photo.
|
168
|
+
#
|
169
|
+
# This can be called in multiple ways:
|
170
|
+
# put_picture(file, [content_type], ...)
|
171
|
+
# put_picture(path_to_file, [content_type], ...)
|
172
|
+
# put_picture(picture_url, ...)
|
173
|
+
#
|
174
|
+
# You can also pass in uploaded files directly from Rails or Sinatra.
|
175
|
+
# See {https://github.com/arsduo/koala/wiki/Uploading-Photos-and-Videos the Koala wiki} for more information.
|
176
|
+
#
|
177
|
+
# @param args (see #get_object)
|
178
|
+
# @param target_id the Facebook object to which to post the picture (default: "me")
|
179
|
+
# @param options (see #get_object)
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
183
|
+
# put_picture(params[:file], {:message => "Message"})
|
184
|
+
# # with URLs, there's no optional content type field
|
185
|
+
# put_picture(picture_url, {:message => "Message"}, my_page_id)
|
186
|
+
#
|
187
|
+
# @note to access the media after upload, you'll need the user_photos or user_videos permission as appropriate.
|
188
|
+
#
|
189
|
+
# @return (see #put_connections)
|
190
|
+
def put_picture(*picture_args)
|
191
|
+
put_object(*parse_media_args(picture_args, "photos"))
|
192
|
+
end
|
193
|
+
|
194
|
+
# Upload a video. Functions exactly the same as put_picture.
|
195
|
+
# @see #put_picture
|
196
|
+
def put_video(*video_args)
|
197
|
+
args = parse_media_args(video_args, "videos")
|
198
|
+
args.last[:video] = true
|
199
|
+
put_object(*args)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Write directly to the user's wall.
|
203
|
+
# Convenience method equivalent to put_object(id, "feed").
|
204
|
+
#
|
205
|
+
# To get wall posts, use get_connections(user, "feed")
|
206
|
+
# To delete a wall post, use delete_object(post_id)
|
207
|
+
#
|
208
|
+
# @param message the message to write for the wall
|
209
|
+
# @param attachment a hash describing the wall post
|
210
|
+
# (see the {https://developers.facebook.com/docs/guides/attachments/ stream attachments} documentation.)
|
211
|
+
# @param target_id the target wall
|
212
|
+
# @param options (see #get_object)
|
213
|
+
#
|
214
|
+
# @example
|
215
|
+
# @api.put_wall_post("Hello there!", {
|
216
|
+
# "name" => "Link name"
|
217
|
+
# "link" => "http://www.example.com/",
|
218
|
+
# "caption" => "{*actor*} posted a new review",
|
219
|
+
# "description" => "This is a longer description of the attachment",
|
220
|
+
# "picture" => "http://www.example.com/thumbnail.jpg"
|
221
|
+
# })
|
222
|
+
#
|
223
|
+
# @see #put_connections
|
224
|
+
# @return (see #put_connections)
|
225
|
+
def put_wall_post(message, attachment = {}, target_id = "me", options = {})
|
226
|
+
self.put_object(target_id, "feed", attachment.merge({:message => message}), options)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Comment on a given object.
|
230
|
+
# Convenience method equivalent to put_connection(id, "comments").
|
231
|
+
#
|
232
|
+
# To delete comments, use delete_object(comment_id).
|
233
|
+
# To get comments, use get_connections(object, "likes").
|
234
|
+
#
|
235
|
+
# @param id (see #get_object)
|
236
|
+
# @param message the comment to write
|
237
|
+
# @param options (see #get_object)
|
238
|
+
#
|
239
|
+
# @return (see #put_connections)
|
240
|
+
def put_comment(id, message, options = {})
|
241
|
+
# Writes the given comment on the given post.
|
242
|
+
self.put_object(id, "comments", {:message => message}, options)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Like a given object.
|
246
|
+
# Convenience method equivalent to put_connections(id, "likes").
|
247
|
+
#
|
248
|
+
# To get a list of a user's or object's likes, use get_connections(id, "likes").
|
249
|
+
#
|
250
|
+
# @param id (see #get_object)
|
251
|
+
# @param options (see #get_object)
|
252
|
+
#
|
253
|
+
# @return (see #put_connections)
|
254
|
+
def put_like(id, options = {})
|
255
|
+
# Likes the given post.
|
256
|
+
self.put_object(id, "likes", {}, options)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Unlike a given object.
|
260
|
+
# Convenience method equivalent to delete_connection(id, "likes").
|
261
|
+
#
|
262
|
+
# @param id (see #get_object)
|
263
|
+
# @param options (see #get_object)
|
264
|
+
#
|
265
|
+
# @return (see #delete_object)
|
266
|
+
def delete_like(id, options = {})
|
267
|
+
# Unlikes a given object for the logged-in user
|
268
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
|
269
|
+
graph_call("#{id}/likes", {}, "delete", options)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Search for a given query among visible Facebook objects.
|
273
|
+
# See {http://developers.facebook.com/docs/reference/api/#searching Facebook documentation} for more information.
|
274
|
+
#
|
275
|
+
# @param search_terms the query to search for
|
276
|
+
# @param args additional arguments, such as type, fields, etc.
|
277
|
+
# @param options (see #get_object)
|
278
|
+
#
|
279
|
+
# @return [Koala::Facebook::API::GraphCollection] an array of search results
|
280
|
+
def search(search_terms, args = {}, options = {})
|
281
|
+
args.merge!({:q => search_terms}) unless search_terms.nil?
|
282
|
+
graph_call("search", args, "get", options)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Convenience Methods
|
286
|
+
# In general, we're trying to avoid adding convenience methods to Koala
|
287
|
+
# except to support cases where the Facebook API requires non-standard input
|
288
|
+
# such as JSON-encoding arguments, posts directly to objects, etc.
|
289
|
+
|
290
|
+
# Make an FQL query.
|
291
|
+
# Convenience method equivalent to get_object("fql", :q => query).
|
292
|
+
#
|
293
|
+
# @param query the FQL query to perform
|
294
|
+
# @param args (see #get_object)
|
295
|
+
# @param options (see #get_object)
|
296
|
+
def fql_query(query, args = {}, options = {})
|
297
|
+
get_object("fql", args.merge(:q => query), options)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Make an FQL multiquery.
|
301
|
+
# This method simplifies the result returned from multiquery into a more logical format.
|
302
|
+
#
|
303
|
+
# @param queries a hash of query names => FQL queries
|
304
|
+
# @param args (see #get_object)
|
305
|
+
# @param options (see #get_object)
|
306
|
+
#
|
307
|
+
# @example
|
308
|
+
# @api.fql_multiquery({
|
309
|
+
# "query1" => "select post_id from stream where source_id = me()",
|
310
|
+
# "query2" => "select fromid from comment where post_id in (select post_id from #query1)"
|
311
|
+
# })
|
312
|
+
# # returns {"query1" => [obj1, obj2, ...], "query2" => [obj3, ...]}
|
313
|
+
# # instead of [{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]}]
|
314
|
+
#
|
315
|
+
# @return a hash of FQL results keyed to the appropriate query
|
316
|
+
def fql_multiquery(queries = {}, args = {}, options = {})
|
317
|
+
if results = get_object("fql", args.merge(:q => MultiJson.encode(queries)), options)
|
318
|
+
# simplify the multiquery result format
|
319
|
+
results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Get a page's access token, allowing you to act as the page.
|
324
|
+
# Convenience method for @api.get_object(page_id, :fields => "access_token").
|
325
|
+
#
|
326
|
+
# @param id the page ID
|
327
|
+
# @param args (see #get_object)
|
328
|
+
# @param options (see #get_object)
|
329
|
+
#
|
330
|
+
# @return the page's access token (discarding expiration and any other information)
|
331
|
+
def get_page_access_token(id, args = {}, options = {})
|
332
|
+
result = get_object(id, args.merge(:fields => "access_token"), options) do
|
333
|
+
result ? result["access_token"] : nil
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Fetchs the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
|
338
|
+
# See https://developers.facebook.com/blog/post/490.
|
339
|
+
#
|
340
|
+
# @param urls the URLs for which you want comments
|
341
|
+
# @param args (see #get_object)
|
342
|
+
# @param options (see #get_object)
|
343
|
+
#
|
344
|
+
# @returns a hash of urls => comment arrays
|
345
|
+
def get_comments_for_urls(urls = [], args = {}, options = {})
|
346
|
+
return [] if urls.empty?
|
347
|
+
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
348
|
+
get_object("comments", args, options)
|
349
|
+
end
|
350
|
+
|
351
|
+
def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {})
|
352
|
+
graph_call(app_id, args.merge(:restrictions => MultiJson.encode(restrictions_hash)), "post", options)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Certain calls such as {#get_connections} return an array of results which you can page through
|
356
|
+
# forwards and backwards (to see more feed stories, search results, etc.).
|
357
|
+
# Those methods use get_page to request another set of results from Facebook.
|
358
|
+
#
|
359
|
+
# @note You'll rarely need to use this method unless you're using Sinatra or another non-Rails framework
|
360
|
+
# (see {Koala::Facebook::GraphCollection GraphCollection} for more information).
|
361
|
+
#
|
362
|
+
# @param params an array of arguments to graph_call
|
363
|
+
# as returned by {Koala::Facebook::GraphCollection.parse_page_url}.
|
364
|
+
#
|
365
|
+
# @return Koala::Facebook::GraphCollection the appropriate page of results (an empty array if there are none)
|
366
|
+
def get_page(params)
|
367
|
+
graph_call(*params)
|
368
|
+
end
|
369
|
+
|
370
|
+
# Execute a set of Graph API calls as a batch.
|
371
|
+
# See {https://github.com/arsduo/koala/wiki/Batch-requests batch request documentation}
|
372
|
+
# for more information and examples.
|
373
|
+
#
|
374
|
+
# @param http_options HTTP options for the entire request.
|
375
|
+
#
|
376
|
+
# @yield batch_api [Koala::Facebook::GraphBatchAPI] an API subclass
|
377
|
+
# whose requests will be queued and executed together at the end of the block
|
378
|
+
#
|
379
|
+
# @raise [Koala::Facebook::APIError] only if there is a problem with the overall batch request
|
380
|
+
# (e.g. connectivity failure, an operation with a missing dependency).
|
381
|
+
# Individual calls that error out will be represented as an unraised
|
382
|
+
# APIError in the appropriate spot in the results array.
|
383
|
+
#
|
384
|
+
# @example
|
385
|
+
# results = @api.batch do |batch_api|
|
386
|
+
# batch_api.get_object('me')
|
387
|
+
# batch_api.get_object(KoalaTest.user1)
|
388
|
+
# end
|
389
|
+
# # => [{"id" => my_id, ...}, {"id"" => koppel_id, ...}]
|
390
|
+
#
|
391
|
+
# @return an array of results from your batch calls (as if you'd made them individually),
|
392
|
+
# arranged in the same order they're made.
|
393
|
+
def batch(http_options = {}, &block)
|
394
|
+
batch_client = GraphBatchAPI.new(access_token, self)
|
395
|
+
if block
|
396
|
+
yield batch_client
|
397
|
+
batch_client.execute(http_options)
|
398
|
+
else
|
399
|
+
batch_client
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# Make a call directly to the Graph API.
|
404
|
+
# (See any of the other methods for example invocations.)
|
405
|
+
#
|
406
|
+
# @param path the Graph API path to query (no leading / needed)
|
407
|
+
# @param args (see #get_object)
|
408
|
+
# @param verb the type of HTTP request to make (get, post, delete, etc.)
|
409
|
+
# @options (see #get_object)
|
410
|
+
#
|
411
|
+
# @yield response when making a batch API call, you can pass in a block
|
412
|
+
# that parses the results, allowing for cleaner code.
|
413
|
+
# The block's return value is returned in the batch results.
|
414
|
+
# See the code for {#get_picture} or {#fql_multiquery} for examples.
|
415
|
+
# (Not needed in regular calls; you'll probably rarely use this.)
|
416
|
+
#
|
417
|
+
# @raise [Koala::Facebook::APIError] if Facebook returns an error
|
418
|
+
#
|
419
|
+
# @return the result from Facebook
|
420
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
421
|
+
result = api(path, args, verb, options) do |response|
|
422
|
+
error = check_response(response)
|
423
|
+
raise error if error
|
424
|
+
end
|
425
|
+
|
426
|
+
# turn this into a GraphCollection if it's pageable
|
427
|
+
result = GraphCollection.evaluate(result, self)
|
428
|
+
|
429
|
+
# now process as appropriate for the given call (get picture header, etc.)
|
430
|
+
post_processing ? post_processing.call(result) : result
|
431
|
+
end
|
432
|
+
|
433
|
+
private
|
434
|
+
|
435
|
+
def check_response(response)
|
436
|
+
# check for Graph API-specific errors
|
437
|
+
# this returns an error, which is immediately raised (non-batch)
|
438
|
+
# or added to the list of batch results (batch)
|
439
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
440
|
+
APIError.new(error_details)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def parse_media_args(media_args, method)
|
445
|
+
# photo and video uploads can accept different types of arguments (see above)
|
446
|
+
# so here, we parse the arguments into a form directly usable in put_object
|
447
|
+
raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
|
448
|
+
|
449
|
+
args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
|
450
|
+
|
451
|
+
args = media_args[1 + args_offset] || {}
|
452
|
+
target_id = media_args[2 + args_offset] || "me"
|
453
|
+
options = media_args[3 + args_offset] || {}
|
454
|
+
|
455
|
+
if url?(media_args.first)
|
456
|
+
# If media_args is a URL, we can upload without UploadableIO
|
457
|
+
args.merge!(:url => media_args.first)
|
458
|
+
else
|
459
|
+
args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
|
460
|
+
end
|
461
|
+
|
462
|
+
[target_id, method, args, options]
|
463
|
+
end
|
464
|
+
|
465
|
+
def url?(data)
|
466
|
+
return false unless data.is_a? String
|
467
|
+
begin
|
468
|
+
uri = URI.parse(data)
|
469
|
+
%w( http https ).include?(uri.scheme)
|
470
|
+
rescue URI::BadURIError
|
471
|
+
false
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|