cbaclig-koala 0.5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +42 -0
- data/Manifest +12 -0
- data/Rakefile +15 -0
- data/cbaclig-koala.gemspec +30 -0
- data/init.rb +2 -0
- data/lib/http_services.rb +58 -0
- data/lib/koala.rb +317 -0
- data/readme.md +39 -0
- data/test/facebook_data.yml +31 -0
- data/test/koala/facebook_no_access_token_tests.rb +128 -0
- data/test/koala/facebook_oauth_tests.rb +202 -0
- data/test/koala/facebook_with_access_token_tests.rb +155 -0
- data/test/koala_tests.rb +35 -0
- metadata +83 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
v0.5.0.1
|
2
|
+
-- Added #fql method to allow easy FQL queries
|
3
|
+
-- Added fql tests
|
4
|
+
|
5
|
+
v0.5.0
|
6
|
+
-- Added several new OAuth methods for making and parsing access token requests
|
7
|
+
-- Added test suite for the OAuth class
|
8
|
+
-- Made second argument to url_for_access_token a hash (strings still work but trigger a deprecation warning)
|
9
|
+
-- Added fields to facebook_data.yml
|
10
|
+
-- Updated readme
|
11
|
+
|
12
|
+
v0.4.1
|
13
|
+
-- Encapsulated GraphAPI and OAuth classes in the Koala::Facebook module for clarity (and to avoid claiming the global Facebook class)
|
14
|
+
-- Moved make_request method to Koala class from GraphAPI instance (for use by future OAuth class functionality)
|
15
|
+
-- Renamed request method to api for consistancy with Javascript library
|
16
|
+
-- Updated tests and readme
|
17
|
+
|
18
|
+
v0.4.0
|
19
|
+
-- Adopted the Koala name
|
20
|
+
-- Updated readme and tests
|
21
|
+
-- Fixed cookie verification bug for non-expiring OAuth tokens
|
22
|
+
|
23
|
+
v0.3.1
|
24
|
+
-- Bug fixes.
|
25
|
+
|
26
|
+
v0.3
|
27
|
+
-- Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
|
28
|
+
-- Created FacebookGraph::OAuth class for tokens and OAuth URLs
|
29
|
+
-- Updated method for including HTTP service (think we've got it this time)
|
30
|
+
-- Updated tests
|
31
|
+
-- Added CHANGELOG and gemspec
|
32
|
+
|
33
|
+
v0.2
|
34
|
+
-- Gemified the project
|
35
|
+
-- Split out HTTP services into their own file, and adjusted inclusion method
|
36
|
+
|
37
|
+
v0.1
|
38
|
+
-- Added modular support for Typhoeus
|
39
|
+
-- Added tests
|
40
|
+
|
41
|
+
v0.0
|
42
|
+
-- Hi from F8! Basic read/write from the graph is working
|
data/Manifest
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
Manifest
|
3
|
+
Rakefile
|
4
|
+
init.rb
|
5
|
+
lib/http_services.rb
|
6
|
+
lib/koala.rb
|
7
|
+
readme.md
|
8
|
+
test/facebook_data.yml
|
9
|
+
test/koala/facebook_no_access_token_tests.rb
|
10
|
+
test/koala/facebook_oauth_tests.rb
|
11
|
+
test/koala/facebook_with_access_token_tests.rb
|
12
|
+
test/koala_tests.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Rakefile
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'echoe'
|
5
|
+
|
6
|
+
# gem management
|
7
|
+
Echoe.new('cbaclig-koala') do |p|
|
8
|
+
p.summary = "A lightweight, flexible library for Facebook's new Graph API"
|
9
|
+
p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook's new Graph API. It allows read/write access to the Facebook Graph and provides OAuth URLs and cookie validation for Facebook Connect sites. Koala supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services."
|
10
|
+
p.url = "http://github.com/arsduo/koala"
|
11
|
+
p.author = ["Alex Koppel", "Rafi Jacoby", "Context Optional"]
|
12
|
+
p.email = "alex@alexkoppel.com"
|
13
|
+
p.ignore_pattern = ["tmp/*", "script/*", "pkg/*"]
|
14
|
+
p.development_dependencies = []
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cbaclig-koala}
|
5
|
+
s.version = "0.5.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Alex Koppel, Rafi Jacoby, Context Optional"]
|
9
|
+
s.date = %q{2010-05-11}
|
10
|
+
s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook's new Graph API. It allows read/write access to the Facebook Graph and provides OAuth URLs and cookie validation for Facebook Connect sites. Koala supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
|
11
|
+
s.email = %q{alex@alexkoppel.com}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/http_services.rb", "lib/koala.rb"]
|
13
|
+
s.files = ["CHANGELOG", "Manifest", "Rakefile", "init.rb", "lib/http_services.rb", "lib/koala.rb", "readme.md", "test/facebook_data.yml", "test/koala/facebook_no_access_token_tests.rb", "test/koala/facebook_oauth_tests.rb", "test/koala/facebook_with_access_token_tests.rb", "test/koala_tests.rb", "cbaclig-koala.gemspec"]
|
14
|
+
s.homepage = %q{http://github.com/arsduo/koala}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Cbaclig-koala", "--main", "readme.md"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{cbaclig-koala}
|
18
|
+
s.rubygems_version = %q{1.3.6}
|
19
|
+
s.summary = %q{A lightweight, flexible library for Facebook's new Graph API}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Koala
|
2
|
+
module NetHTTPService
|
3
|
+
# this service uses Net::HTTP to send requests to the graph
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
require 'net/http' unless defined?(Net::HTTP)
|
7
|
+
require 'net/https'
|
8
|
+
|
9
|
+
def self.make_request(path, args, verb, graph = true)
|
10
|
+
# We translate args to a valid query string. If post is specified,
|
11
|
+
# we send a POST request to the given path with the given arguments.
|
12
|
+
|
13
|
+
# if the verb isn't get or post, send it as a post argument
|
14
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
15
|
+
|
16
|
+
http = Net::HTTP.new(graph ? Facebook::GRAPH_SERVER : Facebook::REST_SERVER, 443)
|
17
|
+
http.use_ssl = true
|
18
|
+
# we turn off certificate validation to avoid the
|
19
|
+
# "warning: peer certificate won't be verified in this SSL session" warning
|
20
|
+
# not sure if this is the right way to handle it
|
21
|
+
# see http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
|
22
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
23
|
+
|
24
|
+
result = http.start { |http|
|
25
|
+
response, body = (verb == "post" ? http.post(path, encode_params(args)) : http.get("#{path}?#{encode_params(args)}"))
|
26
|
+
body
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
def self.encode_params(param_hash)
|
32
|
+
# TODO investigating whether a built-in method handles this
|
33
|
+
# if no hash (e.g. no auth token) return empty string
|
34
|
+
((param_hash || {}).collect do |key_and_value|
|
35
|
+
key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
|
36
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
37
|
+
end).join("&")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TyphoeusService
|
44
|
+
# this service uses Typhoeus to send requests to the graph
|
45
|
+
def self.included(base)
|
46
|
+
base.class_eval do
|
47
|
+
require 'typhoeus' unless defined?(Typhoeus)
|
48
|
+
include Typhoeus
|
49
|
+
|
50
|
+
def self.make_request(path, args, verb, graph = true)
|
51
|
+
# if the verb isn't get or post, send it as a post argument
|
52
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
53
|
+
self.send(verb, "https://#{graph ? Facebook::GRAPH_SERVER : Facebook::REST_SERVER}/#{path}", :params => args).body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/koala.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
# rubygems is required to support json, how facebook returns data
|
5
|
+
require 'rubygems'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
# include default http services
|
9
|
+
require 'http_services'
|
10
|
+
|
11
|
+
module Koala
|
12
|
+
|
13
|
+
module Facebook
|
14
|
+
# Ruby client library for the Facebook Platform.
|
15
|
+
# Copyright 2010 Facebook
|
16
|
+
# Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional
|
17
|
+
#
|
18
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
19
|
+
# not use this file except in compliance with the License. You may obtain
|
20
|
+
# a copy of the License at
|
21
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
22
|
+
#
|
23
|
+
# Unless required by applicable law or agreed to in writing, software
|
24
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
25
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
26
|
+
# License for the specific language governing permissions and limitations
|
27
|
+
# under the License.
|
28
|
+
#
|
29
|
+
# This client library is designed to support the Graph API and the official
|
30
|
+
# Facebook JavaScript SDK, which is the canonical way to implement
|
31
|
+
# Facebook authentication. Read more about the Graph API at
|
32
|
+
# http://developers.facebook.com/docs/api. You can download the Facebook
|
33
|
+
# JavaScript SDK at http://github.com/facebook/connect-js/.
|
34
|
+
|
35
|
+
GRAPH_SERVER = "graph.facebook.com"
|
36
|
+
REST_SERVER = "api.facebook.com"
|
37
|
+
|
38
|
+
class GraphAPI
|
39
|
+
# A client for the Facebook Graph API.
|
40
|
+
#
|
41
|
+
# See http://developers.facebook.com/docs/api for complete documentation
|
42
|
+
# for the API.
|
43
|
+
#
|
44
|
+
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
45
|
+
# events, photos) and the connections between them (e.g., friends,
|
46
|
+
# photo tags, and event RSVPs). This client provides access to those
|
47
|
+
# primitive types in a generic way. For example, given an OAuth access
|
48
|
+
# token, this will fetch the profile of the active user and the list
|
49
|
+
# of the user's friends:
|
50
|
+
#
|
51
|
+
# graph = Koala::Facebook::GraphAPI.new(access_token)
|
52
|
+
# user = graph.get_object("me")
|
53
|
+
# friends = graph.get_connections(user["id"], "friends")
|
54
|
+
#
|
55
|
+
# You can see a list of all of the objects and connections supported
|
56
|
+
# by the API at http://developers.facebook.com/docs/reference/api/.
|
57
|
+
#
|
58
|
+
# You can obtain an access token via OAuth or by using the Facebook
|
59
|
+
# JavaScript SDK. See http://developers.facebook.com/docs/authentication/
|
60
|
+
# for details.
|
61
|
+
#
|
62
|
+
# If you are using the JavaScript SDK, you can use the
|
63
|
+
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
64
|
+
# for the active user from the cookie saved by the SDK.
|
65
|
+
|
66
|
+
# initialize with an access token
|
67
|
+
def initialize(access_token = nil)
|
68
|
+
@access_token = access_token
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_object(id, args = {})
|
72
|
+
# Fetchs the given object from the graph.
|
73
|
+
api(id, args)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_objects(ids, args = {})
|
77
|
+
# Fetchs all of the given object from the graph.
|
78
|
+
# We return a map from ID to object. If any of the IDs are invalid,
|
79
|
+
# we raise an exception.
|
80
|
+
api("", args.merge("ids" => ids.join(",")))
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_connections(id, connection_name, args = {})
|
84
|
+
# Fetchs the connections for given object.
|
85
|
+
api("#{id}/#{connection_name}", args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def put_object(parent_object, connection_name, args = {})
|
89
|
+
# Writes the given object to the graph, connected to the given parent.
|
90
|
+
#
|
91
|
+
# For example,
|
92
|
+
#
|
93
|
+
# graph.put_object("me", "feed", :message => "Hello, world")
|
94
|
+
#
|
95
|
+
# writes "Hello, world" to the active user's wall. Likewise, this
|
96
|
+
# will comment on a the first post of the active user's feed:
|
97
|
+
#
|
98
|
+
# feed = graph.get_connections("me", "feed")
|
99
|
+
# post = feed["data"][0]
|
100
|
+
# graph.put_object(post["id"], "comments", :message => "First!")
|
101
|
+
#
|
102
|
+
# See http://developers.facebook.com/docs/api#publishing for all of
|
103
|
+
# the supported writeable objects.
|
104
|
+
#
|
105
|
+
# Most write operations require extended permissions. For example,
|
106
|
+
# publishing wall posts requires the "publish_stream" permission. See
|
107
|
+
# http://developers.facebook.com/docs/authentication/ for details about
|
108
|
+
# extended permissions.
|
109
|
+
|
110
|
+
raise GraphAPIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
111
|
+
api("#{parent_object}/#{connection_name}", args, "post")
|
112
|
+
end
|
113
|
+
|
114
|
+
def put_wall_post(message, attachment = {}, profile_id = "me")
|
115
|
+
# Writes a wall post to the given profile's wall.
|
116
|
+
#
|
117
|
+
# We default to writing to the authenticated user's wall if no
|
118
|
+
# profile_id is specified.
|
119
|
+
#
|
120
|
+
# attachment adds a structured attachment to the status message being
|
121
|
+
# posted to the Wall. It should be a dictionary of the form:
|
122
|
+
#
|
123
|
+
# {"name": "Link name"
|
124
|
+
# "link": "http://www.example.com/",
|
125
|
+
# "caption": "{*actor*} posted a new review",
|
126
|
+
# "description": "This is a longer description of the attachment",
|
127
|
+
# "picture": "http://www.example.com/thumbnail.jpg"}
|
128
|
+
|
129
|
+
self.put_object(profile_id, "feed", attachment.merge({:message => message}))
|
130
|
+
end
|
131
|
+
|
132
|
+
def put_comment(object_id, message)
|
133
|
+
# Writes the given comment on the given post.
|
134
|
+
self.put_object(object_id, "comments", {:message => message})
|
135
|
+
end
|
136
|
+
|
137
|
+
def put_like(object_id)
|
138
|
+
# Likes the given post.
|
139
|
+
self.put_object(object_id, "likes")
|
140
|
+
end
|
141
|
+
|
142
|
+
def delete_object(id)
|
143
|
+
# Deletes the object with the given ID from the graph.
|
144
|
+
api(id, {}, "delete")
|
145
|
+
end
|
146
|
+
|
147
|
+
def search(search_terms, args = {})
|
148
|
+
# Searches for a given term
|
149
|
+
api("search", args.merge({:q => search_terms}))
|
150
|
+
end
|
151
|
+
|
152
|
+
def api(path, args = {}, verb = "get")
|
153
|
+
# Fetches the given path in the Graph API.
|
154
|
+
args["access_token"] = @access_token if @access_token
|
155
|
+
|
156
|
+
# make the request via the provided service
|
157
|
+
result = Koala.make_request(path, args, verb)
|
158
|
+
|
159
|
+
# Facebook sometimes sends results like "true" and "false", which aren't strictly object
|
160
|
+
# and cause JSON.parse to fail
|
161
|
+
# so we account for that
|
162
|
+
response = JSON.parse("[#{result}]")[0]
|
163
|
+
|
164
|
+
# check for errors
|
165
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
166
|
+
raise GraphAPIError.new(error_details)
|
167
|
+
end
|
168
|
+
|
169
|
+
response
|
170
|
+
end
|
171
|
+
|
172
|
+
def fql(query)
|
173
|
+
args = {
|
174
|
+
"query" => query,
|
175
|
+
"format" => "json",
|
176
|
+
}
|
177
|
+
|
178
|
+
# Adds access_token if available
|
179
|
+
args["access_token"] = @access_token if @access_token
|
180
|
+
|
181
|
+
response = Koala.make_request('method/fql.query', args, 'get', false)
|
182
|
+
|
183
|
+
# Facebook sometimes sends results like "true" and "false", which aren't strictly object
|
184
|
+
# and cause JSON.parse to fail
|
185
|
+
# so we account for that
|
186
|
+
response = JSON.parse("[#{response}]")[0]
|
187
|
+
|
188
|
+
# check for errors
|
189
|
+
if response.is_a?(Hash) && response["error_code"]
|
190
|
+
raise RestAPIError.new(response)
|
191
|
+
end
|
192
|
+
|
193
|
+
response
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class GraphAPIError < Exception
|
198
|
+
attr_accessor :fb_error_type
|
199
|
+
def initialize(details = {})
|
200
|
+
self.fb_error_type = details["type"]
|
201
|
+
super("#{fb_error_type}: #{details["message"]}")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class RestAPIError < Exception
|
206
|
+
attr_accessor :fb_error_code
|
207
|
+
def initialize(details = {})
|
208
|
+
self.fb_error_code = details["error_code"]
|
209
|
+
super("#{fb_error_code}: #{details["error_msg"]} [#{details["request_args"].map{|h| "#{h['key']}=#{h['value']}" }.join('&')}]")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
class OAuth
|
215
|
+
attr_accessor :app_id, :app_secret, :oauth_callback_url
|
216
|
+
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
217
|
+
@app_id = app_id
|
218
|
+
@app_secret = app_secret
|
219
|
+
@oauth_callback_url = oauth_callback_url
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_user_from_cookie(cookie_hash)
|
223
|
+
# Parses the cookie set by the official Facebook JavaScript SDK.
|
224
|
+
#
|
225
|
+
# cookies should be a dictionary-like object mapping cookie names to
|
226
|
+
# cookie values.
|
227
|
+
#
|
228
|
+
# If the user is logged in via Facebook, we return a dictionary with the
|
229
|
+
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
230
|
+
# and the latter can be used to make authenticated requests to the Graph API.
|
231
|
+
# If the user is not logged in, we return None.
|
232
|
+
#
|
233
|
+
# Download the official Facebook JavaScript SDK at
|
234
|
+
# http://github.com/facebook/connect-js/. Read more about Facebook
|
235
|
+
# authentication at http://developers.facebook.com/docs/authentication/.
|
236
|
+
|
237
|
+
if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
|
238
|
+
# remove the opening/closing quote
|
239
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
240
|
+
|
241
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
242
|
+
components = {}
|
243
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
244
|
+
|
245
|
+
# generate the signature and make sure it matches what we expect
|
246
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
247
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
248
|
+
sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
|
249
|
+
end
|
250
|
+
end
|
251
|
+
alias_method :get_user_from_cookies, :get_user_from_cookie
|
252
|
+
|
253
|
+
def url_for_oauth_code(options = {})
|
254
|
+
# for permissions, see http://developers.facebook.com/docs/authentication/permissions
|
255
|
+
permissions = options[:permissions]
|
256
|
+
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
257
|
+
|
258
|
+
callback = options[:callback] || @oauth_callback_url
|
259
|
+
raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
|
260
|
+
|
261
|
+
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
262
|
+
"https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
|
263
|
+
end
|
264
|
+
|
265
|
+
def url_for_access_token(code, options = {})
|
266
|
+
# Creates the URL for the token corresponding to a given code generated by Facebook
|
267
|
+
if options.is_a?(String) # changing the arguments
|
268
|
+
puts "Deprecation warning: url_for_access_token now takes an options hash as the second argument; pass the callback as :callback."
|
269
|
+
options = {:callback => options}
|
270
|
+
end
|
271
|
+
callback = options[:callback] || @oauth_callback_url
|
272
|
+
raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
|
273
|
+
"https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
274
|
+
end
|
275
|
+
|
276
|
+
def parse_access_token(response_text)
|
277
|
+
components = response_text.split("&").inject({}) do |hash, bit|
|
278
|
+
key, value = bit.split("=")
|
279
|
+
hash.merge!(key => value)
|
280
|
+
end
|
281
|
+
components
|
282
|
+
end
|
283
|
+
|
284
|
+
def fetch_token_string(code)
|
285
|
+
Koala.make_request("oauth/access_token", {
|
286
|
+
:client_id => @app_id,
|
287
|
+
:redirect_uri => @oauth_callback_url,
|
288
|
+
:client_secret => @app_secret,
|
289
|
+
:code => code
|
290
|
+
}, "get")
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_access_token(code)
|
294
|
+
result = fetch_token_string(code)
|
295
|
+
|
296
|
+
# if we have an error, parse the error JSON and raise an error
|
297
|
+
raise GraphAPIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
|
298
|
+
# otherwise, parse the access token
|
299
|
+
parse_access_token(result)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# finally, set up the http service Koala methods used to make requests
|
305
|
+
# you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
|
306
|
+
def self.http_service=(service)
|
307
|
+
self.send(:include, service)
|
308
|
+
end
|
309
|
+
|
310
|
+
# by default, try requiring Typhoeus -- if that works, use it
|
311
|
+
begin
|
312
|
+
require 'typhoeus'
|
313
|
+
Koala.http_service = TyphoeusService
|
314
|
+
rescue LoadError
|
315
|
+
Koala.http_service = NetHTTPService
|
316
|
+
end
|
317
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
Koala
|
2
|
+
====
|
3
|
+
Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook Graph library for Ruby. We wrote Koala with four goals:
|
4
|
+
|
5
|
+
* Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 300 lines of code.)
|
6
|
+
* Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
|
7
|
+
* Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
|
8
|
+
* Tested: Koala should have complete test coverage, so you can rely on it. (By the time you read this the final tests are being written.)
|
9
|
+
|
10
|
+
Basic usage:
|
11
|
+
|
12
|
+
graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
|
13
|
+
profile = graph.get_object("me")
|
14
|
+
friends = graph.get_connections("me", "friends")
|
15
|
+
graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
16
|
+
|
17
|
+
If you're using Koala within a web application with the Facebook
|
18
|
+
[JavaScript SDK](http://github.com/facebook/connect-js), you can use the Koala::Facebook::OAuth class
|
19
|
+
to parse the cookies set by the JavaScript SDK for logged in users.
|
20
|
+
|
21
|
+
Examples and More Details
|
22
|
+
-----
|
23
|
+
There's a very detailed description and walkthrough of Koala at <a href="http://blog.twoalex.com/2010/05/03/introducing-koala-a-new-gem-for-facebooks-new-graph-api/">http://blog.twoalex.com/2010/05/03/introducing-koala-a-new-gem-for-facebooks-new-graph-api/</a>.
|
24
|
+
|
25
|
+
You can easily generate OAuth access tokens and any other data needed to play with the Graph API or OAuth at the Koala-powered <a href="http://oauth.twoalex.com" target="_blank">OAuth Playground</a>.
|
26
|
+
|
27
|
+
|
28
|
+
Testing
|
29
|
+
-----
|
30
|
+
|
31
|
+
Unit tests are provided for all of Koala's methods; however, because the OAuth access tokens and cookies expire, you have to provide some of your own data: a valid OAuth access token with publish_stream and read_stream permissions and an OAuth code that can be used to generate an access token. (The file also provides valid values for other tests, which you're welcome to sub out for data specific to your own application.)
|
32
|
+
|
33
|
+
Insert the required values into the file test/facebook_data.yml, then run the test as follows:
|
34
|
+
spec koala_tests.rb
|
35
|
+
|
36
|
+
|
37
|
+
Known Issues
|
38
|
+
-----
|
39
|
+
1. gem install koala produces "Could not find main page readme.md" message
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Check out http://oauth.twoalex.com/ to easily generate tokens, cookies, etc.
|
2
|
+
# Those values will work with the default settings in this yaml.
|
3
|
+
# Of course, you can change this to work with your own app.
|
4
|
+
# Just remember to update all fields!
|
5
|
+
|
6
|
+
# You must supply this value yourself to test the GraphAPI class.
|
7
|
+
# Your OAuth token should have publish_stream and read_stream permissions
|
8
|
+
oauth_token:
|
9
|
+
|
10
|
+
|
11
|
+
# for testing the OAuth class
|
12
|
+
# baseline app
|
13
|
+
oauth_test_data:
|
14
|
+
# You must supply this value yourself, since they will expire.
|
15
|
+
code:
|
16
|
+
|
17
|
+
# These values will work out of the box
|
18
|
+
app_id: 119908831367602
|
19
|
+
secret: e45e55a333eec232d4206d2703de1307
|
20
|
+
callback_url: http://oauth.twoalex.com/
|
21
|
+
raw_token_string: "access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.&expires=6621"
|
22
|
+
raw_offline_access_token_string: access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.
|
23
|
+
valid_cookies:
|
24
|
+
# note: the tests stub the time class so these default cookies are always valid (if you're using the default app)
|
25
|
+
# if not you may want to remove the stubbing to test expiration
|
26
|
+
fbs_119908831367602: '"access_token=119908831367602|2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623|CMpi0AYbn03Oukzv94AUha2qbO4.&expires=1273363200&secret=lT_9zm5r5IbJ6Aa5O54nFw__&session_key=2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623&sig=9515e93113921f9476a4efbdd4a3c746&uid=2905623"'
|
27
|
+
expired_cookies:
|
28
|
+
fbs_119908831367602: '"access_token=119908831367602|2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623|yVt5WH_S6J5p3gFa5_5lBzckhws.&expires=1273287600&secret=V_E79ovQnXqxGctFuC_n5A__&session_key=2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623&sig=eeef60838c0c800258d89b7e6ddddddb&uid=2905623"'
|
29
|
+
offline_access_cookies:
|
30
|
+
# note: I've revoked the offline access for security reasons, so you can't make calls against this :)
|
31
|
+
fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
|
@@ -0,0 +1,128 @@
|
|
1
|
+
class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
2
|
+
|
3
|
+
describe "Koala GraphAPI without an access token" do
|
4
|
+
before :each do
|
5
|
+
@graph = Koala::Facebook::GraphAPI.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should get public data about a user" do
|
9
|
+
result = @graph.get_object("koppel")
|
10
|
+
# the results should have an ID and a name, among other things
|
11
|
+
(result["id"] && result["name"]).should
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not get private data about a user" do
|
15
|
+
result = @graph.get_object("koppel")
|
16
|
+
# updated_time should be a pretty fixed test case
|
17
|
+
result["updated_time"].should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
it "should get public data about a Page" do
|
22
|
+
result = @graph.get_object("contextoptional")
|
23
|
+
# the results should have an ID and a name, among other things
|
24
|
+
(result["id"] && result["name"]).should
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not be able to get data about 'me'" do
|
28
|
+
begin
|
29
|
+
@graph.get_object("me")
|
30
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
31
|
+
rescue Exception => wrong_err
|
32
|
+
end
|
33
|
+
@right_err.should_not be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to get multiple objects" do
|
37
|
+
results = @graph.get_objects(["contextoptional", "naitik"])
|
38
|
+
results.length.should == 2
|
39
|
+
end
|
40
|
+
|
41
|
+
it "shouldn't be able to access connections from users" do
|
42
|
+
begin
|
43
|
+
@graph.get_connections("lukeshepard", "likes")
|
44
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
45
|
+
rescue Exception => wrong_err
|
46
|
+
end
|
47
|
+
@right_err.should_not be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should be able to access connections from public Pages" do
|
51
|
+
result = @graph.get_connections("contextoptional", "likes")
|
52
|
+
result["data"].should be_a(Array)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not be able to put an object" do
|
56
|
+
begin
|
57
|
+
@result = @graph.put_object("lukeshepard", "feed", :message => "Hello, world")
|
58
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
59
|
+
rescue Exception => wrong_err
|
60
|
+
end
|
61
|
+
@right_err.should_not be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
|
65
|
+
it "should not be able to post to a feed" do
|
66
|
+
begin
|
67
|
+
@result = @graph.put_wall_post("Hello, world", {:name => "Context Optional", :link => "http://www.contextoptional.com/"}, "contextoptional")
|
68
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
69
|
+
rescue Exception => wrong_err
|
70
|
+
end
|
71
|
+
@right_err.should_not be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not be able to comment on an object" do
|
75
|
+
begin
|
76
|
+
# random public post on the ContextOptional wall
|
77
|
+
@result = @graph.put_comment("7204941866_119776748033392", "The hackathon was great!")
|
78
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
79
|
+
rescue Exception => wrong_err
|
80
|
+
end
|
81
|
+
@right_err.should_not be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should not be able to like an object" do
|
85
|
+
begin
|
86
|
+
@result = @graph.put_like("7204941866_119776748033392")
|
87
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
88
|
+
rescue Exception => wrong_err
|
89
|
+
end
|
90
|
+
@right_err.should_not be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# DELETE
|
95
|
+
it "should not be able to delete posts" do
|
96
|
+
begin
|
97
|
+
# test post on the Ruby SDK Test application
|
98
|
+
@result = @graph.delete_object("115349521819193_113815981982767")
|
99
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
100
|
+
rescue Exception => wrong_err
|
101
|
+
end
|
102
|
+
@right_err.should_not be_nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# SEARCH
|
106
|
+
it "should be able to search" do
|
107
|
+
result = @graph.search("facebook")
|
108
|
+
result["data"].should be_an(Array)
|
109
|
+
end
|
110
|
+
|
111
|
+
# FQL
|
112
|
+
it "should be able to access public information via FQL" do
|
113
|
+
@result = @graph.fql('select first_name from user where uid = 216743')
|
114
|
+
@result.size.should == 1
|
115
|
+
@result.first['first_name'].should == 'Chris'
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should not be able to access protected information via FQL" do
|
119
|
+
lambda { @graph.fql("select read_stream from permissions where uid = 216743") }.should raise_error(Koala::Facebook::RestAPIError)
|
120
|
+
end
|
121
|
+
|
122
|
+
# API
|
123
|
+
# the above tests test this already, but we should consider additional api tests
|
124
|
+
|
125
|
+
end # describe
|
126
|
+
|
127
|
+
end #class
|
128
|
+
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# stub the Time class to always return a time for which the valid cookie is still valid
|
2
|
+
class Time
|
3
|
+
def self.now
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.to_i
|
8
|
+
1273363199
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FacebookOAuthTests < Test::Unit::TestCase
|
13
|
+
describe "Koala GraphAPI without an access token" do
|
14
|
+
before :each do
|
15
|
+
# make the relevant test data easily accessible
|
16
|
+
@oauth_data = $testing_data["oauth_test_data"]
|
17
|
+
@app_id = @oauth_data["app_id"]
|
18
|
+
@secret = @oauth_data["secret"]
|
19
|
+
@code = @oauth_data["code"]
|
20
|
+
@callback_url = @oauth_data["callback_url"]
|
21
|
+
@raw_token_string = @oauth_data["raw_token_string"]
|
22
|
+
@raw_offline_access_token_string = @oauth_data["raw_offline_access_token_string"]
|
23
|
+
|
24
|
+
# this should expanded to cover all variables
|
25
|
+
raise Exception, "Must supply app data to run FacebookOAuthTests!" unless @app_id && @secret && @callback_url &&
|
26
|
+
@code && @raw_token_string &&
|
27
|
+
@raw_offline_access_token_string
|
28
|
+
|
29
|
+
@oauth = Koala::Facebook::OAuth.new(@app_id, @secret, @callback_url)
|
30
|
+
end
|
31
|
+
|
32
|
+
# initialization
|
33
|
+
it "should properly initialize" do
|
34
|
+
@oauth.should
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should properly set attributes" do
|
38
|
+
(@oauth.app_id == @app_id &&
|
39
|
+
@oauth.app_secret == @secret &&
|
40
|
+
@oauth.oauth_callback_url == @callback_url).should be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should properly initialize without a callback_url" do
|
44
|
+
@oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should properly set attributes without a callback URL" do
|
48
|
+
@oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
49
|
+
(@oauth.app_id == @app_id &&
|
50
|
+
@oauth.app_secret == @secret &&
|
51
|
+
@oauth.oauth_callback_url == nil).should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
# cookie parsing
|
55
|
+
it "should properly parse valid cookies" do
|
56
|
+
result = @oauth.get_user_from_cookie(@oauth_data["valid_cookies"])
|
57
|
+
result["uid"].should
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return all the cookie components from valid cookie string" do
|
61
|
+
cookie_data = @oauth_data["valid_cookies"]
|
62
|
+
parsing_results = @oauth.get_user_from_cookie(cookie_data)
|
63
|
+
number_of_components = cookie_data["fbs_#{@app_id.to_s}"].scan(/\=/).length
|
64
|
+
parsing_results.length.should == number_of_components
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should properly parse valid offline access cookies (e.g. no expiration)" do
|
68
|
+
result = @oauth.get_user_from_cookie(@oauth_data["offline_access_cookies"])
|
69
|
+
result["uid"].should
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return all the cookie components from offline access cookies" do
|
73
|
+
cookie_data = @oauth_data["offline_access_cookies"]
|
74
|
+
parsing_results = @oauth.get_user_from_cookie(cookie_data)
|
75
|
+
number_of_components = cookie_data["fbs_#{@app_id.to_s}"].scan(/\=/).length
|
76
|
+
parsing_results.length.should == number_of_components
|
77
|
+
end
|
78
|
+
|
79
|
+
it "shouldn't parse expired cookies" do
|
80
|
+
result = @oauth.get_user_from_cookie(@oauth_data["expired_cookies"])
|
81
|
+
result.should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "shouldn't parse invalid cookies" do
|
85
|
+
# make an invalid string by replacing some values
|
86
|
+
bad_cookie_hash = @oauth_data["valid_cookies"].inject({}) { |hash, value| hash[value[0]] = value[1].gsub(/[0-9]/, "3") }
|
87
|
+
result = @oauth.get_user_from_cookie(bad_cookie_hash)
|
88
|
+
result.should be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# OAuth URLs
|
92
|
+
|
93
|
+
# url_for_oauth_code
|
94
|
+
it "should generate a properly formatted OAuth code URL with the default values" do
|
95
|
+
url = @oauth.url_for_oauth_code
|
96
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should generate a properly formatted OAuth code URL when a callback is given" do
|
100
|
+
callback = "foo.com"
|
101
|
+
url = @oauth.url_for_oauth_code(:callback => callback)
|
102
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}"
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should generate a properly formatted OAuth code URL when permissions are requested as a string" do
|
106
|
+
permissions = "publish_stream,read_stream"
|
107
|
+
url = @oauth.url_for_oauth_code(:permissions => permissions)
|
108
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}&scope=#{permissions}"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should generate a properly formatted OAuth code URL when permissions are requested as a string" do
|
112
|
+
permissions = ["publish_stream", "read_stream"]
|
113
|
+
url = @oauth.url_for_oauth_code(:permissions => permissions)
|
114
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{@callback_url}&scope=#{permissions.join(",")}"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should generate a properly formatted OAuth code URL when both permissions and callback are provided" do
|
118
|
+
permissions = "publish_stream,read_stream"
|
119
|
+
callback = "foo.com"
|
120
|
+
url = @oauth.url_for_oauth_code(:callback => callback, :permissions => permissions)
|
121
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}&scope=#{permissions}"
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should raise an exception if no callback is given in initialization or the call" do
|
125
|
+
oauth2 = Koala::Facebook::OAuth.new(@app_id, @secret)
|
126
|
+
begin
|
127
|
+
url = oauth2.url_for_oauth_code
|
128
|
+
rescue ArgumentError => @right_err
|
129
|
+
rescue
|
130
|
+
end
|
131
|
+
|
132
|
+
@right_err.should
|
133
|
+
end
|
134
|
+
|
135
|
+
# url_for_access_token
|
136
|
+
it "should generate a properly formatted OAuth token URL when provided a code" do
|
137
|
+
url = @oauth.url_for_access_token(@code)
|
138
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{@callback_url}&client_secret=#{@secret}&code=#{@code}"
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should generate a properly formatted OAuth token URL when provided a callback" do
|
142
|
+
callback = "foo.com"
|
143
|
+
url = @oauth.url_for_access_token(@code, :callback => callback)
|
144
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@secret}&code=#{@code}"
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should output a deprecation warning but generate a properly formatted OAuth token URL when provided a callback in the deprecated fashion" do
|
148
|
+
callback = "foo.com"
|
149
|
+
url = out = nil
|
150
|
+
|
151
|
+
begin
|
152
|
+
# we want to capture the deprecation warning as well as the output
|
153
|
+
# credit to http://thinkingdigitally.com/archive/capturing-output-from-puts-in-ruby/ for the technique
|
154
|
+
out = StringIO.new
|
155
|
+
$stdout = out
|
156
|
+
url = @oauth.url_for_access_token(@code, callback)
|
157
|
+
ensure
|
158
|
+
$stdout = STDOUT
|
159
|
+
end
|
160
|
+
|
161
|
+
# two assertions may be bad test writing, but this is for a deprecated method
|
162
|
+
url.should == "https://#{Koala::Facebook::GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@secret}&code=#{@code}"
|
163
|
+
out.should_not be_nil
|
164
|
+
end
|
165
|
+
|
166
|
+
# parse_access_token
|
167
|
+
it "should properly parse access token results" do
|
168
|
+
result = @oauth.parse_access_token(@raw_token_string)
|
169
|
+
has_both_parts = result["access_token"] && result["expires"]
|
170
|
+
has_both_parts.should
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should properly parse offline access token results" do
|
174
|
+
result = @oauth.parse_access_token(@raw_offline_access_token_string)
|
175
|
+
has_both_parts = result["access_token"] && !result["expires"]
|
176
|
+
has_both_parts.should
|
177
|
+
end
|
178
|
+
|
179
|
+
# fetch_token_string
|
180
|
+
# note -- we just test that this looks right, since get_access_token tests the parsing too
|
181
|
+
it "should fetch a proper token string from Facebook when given a code" do
|
182
|
+
result = @oauth.fetch_token_string(@code)
|
183
|
+
result.should =~ /^access_token/
|
184
|
+
end
|
185
|
+
|
186
|
+
# get_access_token
|
187
|
+
it "should properly get and parse an access token token results" do
|
188
|
+
result = @oauth.get_access_token(@code)
|
189
|
+
result["access_token"].should
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should raise an error when get_access_token is called with a bad code" do
|
193
|
+
begin
|
194
|
+
result = @oauth.get_access_token("foo")
|
195
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
196
|
+
rescue
|
197
|
+
end
|
198
|
+
@right_err.should
|
199
|
+
end
|
200
|
+
end # describe
|
201
|
+
|
202
|
+
end #class
|
@@ -0,0 +1,155 @@
|
|
1
|
+
class FacebookWithAccessTokenTests < Test::Unit::TestCase
|
2
|
+
describe "Koala GraphAPI with an access token" do
|
3
|
+
before :each do
|
4
|
+
token = $testing_data["oauth_token"]
|
5
|
+
raise Exception, "Must supply access token to run FacebookWithAccessTokenTests!" unless token
|
6
|
+
@graph = Koala::Facebook::GraphAPI.new(token)
|
7
|
+
end
|
8
|
+
|
9
|
+
after :each do
|
10
|
+
# clean up any temporary objects
|
11
|
+
if @temporary_object_id
|
12
|
+
puts "\nCleaning up temporary object #{@temporary_object_id.to_s}"
|
13
|
+
result = @graph.delete_object(@temporary_object_id)
|
14
|
+
raise "Unable to clean up temporary Graph object #{@temporary_object_id}!" unless result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should get public data about a user" do
|
19
|
+
result = @graph.get_object("koppel")
|
20
|
+
# the results should have an ID and a name, among other things
|
21
|
+
(result["id"] && result["name"]).should_not be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should get private data about a user" do
|
25
|
+
result = @graph.get_object("koppel")
|
26
|
+
# updated_time should be a pretty fixed test case
|
27
|
+
result["updated_time"].should_not be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should get public data about a Page" do
|
31
|
+
result = @graph.get_object("contextoptional")
|
32
|
+
# the results should have an ID and a name, among other things
|
33
|
+
(result["id"] && result["name"]).should
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should get data about 'me'" do
|
37
|
+
result = @graph.get_object("me")
|
38
|
+
result["updated_time"].should
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be able to get multiple objects" do
|
42
|
+
result = @graph.get_objects(["contextoptional", "naitik"])
|
43
|
+
result.length.should == 2
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be able to access connections from users" do
|
47
|
+
result = @graph.get_connections("lukeshepard", "likes")
|
48
|
+
result["data"].length.should > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should be able to access connections from public Pages" do
|
52
|
+
result = @graph.get_connections("contextoptional", "likes")
|
53
|
+
result["data"].should be_a(Array)
|
54
|
+
end
|
55
|
+
|
56
|
+
# PUT
|
57
|
+
it "should be able to write an object to the graph" do
|
58
|
+
result = @graph.put_wall_post("Hello, world, from the test suite!")
|
59
|
+
@temporary_object_id = result["id"]
|
60
|
+
@temporary_object_id.should_not be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# DELETE
|
64
|
+
it "should be able to delete posts" do
|
65
|
+
result = @graph.put_wall_post("Hello, world, from the test suite delete method!")
|
66
|
+
object_id_to_delete = result["id"]
|
67
|
+
delete_result = @graph.delete_object(object_id_to_delete)
|
68
|
+
delete_result.should == true
|
69
|
+
end
|
70
|
+
|
71
|
+
# additional put tests
|
72
|
+
it "should be able to verify messages posted to a wall" do
|
73
|
+
message = "the cats are asleep"
|
74
|
+
put_result = @graph.put_wall_post(message)
|
75
|
+
@temporary_object_id = put_result["id"]
|
76
|
+
get_result = @graph.get_object(@temporary_object_id)
|
77
|
+
|
78
|
+
# make sure the message we sent is the message that got posted
|
79
|
+
get_result["message"].should == message
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should be able to post a message with an attachment to a feed" do
|
83
|
+
result = @graph.put_wall_post("Hello, world, from the test suite again!", {:name => "Context Optional", :link => "http://www.contextoptional.com/"})
|
84
|
+
@temporary_object_id = result["id"]
|
85
|
+
@temporary_object_id.should_not be_nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should be able to verify a message with an attachment posted to a feed" do
|
89
|
+
attachment = {"name" => "Context Optional", "link" => "http://www.contextoptional.com/"}
|
90
|
+
result = @graph.put_wall_post("Hello, world, from the test suite again!", attachment)
|
91
|
+
@temporary_object_id = result["id"]
|
92
|
+
get_result = @graph.get_object(@temporary_object_id)
|
93
|
+
|
94
|
+
# make sure the result we fetch includes all the parameters we sent
|
95
|
+
it_matches = attachment.inject(true) {|valid, param| valid && (get_result[param[0]] == attachment[param[0]])}
|
96
|
+
it_matches.should == true
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should be able to comment on an object" do
|
100
|
+
result = @graph.put_wall_post("Hello, world, from the test suite, testing comments!")
|
101
|
+
@temporary_object_id = result["id"]
|
102
|
+
|
103
|
+
# this will be deleted when the post gets deleted
|
104
|
+
comment_result = @graph.put_comment(@temporary_object_id, "it's my comment!")
|
105
|
+
comment_result.should_not be_nil
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should be able to verify a comment posted about an object" do
|
109
|
+
message_text = "Hello, world, from the test suite, testing comments!"
|
110
|
+
result = @graph.put_wall_post(message_text)
|
111
|
+
@temporary_object_id = result["id"]
|
112
|
+
|
113
|
+
# this will be deleted when the post gets deleted
|
114
|
+
comment_text = "it's my comment!"
|
115
|
+
comment_result = @graph.put_comment(@temporary_object_id, comment_text)
|
116
|
+
get_result = @graph.get_object(comment_result["id"])
|
117
|
+
|
118
|
+
# make sure the text of the comment matches what we sent
|
119
|
+
get_result["message"].should == comment_text
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should be able to like an object" do
|
123
|
+
result = @graph.put_wall_post("Hello, world, from the test suite, testing comments!")
|
124
|
+
@temporary_object_id = result["id"]
|
125
|
+
like_result = @graph.put_like(@temporary_object_id)
|
126
|
+
end
|
127
|
+
|
128
|
+
# SEARCH
|
129
|
+
it "should be able to search" do
|
130
|
+
result = @graph.search("facebook")
|
131
|
+
result["data"].should be_an(Array)
|
132
|
+
end
|
133
|
+
|
134
|
+
# FQL
|
135
|
+
it "should be able to access public information via FQL" do
|
136
|
+
result = @graph.fql('select first_name from user where uid = 216743')
|
137
|
+
result.size.should == 1
|
138
|
+
result.first['first_name'].should == 'Chris'
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should be able to access protected information via FQL" do
|
142
|
+
# Tests agains read_stream since these tests assume we have that permission
|
143
|
+
YOUR_ID = 216743
|
144
|
+
result = @graph.fql("select read_stream from permissions where uid = #{YOUR_ID}")
|
145
|
+
|
146
|
+
result.size.should == 1
|
147
|
+
result.first['read_stream'].should == 1
|
148
|
+
end
|
149
|
+
|
150
|
+
# API
|
151
|
+
# the above tests test this already, but we should consider additional api tests
|
152
|
+
|
153
|
+
end # describe
|
154
|
+
|
155
|
+
end #class
|
data/test/koala_tests.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/test/unit'
|
4
|
+
|
5
|
+
# load the libraries
|
6
|
+
require 'koala'
|
7
|
+
|
8
|
+
# load the tests
|
9
|
+
require 'koala/facebook_no_access_token_tests'
|
10
|
+
require 'koala/facebook_with_access_token_tests'
|
11
|
+
require 'koala/facebook_oauth_tests'
|
12
|
+
|
13
|
+
class FacebookTestSuite
|
14
|
+
def self.suite
|
15
|
+
suite = Test::Unit::TestSuite.new
|
16
|
+
suite << FacebookNoAccessTokenTests.suite
|
17
|
+
suite << FacebookWithAccessTokenTests.suite
|
18
|
+
suite << FacebookOAuthTests.suite
|
19
|
+
suite
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# load testing data (see note in readme.md)
|
24
|
+
# I'm seeing a bug with spec and gets where the facebook_test_suite.rb file gets read in when gets is called
|
25
|
+
# until that's solved, we'll need to store/update tokens in the access_token file
|
26
|
+
$testing_data = YAML.load_file("facebook_data.yml") rescue {}
|
27
|
+
|
28
|
+
unless $testing_data["oauth_token"]
|
29
|
+
puts "Access token tests will fail until you store a valid token in facebook_data.yml"
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO finish tests for OAuth class
|
33
|
+
# unless $testing_data["cookie_hash"] && $testing_data["app_id"] && $testing_data["secret"]
|
34
|
+
# puts "Cookie tests will fail until you store valid data for the cookie hash, app_id, and app secret in facebook_data.yml"
|
35
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cbaclig-koala
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.5.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alex Koppel, Rafi Jacoby, Context Optional
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-05-11 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Koala is a lightweight, flexible Ruby SDK for Facebook's new Graph API. It allows read/write access to the Facebook Graph and provides OAuth URLs and cookie validation for Facebook Connect sites. Koala supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.
|
23
|
+
email: alex@alexkoppel.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- CHANGELOG
|
30
|
+
- lib/http_services.rb
|
31
|
+
- lib/koala.rb
|
32
|
+
files:
|
33
|
+
- CHANGELOG
|
34
|
+
- Manifest
|
35
|
+
- Rakefile
|
36
|
+
- init.rb
|
37
|
+
- lib/http_services.rb
|
38
|
+
- lib/koala.rb
|
39
|
+
- readme.md
|
40
|
+
- test/facebook_data.yml
|
41
|
+
- test/koala/facebook_no_access_token_tests.rb
|
42
|
+
- test/koala/facebook_oauth_tests.rb
|
43
|
+
- test/koala/facebook_with_access_token_tests.rb
|
44
|
+
- test/koala_tests.rb
|
45
|
+
- cbaclig-koala.gemspec
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/arsduo/koala
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --line-numbers
|
53
|
+
- --inline-source
|
54
|
+
- --title
|
55
|
+
- Cbaclig-koala
|
56
|
+
- --main
|
57
|
+
- readme.md
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 1
|
73
|
+
- 2
|
74
|
+
version: "1.2"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: cbaclig-koala
|
78
|
+
rubygems_version: 1.3.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: A lightweight, flexible library for Facebook's new Graph API
|
82
|
+
test_files: []
|
83
|
+
|