koala 0.4 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/Rakefile +2 -2
- data/koala.gemspec +2 -2
- data/lib/http_services.rb +46 -48
- data/lib/koala.rb +201 -260
- data/readme.md +28 -14
- data/test/facebook_data.yml +5 -1
- data/test/koala/facebook_no_access_token_tests.rb +11 -13
- data/test/koala/facebook_with_access_token_tests.rb +3 -6
- data/test/koala_tests.rb +5 -4
- metadata +3 -2
data/CHANGELOG
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
v0.4.1
|
2
|
+
-- Encapsulated GraphAPI and OAuth classes in the Koala::Facebook module for clarity (and to avoid claiming the global Facebook class)
|
3
|
+
-- Moved make_request method to Koala class from GraphAPI instance (for use by future OAuth class functionality)
|
4
|
+
-- Renamed request method to api for consistancy with Javascript library
|
5
|
+
-- Updated tests and readme
|
6
|
+
|
1
7
|
v0.4.0
|
2
8
|
-- Adopted the Koala name
|
9
|
+
-- Updated readme and tests
|
3
10
|
-- Fixed cookie verification bug for non-expiring OAuth tokens
|
4
11
|
|
5
12
|
v0.3.1
|
data/Rakefile
CHANGED
@@ -4,12 +4,12 @@ require 'rake'
|
|
4
4
|
require 'echoe'
|
5
5
|
|
6
6
|
# gem management
|
7
|
-
Echoe.new('koala', '0.4') do |p|
|
7
|
+
Echoe.new('koala', '0.4.1') do |p|
|
8
8
|
p.summary = "A lightweight, flexible library for Facebook's new Graph API"
|
9
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
10
|
p.url = "http://github.com/arsduo/ruby-sdk"
|
11
11
|
p.author = ["Alex Koppel", "Rafi Jacoby", "Context Optional"]
|
12
12
|
p.email = "alex@alexkoppel.com"
|
13
|
-
p.ignore_pattern = ["tmp/*", "script/*"]
|
13
|
+
p.ignore_pattern = ["tmp/*", "script/*", "pkg/*"]
|
14
14
|
p.development_dependencies = []
|
15
15
|
end
|
data/koala.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{koala}
|
5
|
-
s.version = "0.4"
|
5
|
+
s.version = "0.4.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Alex Koppel, Rafi Jacoby, Context Optional"]
|
9
|
-
s.date = %q{2010-05-
|
9
|
+
s.date = %q{2010-05-03}
|
10
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
11
|
s.email = %q{alex@alexkoppel.com}
|
12
12
|
s.extra_rdoc_files = ["CHANGELOG", "lib/http_services.rb", "lib/koala.rb"]
|
data/lib/http_services.rb
CHANGED
@@ -1,60 +1,58 @@
|
|
1
1
|
module Koala
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
require 'net/https'
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def make_request(path, args, verb)
|
13
|
-
# We translate args to a valid query string. If post is specified,
|
14
|
-
# we send a POST request to the given path with the given arguments.
|
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'
|
15
8
|
|
16
|
-
|
17
|
-
|
9
|
+
def self.make_request(path, args, verb)
|
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.
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
# we turn off certificate validation to avoid the
|
22
|
-
# "warning: peer certificate won't be verified in this SSL session" warning
|
23
|
-
# not sure if this is the right way to handle it
|
24
|
-
# see http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
|
25
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
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"
|
26
15
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
http = Net::HTTP.new(Facebook::GRAPH_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
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
41
39
|
end
|
42
40
|
end
|
41
|
+
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
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
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
50
|
+
def self.make_request(path, args, verb)
|
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://#{Facebook::GRAPH_SERVER}/#{path}", :params => args).body
|
54
|
+
end
|
55
|
+
end
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
data/lib/koala.rb
CHANGED
@@ -9,299 +9,240 @@ require 'json'
|
|
9
9
|
require 'http_services'
|
10
10
|
|
11
11
|
module Koala
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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/.
|
32
34
|
|
33
|
-
|
35
|
+
GRAPH_SERVER = "graph.facebook.com"
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
#
|
38
|
-
# See http://developers.facebook.com/docs/api for complete documentation
|
39
|
-
# for the API.
|
40
|
-
#
|
41
|
-
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
42
|
-
# events, photos) and the connections between them (e.g., friends,
|
43
|
-
# photo tags, and event RSVPs). This client provides access to those
|
44
|
-
# primitive types in a generic way. For example, given an OAuth access
|
45
|
-
# token, this will fetch the profile of the active user and the list
|
46
|
-
# of the user's friends:
|
47
|
-
#
|
48
|
-
# graph = Facebook::GraphAPI.new(access_token)
|
49
|
-
# user = graph.get_object("me")
|
50
|
-
# friends = graph.get_connections(user["id"], "friends")
|
51
|
-
#
|
52
|
-
# You can see a list of all of the objects and connections supported
|
53
|
-
# by the API at http://developers.facebook.com/docs/reference/api/.
|
54
|
-
#
|
55
|
-
# You can obtain an access token via OAuth or by using the Facebook
|
56
|
-
# JavaScript SDK. See http://developers.facebook.com/docs/authentication/
|
57
|
-
# for details.
|
58
|
-
#
|
59
|
-
# If you are using the JavaScript SDK, you can use the
|
60
|
-
# Facebook::get_user_from_cookie() method below to get the OAuth access token
|
61
|
-
# for the active user from the cookie saved by the SDK.
|
62
|
-
|
63
|
-
# initialize with an access token
|
64
|
-
def initialize(access_token = nil)
|
65
|
-
@access_token = access_token
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_object(id, args = {})
|
69
|
-
# Fetchs the given object from the graph.
|
70
|
-
request(id, args)
|
71
|
-
end
|
72
|
-
|
73
|
-
def get_objects(ids, args = {})
|
74
|
-
# Fetchs all of the given object from the graph.
|
75
|
-
# We return a map from ID to object. If any of the IDs are invalid,
|
76
|
-
# we raise an exception.
|
77
|
-
request("", args.merge("ids" => ids.join(",")))
|
78
|
-
end
|
79
|
-
|
80
|
-
def get_connections(id, connection_name, args = {})
|
81
|
-
# Fetchs the connections for given object.
|
82
|
-
request("#{id}/#{connection_name}", args)
|
83
|
-
end
|
84
|
-
|
85
|
-
def put_object(parent_object, connection_name, args = {})
|
86
|
-
# Writes the given object to the graph, connected to the given parent.
|
37
|
+
class GraphAPI
|
38
|
+
# A client for the Facebook Graph API.
|
87
39
|
#
|
88
|
-
#
|
40
|
+
# See http://developers.facebook.com/docs/api for complete documentation
|
41
|
+
# for the API.
|
89
42
|
#
|
90
|
-
#
|
43
|
+
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
44
|
+
# events, photos) and the connections between them (e.g., friends,
|
45
|
+
# photo tags, and event RSVPs). This client provides access to those
|
46
|
+
# primitive types in a generic way. For example, given an OAuth access
|
47
|
+
# token, this will fetch the profile of the active user and the list
|
48
|
+
# of the user's friends:
|
91
49
|
#
|
92
|
-
#
|
93
|
-
#
|
50
|
+
# graph = Koala::Facebook::GraphAPI.new(access_token)
|
51
|
+
# user = graph.get_object("me")
|
52
|
+
# friends = graph.get_connections(user["id"], "friends")
|
94
53
|
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
# graph.put_object(post["id"], "comments", :message => "First!")
|
54
|
+
# You can see a list of all of the objects and connections supported
|
55
|
+
# by the API at http://developers.facebook.com/docs/reference/api/.
|
98
56
|
#
|
99
|
-
#
|
100
|
-
#
|
57
|
+
# You can obtain an access token via OAuth or by using the Facebook
|
58
|
+
# JavaScript SDK. See http://developers.facebook.com/docs/authentication/
|
59
|
+
# for details.
|
101
60
|
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
61
|
+
# If you are using the JavaScript SDK, you can use the
|
62
|
+
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
63
|
+
# for the active user from the cookie saved by the SDK.
|
64
|
+
|
65
|
+
# initialize with an access token
|
66
|
+
def initialize(access_token = nil)
|
67
|
+
@access_token = access_token
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_object(id, args = {})
|
71
|
+
# Fetchs the given object from the graph.
|
72
|
+
api(id, args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_objects(ids, args = {})
|
76
|
+
# Fetchs all of the given object from the graph.
|
77
|
+
# We return a map from ID to object. If any of the IDs are invalid,
|
78
|
+
# we raise an exception.
|
79
|
+
api("", args.merge("ids" => ids.join(",")))
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_connections(id, connection_name, args = {})
|
83
|
+
# Fetchs the connections for given object.
|
84
|
+
api("#{id}/#{connection_name}", args)
|
85
|
+
end
|
86
|
+
|
87
|
+
def put_object(parent_object, connection_name, args = {})
|
88
|
+
# Writes the given object to the graph, connected to the given parent.
|
89
|
+
#
|
90
|
+
# For example,
|
91
|
+
#
|
92
|
+
# graph.put_object("me", "feed", :message => "Hello, world")
|
93
|
+
#
|
94
|
+
# writes "Hello, world" to the active user's wall. Likewise, this
|
95
|
+
# will comment on a the first post of the active user's feed:
|
96
|
+
#
|
97
|
+
# feed = graph.get_connections("me", "feed")
|
98
|
+
# post = feed["data"][0]
|
99
|
+
# graph.put_object(post["id"], "comments", :message => "First!")
|
100
|
+
#
|
101
|
+
# See http://developers.facebook.com/docs/api#publishing for all of
|
102
|
+
# the supported writeable objects.
|
103
|
+
#
|
104
|
+
# Most write operations require extended permissions. For example,
|
105
|
+
# publishing wall posts requires the "publish_stream" permission. See
|
106
|
+
# http://developers.facebook.com/docs/authentication/ for details about
|
107
|
+
# extended permissions.
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
|
109
|
+
raise GraphAPIError.new(nil, "Write operations require an access token") unless @access_token
|
110
|
+
api("#{parent_object}/#{connection_name}", args, "post")
|
111
|
+
end
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
113
|
+
def put_wall_post(message, attachment = {}, profile_id = "me")
|
114
|
+
# Writes a wall post to the given profile's wall.
|
115
|
+
#
|
116
|
+
# We default to writing to the authenticated user's wall if no
|
117
|
+
# profile_id is specified.
|
118
|
+
#
|
119
|
+
# attachment adds a structured attachment to the status message being
|
120
|
+
# posted to the Wall. It should be a dictionary of the form:
|
121
|
+
#
|
122
|
+
# {"name": "Link name"
|
123
|
+
# "link": "http://www.example.com/",
|
124
|
+
# "caption": "{*actor*} posted a new review",
|
125
|
+
# "description": "This is a longer description of the attachment",
|
126
|
+
# "picture": "http://www.example.com/thumbnail.jpg"}
|
125
127
|
|
126
|
-
|
127
|
-
|
128
|
+
self.put_object(profile_id, "feed", attachment.merge({:message => message}))
|
129
|
+
end
|
128
130
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
131
|
+
def put_comment(object_id, message)
|
132
|
+
# Writes the given comment on the given post.
|
133
|
+
self.put_object(object_id, "comments", {:message => message})
|
134
|
+
end
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
136
|
+
def put_like(object_id)
|
137
|
+
# Likes the given post.
|
138
|
+
self.put_object(object_id, "likes")
|
139
|
+
end
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def delete_object(id)
|
142
|
+
# Deletes the object with the given ID from the graph.
|
143
|
+
api(id, {}, "delete")
|
144
|
+
end
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
146
|
+
def search(search_terms, args = {})
|
147
|
+
# Searches for a given term
|
148
|
+
api("search", args.merge({:q => search_terms}))
|
149
|
+
end
|
148
150
|
|
149
|
-
|
150
|
-
|
151
|
-
|
151
|
+
def api(path, args = {}, verb = "get")
|
152
|
+
# Fetches the given path in the Graph API.
|
153
|
+
args["access_token"] = @access_token if @access_token
|
152
154
|
|
153
|
-
|
154
|
-
|
155
|
+
# make the request via the provided service
|
156
|
+
result = Koala.make_request(path, args, verb)
|
155
157
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
158
|
+
# Facebook sometimes sends results like "true" and "false", which aren't strictly object
|
159
|
+
# and cause JSON.parse to fail
|
160
|
+
# so we account for that
|
161
|
+
response = JSON.parse("[#{result}]")[0]
|
160
162
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
163
|
+
# check for errors
|
164
|
+
if response.is_a?(Hash) && error = response["error"]
|
165
|
+
raise GraphAPIError.new(error["code"], error["message"])
|
166
|
+
end
|
165
167
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# set up the http service used to make requests
|
170
|
-
# you can use your own (for HTTParty, etc.) by calling Koala::API.http_service = YourModule
|
171
|
-
def self.http_service=(service)
|
172
|
-
self.send(:include, service)
|
173
|
-
end
|
174
|
-
|
175
|
-
# by default, try requiring Typhoeus -- if that works, use it
|
176
|
-
begin
|
177
|
-
require 'typhoeus'
|
178
|
-
Koala::GraphAPI.http_service = TyphoeusService
|
179
|
-
rescue LoadError
|
180
|
-
Koala::GraphAPI.http_service = NetHTTPService
|
168
|
+
response
|
169
|
+
end
|
181
170
|
end
|
182
|
-
end
|
183
171
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
172
|
+
class GraphAPIError < Exception
|
173
|
+
attr_accessor :code
|
174
|
+
def initialize(code, message)
|
175
|
+
super(message)
|
176
|
+
self.code = code
|
177
|
+
end
|
189
178
|
end
|
190
|
-
end
|
191
179
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
180
|
+
class OAuth
|
181
|
+
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
182
|
+
@app_id = app_id
|
183
|
+
@app_secret = app_secret
|
184
|
+
@oauth_callback_url = oauth_callback_url
|
185
|
+
end
|
198
186
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
187
|
+
def get_user_from_cookie(cookie_hash)
|
188
|
+
# Parses the cookie set by the official Facebook JavaScript SDK.
|
189
|
+
#
|
190
|
+
# cookies should be a dictionary-like object mapping cookie names to
|
191
|
+
# cookie values.
|
192
|
+
#
|
193
|
+
# If the user is logged in via Facebook, we return a dictionary with the
|
194
|
+
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
195
|
+
# and the latter can be used to make authenticated requests to the Graph API.
|
196
|
+
# If the user is not logged in, we return None.
|
197
|
+
#
|
198
|
+
# Download the official Facebook JavaScript SDK at
|
199
|
+
# http://github.com/facebook/connect-js/. Read more about Facebook
|
200
|
+
# authentication at http://developers.facebook.com/docs/authentication/.
|
213
201
|
|
214
|
-
|
215
|
-
|
216
|
-
|
202
|
+
if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
|
203
|
+
# remove the opening/closing quote
|
204
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
217
205
|
|
218
|
-
|
219
|
-
|
220
|
-
|
206
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
207
|
+
components = {}
|
208
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
221
209
|
|
222
|
-
|
223
|
-
|
210
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
211
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
224
212
|
|
225
|
-
|
213
|
+
sig == components["sig"] && (components["expires"].to_i == 0 || Time.now.to_i < components["expires"].to_i) ? components : nil
|
214
|
+
end
|
226
215
|
end
|
227
|
-
end
|
228
216
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
217
|
+
def url_for_oauth_code(options = {})
|
218
|
+
# for permissions, see http://developers.facebook.com/docs/authentication/permissions
|
219
|
+
permissions = options[:permissions]
|
220
|
+
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
233
221
|
|
234
|
-
|
235
|
-
|
236
|
-
|
222
|
+
callback = options[:callback] || @oauth_callback_url
|
223
|
+
|
224
|
+
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
225
|
+
"https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
|
226
|
+
end
|
237
227
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
# for more details and up to date information, see http://developers.facebook.com/docs/authentication/permissions
|
244
|
-
def self.all_permissions
|
245
|
-
USER_PERMISSIONS.concat(FRIEND_PERMISSIONS)
|
228
|
+
def url_for_access_token(code, callback = @oauth_callback_url)
|
229
|
+
# Creates the URL for the token corresponding to a given code generated by Facebook
|
230
|
+
"https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
231
|
+
end
|
246
232
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
"user_birthday", # Provides access to the full birthday with year as the birthday_date property
|
262
|
-
"user_education_history", # Provides access to education history as the education property
|
263
|
-
"user_events", # Provides access to the list of events the user is attending as the events connection
|
264
|
-
"user_groups", # Provides access to the list of groups the user is a member of as the groups connection
|
265
|
-
"user_hometown", # Provides access to the user's hometown in the hometown property
|
266
|
-
"user_interests", # Provides access to the user's list of interests as the interests connection
|
267
|
-
"user_likes", # Provides access to the list of all of the pages the user has liked as the likes connection
|
268
|
-
"user_location", # Provides access to the user's current location as the current_location property
|
269
|
-
"user_notes", # Provides access to the user's notes as the notes connection
|
270
|
-
"user_online_presence", # Provides access to the user's online/offline presence
|
271
|
-
"user_photo_video_tags", # Provides access to the photos the user has been tagged in as the photos connection
|
272
|
-
"user_photos", # Provides access to the photos the user has uploaded
|
273
|
-
"user_relationships", # Provides access to the user's family and personal relationships and relationship status
|
274
|
-
"user_religion_politics", # Provides access to the user's religious and political affiliations
|
275
|
-
"user_status", # Provides access to the user's most recent status message
|
276
|
-
"user_videos", # Provides access to the videos the user has uploaded
|
277
|
-
"user_website", # Provides access to the user's web site URL
|
278
|
-
"user_work_history", # Provides access to work history as the work property
|
279
|
-
"read_friendlists", # Provides read access to the user's friend lists
|
280
|
-
"read_requests" # Provides read access to the user's friend requests
|
281
|
-
]
|
282
|
-
|
283
|
-
FRIEND_PERMISSIONS = [
|
284
|
-
# DATA ACCESS
|
285
|
-
"friends_about_me", # Provides access to the "About Me" section of the profile in the about property
|
286
|
-
"friends_activities", # Provides access to the user's list of activities as the activities connection
|
287
|
-
"friends_birthday", # Provides access to the full birthday with year as the birthday_date property
|
288
|
-
"friends_education_history", # Provides access to education history as the education property
|
289
|
-
"friends_events", # Provides access to the list of events the user is attending as the events connection
|
290
|
-
"friends_groups", # Provides access to the list of groups the user is a member of as the groups connection
|
291
|
-
"friends_hometown", # Provides access to the user's hometown in the hometown property
|
292
|
-
"friends_interests", # Provides access to the user's list of interests as the interests connection
|
293
|
-
"friends_likes", # Provides access to the list of all of the pages the user has liked as the likes connection
|
294
|
-
"friends_location", # Provides access to the user's current location as the current_location property
|
295
|
-
"friends_notes", # Provides access to the user's notes as the notes connection
|
296
|
-
"friends_online_presence", # Provides access to the user's online/offline presence
|
297
|
-
"friends_photo_video_tags", # Provides access to the photos the user has been tagged in as the photos connection
|
298
|
-
"friends_photos", # Provides access to the photos the user has uploaded
|
299
|
-
"friends_relationships", # Provides access to the user's family and personal relationships and relationship status
|
300
|
-
"friends_religion_politics", # Provides access to the user's religious and political affiliations
|
301
|
-
"friends_status", # Provides access to the user's most recent status message
|
302
|
-
"friends_videos", # Provides access to the videos the user has uploaded
|
303
|
-
"friends_website", # Provides access to the user's web site URL
|
304
|
-
"friends_work_history" # Provides access to work history as the work property
|
305
|
-
]
|
233
|
+
end
|
234
|
+
|
235
|
+
# finally, set up the http service Koala methods used to make requests
|
236
|
+
# you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
|
237
|
+
def self.http_service=(service)
|
238
|
+
self.send(:include, service)
|
239
|
+
end
|
240
|
+
|
241
|
+
# by default, try requiring Typhoeus -- if that works, use it
|
242
|
+
begin
|
243
|
+
require 'typhoeus'
|
244
|
+
Koala.http_service = TyphoeusService
|
245
|
+
rescue LoadError
|
246
|
+
Koala.http_service = NetHTTPService
|
306
247
|
end
|
307
248
|
end
|
data/readme.md
CHANGED
@@ -1,29 +1,43 @@
|
|
1
|
-
|
1
|
+
Koala
|
2
2
|
====
|
3
|
+
Koala (<a href="http://github.com/arsduo/ruby-sdk" target="_blank">http://github.com/arsduo/ruby-sdk</a>) is a new Facebook Graph library for Ruby. We wrote Koala with four goals:
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
about the Graph API at [http://developers.facebook.com/docs/api](http://developers.facebook.com/docs/api).
|
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
9
|
|
10
10
|
Basic usage:
|
11
11
|
|
12
|
-
graph = Facebook::GraphAPI.new(oauth_access_token)
|
12
|
+
graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
|
13
13
|
profile = graph.get_object("me")
|
14
14
|
friends = graph.get_connections("me", "friends")
|
15
15
|
graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
16
16
|
|
17
|
-
If you
|
18
|
-
[JavaScript SDK](http://github.com/facebook/connect-js), you can
|
19
|
-
|
20
|
-
|
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 http://blog.twoalex.com/2010/05/03/introducing-koala-a-new-gem-for-facebooks-new-graph-api/.
|
24
|
+
|
21
25
|
|
22
26
|
Testing
|
23
27
|
-----
|
24
28
|
|
25
29
|
Unit tests are provided for Graph API methods. However, because the Graph API uses access tokens, which expire, you have to provide your own token with stream publishing permissions for the tests. Insert the token value into the file test/facebook_data.yml, then run the test as follows:
|
26
|
-
spec
|
30
|
+
spec koala_tests.rb
|
27
31
|
|
28
|
-
Unit tests for cookie validation will be provided shortly. (You'll also need to add that information into the yml.)
|
29
|
-
|
32
|
+
Unit tests for cookie validation and other methods in the OAuth class will be provided shortly. (You'll also need to add that information into the yml.)
|
33
|
+
|
34
|
+
|
35
|
+
Coming Soon
|
36
|
+
-----
|
37
|
+
1. OAuth class methods to parse the results of the access token call
|
38
|
+
2. OAuth class method to directly fetch the access token when given a code value
|
39
|
+
|
40
|
+
|
41
|
+
Known Issues
|
42
|
+
-----
|
43
|
+
1. gem install koala produces "Could not find main page readme.md" message
|
data/test/facebook_data.yml
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# for testing the GraphAPI classs
|
2
|
+
# OAuth token should have stream_publish permissions
|
3
|
+
oauth_token:
|
4
|
+
|
5
|
+
# for testing the OAuth class
|
2
6
|
app_id:
|
3
7
|
secret:
|
4
8
|
callback_url:
|
@@ -1,7 +1,8 @@
|
|
1
1
|
class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
2
|
+
|
2
3
|
describe "Koala GraphAPI without an access token" do
|
3
4
|
before :each do
|
4
|
-
@graph = Koala::GraphAPI.new
|
5
|
+
@graph = Koala::Facebook::GraphAPI.new
|
5
6
|
end
|
6
7
|
|
7
8
|
it "should get public data about a user" do
|
@@ -26,7 +27,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
26
27
|
it "should not be able to get data about 'me'" do
|
27
28
|
begin
|
28
29
|
@graph.get_object("me")
|
29
|
-
rescue Koala::GraphAPIError => @right_err
|
30
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
30
31
|
rescue Exception => wrong_err
|
31
32
|
end
|
32
33
|
@right_err.should_not be_nil
|
@@ -40,7 +41,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
40
41
|
it "shouldn't be able to access connections from users" do
|
41
42
|
begin
|
42
43
|
@graph.get_connections("lukeshepard", "likes")
|
43
|
-
rescue Koala::GraphAPIError => @right_err
|
44
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
44
45
|
rescue Exception => wrong_err
|
45
46
|
end
|
46
47
|
@right_err.should_not be_nil
|
@@ -54,7 +55,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
54
55
|
it "should not be able to put an object" do
|
55
56
|
begin
|
56
57
|
@result = @graph.put_object("lukeshepard", "feed", :message => "Hello, world")
|
57
|
-
rescue Koala::GraphAPIError => @right_err
|
58
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
58
59
|
rescue Exception => wrong_err
|
59
60
|
end
|
60
61
|
@right_err.should_not be_nil
|
@@ -64,7 +65,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
64
65
|
it "should not be able to post to a feed" do
|
65
66
|
begin
|
66
67
|
@result = @graph.put_wall_post("Hello, world", {:name => "Context Optional", :link => "http://www.contextoptional.com/"}, "contextoptional")
|
67
|
-
rescue Koala::GraphAPIError => @right_err
|
68
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
68
69
|
rescue Exception => wrong_err
|
69
70
|
end
|
70
71
|
@right_err.should_not be_nil
|
@@ -74,7 +75,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
74
75
|
begin
|
75
76
|
# random public post on the ContextOptional wall
|
76
77
|
@result = @graph.put_comment("7204941866_119776748033392", "The hackathon was great!")
|
77
|
-
rescue Koala::GraphAPIError => @right_err
|
78
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
78
79
|
rescue Exception => wrong_err
|
79
80
|
end
|
80
81
|
@right_err.should_not be_nil
|
@@ -83,7 +84,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
83
84
|
it "should not be able to like an object" do
|
84
85
|
begin
|
85
86
|
@result = @graph.put_like("7204941866_119776748033392")
|
86
|
-
rescue Koala::GraphAPIError => @right_err
|
87
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
87
88
|
rescue Exception => wrong_err
|
88
89
|
end
|
89
90
|
@right_err.should_not be_nil
|
@@ -95,7 +96,7 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
95
96
|
begin
|
96
97
|
# test post on the Ruby SDK Test application
|
97
98
|
@result = @graph.delete_object("115349521819193_113815981982767")
|
98
|
-
rescue Koala::GraphAPIError => @right_err
|
99
|
+
rescue Koala::Facebook::GraphAPIError => @right_err
|
99
100
|
rescue Exception => wrong_err
|
100
101
|
end
|
101
102
|
@right_err.should_not be_nil
|
@@ -107,11 +108,8 @@ class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
|
107
108
|
result["data"].should be_an(Array)
|
108
109
|
end
|
109
110
|
|
110
|
-
#
|
111
|
-
# the above tests test this already, but we should
|
112
|
-
it "should be able to send get-only requests" do
|
113
|
-
# to be written
|
114
|
-
end
|
111
|
+
# API
|
112
|
+
# the above tests test this already, but we should consider additional api tests
|
115
113
|
|
116
114
|
end # describe
|
117
115
|
|
@@ -3,7 +3,7 @@ class FacebookWithAccessTokenTests < Test::Unit::TestCase
|
|
3
3
|
before :each do
|
4
4
|
token = $testing_data["oauth_token"]
|
5
5
|
raise Exception, "Must supply access token to run FacebookWithAccessTokenTests!" unless token
|
6
|
-
@graph = Koala::GraphAPI.new(token)
|
6
|
+
@graph = Koala::Facebook::GraphAPI.new(token)
|
7
7
|
end
|
8
8
|
|
9
9
|
after :each do
|
@@ -95,11 +95,8 @@ class FacebookWithAccessTokenTests < Test::Unit::TestCase
|
|
95
95
|
result["data"].should be_an(Array)
|
96
96
|
end
|
97
97
|
|
98
|
-
#
|
99
|
-
# the above tests test this already, but we should
|
100
|
-
it "should be able to send get and put requests" do
|
101
|
-
# to be written
|
102
|
-
end
|
98
|
+
# API
|
99
|
+
# the above tests test this already, but we should consider additional api tests
|
103
100
|
|
104
101
|
end # describe
|
105
102
|
|
data/test/koala_tests.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
puts __FILE__
|
2
1
|
require 'test/unit'
|
3
2
|
require 'rubygems'
|
4
3
|
require 'spec/test/unit'
|
@@ -25,6 +24,8 @@ $testing_data = YAML.load_file("facebook_data.yml") rescue {}
|
|
25
24
|
unless $testing_data["oauth_token"]
|
26
25
|
puts "Access token tests will fail until you store a valid token in facebook_data.yml"
|
27
26
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
|
28
|
+
# TODO finish tests for OAuth class
|
29
|
+
# unless $testing_data["cookie_hash"] && $testing_data["app_id"] && $testing_data["secret"]
|
30
|
+
# puts "Cookie tests will fail until you store valid data for the cookie hash, app_id, and app secret in facebook_data.yml"
|
31
|
+
# end
|
metadata
CHANGED
@@ -5,7 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 4
|
8
|
-
|
8
|
+
- 1
|
9
|
+
version: 0.4.1
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- Alex Koppel, Rafi Jacoby, Context Optional
|
@@ -13,7 +14,7 @@ autorequire:
|
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
16
|
|
16
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-03 00:00:00 -07:00
|
17
18
|
default_executable:
|
18
19
|
dependencies: []
|
19
20
|
|