koala 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +24 -0
- data/Manifest +12 -0
- data/Rakefile +15 -0
- data/init.rb +2 -0
- data/koala.gemspec +30 -0
- data/lib/http_services.rb +60 -0
- data/lib/koala.rb +307 -0
- data/readme.md +29 -0
- data/test/facebook_data.yml +5 -0
- data/test/koala/facebook_no_access_token_tests.rb +119 -0
- data/test/koala/facebook_with_access_token_tests.rb +106 -0
- data/test/koala_tests.rb +30 -0
- metadata +80 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
v0.4.0
|
2
|
+
-- Adopted the Koala name
|
3
|
+
-- Fixed cookie verification bug for non-expiring OAuth tokens
|
4
|
+
|
5
|
+
v0.3.1
|
6
|
+
-- Bug fixes.
|
7
|
+
|
8
|
+
v0.3
|
9
|
+
-- Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
|
10
|
+
-- Created FacebookGraph::OAuth class for tokens and OAuth URLs
|
11
|
+
-- Updated method for including HTTP service (think we've got it this time)
|
12
|
+
-- Updated tests
|
13
|
+
-- Added CHANGELOG and gemspec
|
14
|
+
|
15
|
+
v0.2
|
16
|
+
-- Gemified the project
|
17
|
+
-- Split out HTTP services into their own file, and adjusted inclusion method
|
18
|
+
|
19
|
+
v0.1
|
20
|
+
-- Added modular support for Typhoeus
|
21
|
+
-- Added tests
|
22
|
+
|
23
|
+
v0.0
|
24
|
+
-- Hi from F8! Basic read/write from the graph is working
|
data/Manifest
ADDED
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('koala', '0.4') 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/ruby-sdk"
|
11
|
+
p.author = ["Alex Koppel", "Rafi Jacoby", "Context Optional"]
|
12
|
+
p.email = "alex@alexkoppel.com"
|
13
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
14
|
+
p.development_dependencies = []
|
15
|
+
end
|
data/init.rb
ADDED
data/koala.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{koala}
|
5
|
+
s.version = "0.4"
|
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-01}
|
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", "koala.gemspec", "lib/http_services.rb", "lib/koala.rb", "readme.md", "test/facebook_data.yml", "test/koala/facebook_no_access_token_tests.rb", "test/koala/facebook_with_access_token_tests.rb", "test/koala_tests.rb"]
|
14
|
+
s.homepage = %q{http://github.com/arsduo/ruby-sdk}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{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
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Koala
|
2
|
+
class GraphAPI
|
3
|
+
module NetHTTPService
|
4
|
+
# this service uses Net::HTTP to send requests to the graph
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
require 'net/http' unless defined?(Net::HTTP)
|
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.
|
15
|
+
|
16
|
+
# if the verb isn't get or post, send it as a post argument
|
17
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
18
|
+
|
19
|
+
http = Net::HTTP.new(FACEBOOK_GRAPH_SERVER, 443)
|
20
|
+
http.use_ssl = true
|
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
|
26
|
+
|
27
|
+
result = http.start { |http|
|
28
|
+
response, body = (verb == "post" ? http.post(path, encode_params(args)) : http.get("#{path}?#{encode_params(args)}"))
|
29
|
+
body
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def encode_params(param_hash)
|
35
|
+
# TODO investigating whether a built-in method handles this
|
36
|
+
# if no hash (e.g. no auth token) return empty string
|
37
|
+
((param_hash || {}).collect do |key_and_value|
|
38
|
+
key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
|
39
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
40
|
+
end).join("&")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module TyphoeusService
|
45
|
+
# this service uses Typhoeus to send requests to the graph
|
46
|
+
def self.included(base)
|
47
|
+
base.class_eval do
|
48
|
+
require 'typhoeus' unless defined?(Typhoeus)
|
49
|
+
include Typhoeus
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_request(path, args, verb)
|
54
|
+
# if the verb isn't get or post, send it as a post argument
|
55
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
56
|
+
self.class.send(verb, "https://#{FACEBOOK_GRAPH_SERVER}/#{path}", :params => args).body
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/koala.rb
ADDED
@@ -0,0 +1,307 @@
|
|
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
|
+
# Ruby client library for the Facebook Platform.
|
13
|
+
# Copyright 2010 Facebook
|
14
|
+
# Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional
|
15
|
+
#
|
16
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
17
|
+
# not use this file except in compliance with the License. You may obtain
|
18
|
+
# a copy of the License at
|
19
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
20
|
+
#
|
21
|
+
# Unless required by applicable law or agreed to in writing, software
|
22
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
23
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
24
|
+
# License for the specific language governing permissions and limitations
|
25
|
+
# under the License.
|
26
|
+
#
|
27
|
+
# This client library is designed to support the Graph API and the official
|
28
|
+
# Facebook JavaScript SDK, which is the canonical way to implement
|
29
|
+
# Facebook authentication. Read more about the Graph API at
|
30
|
+
# http://developers.facebook.com/docs/api. You can download the Facebook
|
31
|
+
# JavaScript SDK at http://github.com/facebook/connect-js/.
|
32
|
+
|
33
|
+
FACEBOOK_GRAPH_SERVER = "graph.facebook.com"
|
34
|
+
|
35
|
+
class GraphAPI
|
36
|
+
# A client for the Facebook Graph API.
|
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.
|
87
|
+
#
|
88
|
+
# For example,
|
89
|
+
#
|
90
|
+
# graph.put_object("me", "feed", :message => "Hello, world")
|
91
|
+
#
|
92
|
+
# writes "Hello, world" to the active user's wall. Likewise, this
|
93
|
+
# will comment on a the first post of the active user's feed:
|
94
|
+
#
|
95
|
+
# feed = graph.get_connections("me", "feed")
|
96
|
+
# post = feed["data"][0]
|
97
|
+
# graph.put_object(post["id"], "comments", :message => "First!")
|
98
|
+
#
|
99
|
+
# See http://developers.facebook.com/docs/api#publishing for all of
|
100
|
+
# the supported writeable objects.
|
101
|
+
#
|
102
|
+
# Most write operations require extended permissions. For example,
|
103
|
+
# publishing wall posts requires the "publish_stream" permission. See
|
104
|
+
# http://developers.facebook.com/docs/authentication/ for details about
|
105
|
+
# extended permissions.
|
106
|
+
|
107
|
+
raise GraphAPIError.new(nil, "Write operations require an access token") unless @access_token
|
108
|
+
request("#{parent_object}/#{connection_name}", args, "post")
|
109
|
+
end
|
110
|
+
|
111
|
+
def put_wall_post(message, attachment = {}, profile_id = "me")
|
112
|
+
# Writes a wall post to the given profile's wall.
|
113
|
+
#
|
114
|
+
# We default to writing to the authenticated user's wall if no
|
115
|
+
# profile_id is specified.
|
116
|
+
#
|
117
|
+
# attachment adds a structured attachment to the status message being
|
118
|
+
# posted to the Wall. It should be a dictionary of the form:
|
119
|
+
#
|
120
|
+
# {"name": "Link name"
|
121
|
+
# "link": "http://www.example.com/",
|
122
|
+
# "caption": "{*actor*} posted a new review",
|
123
|
+
# "description": "This is a longer description of the attachment",
|
124
|
+
# "picture": "http://www.example.com/thumbnail.jpg"}
|
125
|
+
|
126
|
+
self.put_object(profile_id, "feed", attachment.merge({:message => message}))
|
127
|
+
end
|
128
|
+
|
129
|
+
def put_comment(object_id, message)
|
130
|
+
# Writes the given comment on the given post.
|
131
|
+
self.put_object(object_id, "comments", {:message => message})
|
132
|
+
end
|
133
|
+
|
134
|
+
def put_like(object_id)
|
135
|
+
# Likes the given post.
|
136
|
+
self.put_object(object_id, "likes")
|
137
|
+
end
|
138
|
+
|
139
|
+
def delete_object(id)
|
140
|
+
# Deletes the object with the given ID from the graph.
|
141
|
+
request(id, {}, "delete")
|
142
|
+
end
|
143
|
+
|
144
|
+
def search(search_terms, args = {})
|
145
|
+
# Searches for a given term
|
146
|
+
request("search", args.merge({:q => search_terms}))
|
147
|
+
end
|
148
|
+
|
149
|
+
def request(path, args = {}, verb = "get")
|
150
|
+
# Fetches the given path in the Graph API.
|
151
|
+
args["access_token"] = @access_token if @access_token
|
152
|
+
|
153
|
+
# make the request via the provided service
|
154
|
+
result = make_request(path, args, verb)
|
155
|
+
|
156
|
+
# Facebook sometimes sends results like "true" and "false", which aren't strictly object
|
157
|
+
# and cause JSON.parse to fail
|
158
|
+
# so we account for that
|
159
|
+
response = JSON.parse("[#{result}]")[0]
|
160
|
+
|
161
|
+
# check for errors
|
162
|
+
if response.is_a?(Hash) && error = response["error"]
|
163
|
+
raise GraphAPIError.new(error["code"], error["message"])
|
164
|
+
end
|
165
|
+
|
166
|
+
response
|
167
|
+
end
|
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
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class GraphAPIError < Exception
|
185
|
+
attr_accessor :code
|
186
|
+
def initialize(code, message)
|
187
|
+
super(message)
|
188
|
+
self.code = code
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class OAuth
|
193
|
+
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
194
|
+
@app_id = app_id
|
195
|
+
@app_secret = app_secret
|
196
|
+
@oauth_callback_url = oauth_callback_url
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_user_from_cookie(cookie_hash)
|
200
|
+
# Parses the cookie set by the official Facebook JavaScript SDK.
|
201
|
+
#
|
202
|
+
# cookies should be a dictionary-like object mapping cookie names to
|
203
|
+
# cookie values.
|
204
|
+
#
|
205
|
+
# If the user is logged in via Facebook, we return a dictionary with the
|
206
|
+
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
207
|
+
# and the latter can be used to make authenticated requests to the Graph API.
|
208
|
+
# If the user is not logged in, we return None.
|
209
|
+
#
|
210
|
+
# Download the official Facebook JavaScript SDK at
|
211
|
+
# http://github.com/facebook/connect-js/. Read more about Facebook
|
212
|
+
# authentication at http://developers.facebook.com/docs/authentication/.
|
213
|
+
|
214
|
+
if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
|
215
|
+
# remove the opening/closing quote
|
216
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
217
|
+
|
218
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
219
|
+
components = {}
|
220
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
221
|
+
|
222
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
223
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
224
|
+
|
225
|
+
sig == components["sig"] && Time.now.to_i < components["expires"].to_i ? components : nil
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def url_for_oauth_code(options = {})
|
230
|
+
callback = options[:callback] || @oauth_callback_url
|
231
|
+
permissions = options[:permissions]
|
232
|
+
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
233
|
+
|
234
|
+
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
235
|
+
"https://#{FACEBOOK_GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def url_for_access_token(code, callback = @oauth_callback_url)
|
239
|
+
# Creates the URL for the token corresponding to a given code generated by Facebook
|
240
|
+
"https://#{FACEBOOK_GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
241
|
+
end
|
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)
|
246
|
+
end
|
247
|
+
|
248
|
+
USER_PERMISSIONS = [
|
249
|
+
# PUBLISHING
|
250
|
+
"publish_stream", # Enables your application to post content, comments, and likes to a user's stream and to the streams of the user's friends, without prompting the user each time.
|
251
|
+
"create_event", # Enables your application to create and modify events on the user's behalf
|
252
|
+
"rsvp_event", # Enables your application to RSVP to events on the user's behalf
|
253
|
+
"sms", # Enables your application to send messages to the user and respond to messages from the user via text message
|
254
|
+
"offline_access", # Enables your application to perform authorized requests on behalf of the user at any time (e.g. permanent access token)
|
255
|
+
|
256
|
+
# DATA ACCESS
|
257
|
+
"email", # Provides access to the user's primary email address in the email property
|
258
|
+
"read_stream", # Provides access to all the posts in the user's News Feed and enables your application to perform searches against the user's News Feed
|
259
|
+
"user_about_me", # Provides access to the "About Me" section of the profile in the about property
|
260
|
+
"user_activities", # Provides access to the user's list of activities as the activities connection
|
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
|
+
]
|
306
|
+
end
|
307
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Facebook Graph
|
2
|
+
====
|
3
|
+
|
4
|
+
This Ruby client library is designed to support the
|
5
|
+
[Facebook Graph API](http://developers.facebook.com/docs/api) and the official
|
6
|
+
[Facebook JavaScript SDK](http://github.com/facebook/connect-js), which is
|
7
|
+
the canonical way to implement Facebook authentication. You can read more
|
8
|
+
about the Graph API at [http://developers.facebook.com/docs/api](http://developers.facebook.com/docs/api).
|
9
|
+
|
10
|
+
Basic usage:
|
11
|
+
|
12
|
+
graph = 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 are using the module within a web application with the
|
18
|
+
[JavaScript SDK](http://github.com/facebook/connect-js), you can also use the
|
19
|
+
module to use Facebook for login, parsing the cookie set by the JavaScript SDK
|
20
|
+
for logged in users.
|
21
|
+
|
22
|
+
Testing
|
23
|
+
-----
|
24
|
+
|
25
|
+
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 facebook_tests.rb
|
27
|
+
|
28
|
+
Unit tests for cookie validation will be provided shortly. (You'll also need to add that information into the yml.)
|
29
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
2
|
+
describe "Koala GraphAPI without an access token" do
|
3
|
+
before :each do
|
4
|
+
@graph = Koala::GraphAPI.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should get public data about a user" do
|
8
|
+
result = @graph.get_object("koppel")
|
9
|
+
# the results should have an ID and a name, among other things
|
10
|
+
(result["id"] && result["name"]).should
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not get private data about a user" do
|
14
|
+
result = @graph.get_object("koppel")
|
15
|
+
# updated_time should be a pretty fixed test case
|
16
|
+
result["updated_time"].should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
it "should get public data about a Page" do
|
21
|
+
result = @graph.get_object("contextoptional")
|
22
|
+
# the results should have an ID and a name, among other things
|
23
|
+
(result["id"] && result["name"]).should
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not be able to get data about 'me'" do
|
27
|
+
begin
|
28
|
+
@graph.get_object("me")
|
29
|
+
rescue Koala::GraphAPIError => @right_err
|
30
|
+
rescue Exception => wrong_err
|
31
|
+
end
|
32
|
+
@right_err.should_not be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to get multiple objects" do
|
36
|
+
results = @graph.get_objects(["contextoptional", "naitik"])
|
37
|
+
results.length.should == 2
|
38
|
+
end
|
39
|
+
|
40
|
+
it "shouldn't be able to access connections from users" do
|
41
|
+
begin
|
42
|
+
@graph.get_connections("lukeshepard", "likes")
|
43
|
+
rescue Koala::GraphAPIError => @right_err
|
44
|
+
rescue Exception => wrong_err
|
45
|
+
end
|
46
|
+
@right_err.should_not be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be able to access connections from public Pages" do
|
50
|
+
result = @graph.get_connections("contextoptional", "likes")
|
51
|
+
result["data"].should be_a(Array)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not be able to put an object" do
|
55
|
+
begin
|
56
|
+
@result = @graph.put_object("lukeshepard", "feed", :message => "Hello, world")
|
57
|
+
rescue Koala::GraphAPIError => @right_err
|
58
|
+
rescue Exception => wrong_err
|
59
|
+
end
|
60
|
+
@right_err.should_not be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
|
64
|
+
it "should not be able to post to a feed" do
|
65
|
+
begin
|
66
|
+
@result = @graph.put_wall_post("Hello, world", {:name => "Context Optional", :link => "http://www.contextoptional.com/"}, "contextoptional")
|
67
|
+
rescue Koala::GraphAPIError => @right_err
|
68
|
+
rescue Exception => wrong_err
|
69
|
+
end
|
70
|
+
@right_err.should_not be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not be able to comment on an object" do
|
74
|
+
begin
|
75
|
+
# random public post on the ContextOptional wall
|
76
|
+
@result = @graph.put_comment("7204941866_119776748033392", "The hackathon was great!")
|
77
|
+
rescue Koala::GraphAPIError => @right_err
|
78
|
+
rescue Exception => wrong_err
|
79
|
+
end
|
80
|
+
@right_err.should_not be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not be able to like an object" do
|
84
|
+
begin
|
85
|
+
@result = @graph.put_like("7204941866_119776748033392")
|
86
|
+
rescue Koala::GraphAPIError => @right_err
|
87
|
+
rescue Exception => wrong_err
|
88
|
+
end
|
89
|
+
@right_err.should_not be_nil
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# DELETE
|
94
|
+
it "should not be able to delete posts" do
|
95
|
+
begin
|
96
|
+
# test post on the Ruby SDK Test application
|
97
|
+
@result = @graph.delete_object("115349521819193_113815981982767")
|
98
|
+
rescue Koala::GraphAPIError => @right_err
|
99
|
+
rescue Exception => wrong_err
|
100
|
+
end
|
101
|
+
@right_err.should_not be_nil
|
102
|
+
end
|
103
|
+
|
104
|
+
# SEARCH
|
105
|
+
it "should be able to search" do
|
106
|
+
result = @graph.search("facebook")
|
107
|
+
result["data"].should be_an(Array)
|
108
|
+
end
|
109
|
+
|
110
|
+
# REQUEST
|
111
|
+
# the above tests test this already, but we should do it explicitly
|
112
|
+
it "should be able to send get-only requests" do
|
113
|
+
# to be written
|
114
|
+
end
|
115
|
+
|
116
|
+
end # describe
|
117
|
+
|
118
|
+
end #class
|
119
|
+
|
@@ -0,0 +1,106 @@
|
|
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::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
|
+
@graph.delete_object(@temporary_object_id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should get public data about a user" do
|
18
|
+
result = @graph.get_object("koppel")
|
19
|
+
# the results should have an ID and a name, among other things
|
20
|
+
(result["id"] && result["name"]).should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should get private data about a user" do
|
24
|
+
result = @graph.get_object("koppel")
|
25
|
+
# updated_time should be a pretty fixed test case
|
26
|
+
result["updated_time"].should_not be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should get public data about a Page" do
|
30
|
+
result = @graph.get_object("contextoptional")
|
31
|
+
# the results should have an ID and a name, among other things
|
32
|
+
(result["id"] && result["name"]).should
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should get data about 'me'" do
|
36
|
+
result = @graph.get_object("me")
|
37
|
+
result["updated_time"].should
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be able to get multiple objects" do
|
41
|
+
result = @graph.get_objects(["contextoptional", "naitik"])
|
42
|
+
result.length.should == 2
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to access connections from users" do
|
46
|
+
result = @graph.get_connections("lukeshepard", "likes")
|
47
|
+
result["data"].length.should > 0
|
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
|
+
# PUT
|
56
|
+
it "should be able to put an object" do
|
57
|
+
result = @graph.put_wall_post("Hello, world, from the test suite!")
|
58
|
+
@temporary_object_id = result["id"]
|
59
|
+
@temporary_object_id.should_not be_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# DELETE
|
63
|
+
it "should not be able to delete posts" do
|
64
|
+
result = @graph.put_wall_post("Hello, world, from the test suite delete method!")
|
65
|
+
object_id_to_delete = result["id"]
|
66
|
+
delete_result = @graph.delete_object(object_id_to_delete)
|
67
|
+
delete_result.should == true
|
68
|
+
end
|
69
|
+
|
70
|
+
# these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
|
71
|
+
it "should be able to post to a feed" do
|
72
|
+
result = @graph.put_wall_post("Hello, world, from the test suite again!", {:name => "Context Optional", :link => "http://www.contextoptional.com/"})
|
73
|
+
@temporary_object_id = result["id"]
|
74
|
+
@temporary_object_id.should_not be_nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should be able to comment on an object" do
|
78
|
+
result = @graph.put_wall_post("Hello, world, from the test suite, testing comments!")
|
79
|
+
@temporary_object_id = result["id"]
|
80
|
+
|
81
|
+
# this will be deleted when the post gets deleted
|
82
|
+
comment_result = @graph.put_comment(@temporary_object_id, "it's my comment!")
|
83
|
+
comment_result.should_not be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should be able to like an object" do
|
87
|
+
result = @graph.put_wall_post("Hello, world, from the test suite, testing comments!")
|
88
|
+
@temporary_object_id = result["id"]
|
89
|
+
like_result = @graph.put_like(@temporary_object_id)
|
90
|
+
end
|
91
|
+
|
92
|
+
# SEARCH
|
93
|
+
it "should be able to search" do
|
94
|
+
result = @graph.search("facebook")
|
95
|
+
result["data"].should be_an(Array)
|
96
|
+
end
|
97
|
+
|
98
|
+
# REQUEST
|
99
|
+
# the above tests test this already, but we should do it explicitly
|
100
|
+
it "should be able to send get and put requests" do
|
101
|
+
# to be written
|
102
|
+
end
|
103
|
+
|
104
|
+
end # describe
|
105
|
+
|
106
|
+
end #class
|
data/test/koala_tests.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
puts __FILE__
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec/test/unit'
|
5
|
+
|
6
|
+
require 'koala'
|
7
|
+
require 'koala/facebook_no_access_token_tests'
|
8
|
+
require 'koala/facebook_with_access_token_tests'
|
9
|
+
|
10
|
+
class FacebookTestSuite
|
11
|
+
def self.suite
|
12
|
+
suite = Test::Unit::TestSuite.new
|
13
|
+
suite << FacebookNoAccessTokenTests.suite
|
14
|
+
suite << FacebookWithAccessTokenTests.suite
|
15
|
+
#suite << FacebookCookieTest.suite
|
16
|
+
suite
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# load testing data (see note in readme.md)
|
21
|
+
# I'm seeing a bug with spec and gets where the facebook_test_suite.rb file gets read in when gets is called
|
22
|
+
# until that's solved, we'll need to store/update tokens in the access_token file
|
23
|
+
$testing_data = YAML.load_file("facebook_data.yml") rescue {}
|
24
|
+
|
25
|
+
unless $testing_data["oauth_token"]
|
26
|
+
puts "Access token tests will fail until you store a valid token in facebook_data.yml"
|
27
|
+
end
|
28
|
+
unless cookies = $testing_data["cookie_hash"]
|
29
|
+
puts "Cookie tests will fail until you store a valid token in facebook_data.yml"
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: koala
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
version: "0.4"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Alex Koppel, Rafi Jacoby, Context Optional
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-05-01 00:00:00 -07:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
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.
|
21
|
+
email: alex@alexkoppel.com
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files:
|
27
|
+
- CHANGELOG
|
28
|
+
- lib/http_services.rb
|
29
|
+
- lib/koala.rb
|
30
|
+
files:
|
31
|
+
- CHANGELOG
|
32
|
+
- Manifest
|
33
|
+
- Rakefile
|
34
|
+
- init.rb
|
35
|
+
- koala.gemspec
|
36
|
+
- lib/http_services.rb
|
37
|
+
- lib/koala.rb
|
38
|
+
- readme.md
|
39
|
+
- test/facebook_data.yml
|
40
|
+
- test/koala/facebook_no_access_token_tests.rb
|
41
|
+
- test/koala/facebook_with_access_token_tests.rb
|
42
|
+
- test/koala_tests.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/arsduo/ruby-sdk
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --line-numbers
|
50
|
+
- --inline-source
|
51
|
+
- --title
|
52
|
+
- Koala
|
53
|
+
- --main
|
54
|
+
- readme.md
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 1
|
70
|
+
- 2
|
71
|
+
version: "1.2"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: koala
|
75
|
+
rubygems_version: 1.3.6
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A lightweight, flexible library for Facebook's new Graph API
|
79
|
+
test_files: []
|
80
|
+
|