facebookrb 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -27
- data/Rakefile +2 -2
- data/lib/facebookrb.rb +214 -61
- metadata +2 -12
data/README.rdoc
CHANGED
@@ -1,47 +1,82 @@
|
|
1
|
-
= facebookrb
|
1
|
+
= facebookrb
|
2
2
|
|
3
|
-
Facebookrb
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
Facebookrb aims to be a lightweight yet fully featured client for the
|
4
|
+
{Facebook API}[http://wiki.developers.facebook.com/index.php/API], drawing
|
5
|
+
on the best features from existing Ruby Facebook libraries.
|
6
|
+
The functions and features are intentionally kept simple, so you can spend
|
7
|
+
your time reading the official Facebook documentation rather than learning
|
8
|
+
how the code works.
|
7
9
|
|
8
10
|
== Installation
|
9
11
|
|
10
12
|
gem install facebookrb
|
11
13
|
|
12
14
|
== General Usage
|
13
|
-
|
14
|
-
require 'facebookrb'
|
15
15
|
|
16
|
-
|
16
|
+
The easiest way is to use the middleware. Put this in your config.ru, Sinatra application, or wherever you declare middleware:
|
17
17
|
|
18
|
-
|
18
|
+
require 'facebookrb'
|
19
19
|
|
20
|
-
|
21
|
-
available in `env['facebook.client']`.
|
20
|
+
use FacebookRb::Middleware, :api_key => "APIKEY", :secret => "SECRET"
|
22
21
|
|
23
|
-
|
22
|
+
This will create a Facebook API client, populate it with any parameters from Facebook, and store it in the Rack env for you.
|
24
23
|
|
25
|
-
|
26
|
-
The client will ask for results in JSON by default.
|
24
|
+
fb = env['facebook.client']
|
27
25
|
|
28
|
-
|
26
|
+
Make a call using short or long format (thanks tmm1/sinbook for the short version)
|
29
27
|
|
30
|
-
|
28
|
+
user = fb.users.getInfo('uids' => '123235345', 'fields' => ['name', 'sex', 'religion'])
|
29
|
+
user = fb.call('users.getInfo', 'uids' => '123235345', 'fields' => ['name', 'sex', 'religion'])
|
31
30
|
|
32
|
-
|
31
|
+
This call parses the JSON from Facebook and returns the resulting objects (a Hash, Array, String, or Integer depending on how complex the JSON is).
|
32
|
+
The raw text of the response is also available:
|
33
|
+
|
34
|
+
fb.call(...)
|
35
|
+
fb.last_response
|
36
|
+
|
37
|
+
If you received params from Facebook, and they are {valid}[http://wiki.developers.facebook.com/index.php/Verifying_The_Signature], then you can access them:
|
38
|
+
|
39
|
+
fb.params['user']
|
40
|
+
fb['user'] # Also works
|
41
|
+
|
42
|
+
The 'session_key' param will automatically be passed forward to any API calls if available.
|
43
|
+
|
44
|
+
== Options
|
45
|
+
|
46
|
+
The options for the middleware and the client are identical, and are values you get from Facebook:
|
47
|
+
|
48
|
+
:api_key, :secret, :canvas_url
|
49
|
+
|
50
|
+
== Features
|
51
|
+
|
52
|
+
=== Facebook Connect
|
53
|
+
|
54
|
+
The library supports reading parameters from cookies, so Connect support should be there (not thoroughly tested ATM).
|
55
|
+
|
56
|
+
=== {Batching}[http://wiki.developers.facebook.com/index.php/Using_Batching_API]
|
57
|
+
|
58
|
+
results = fb.batch do
|
59
|
+
fb.application.getPublicInfo(...)
|
60
|
+
fb.users.getInfo(...)
|
61
|
+
end
|
62
|
+
|
63
|
+
results[0] # => result for first call
|
64
|
+
results[1] # => result for second call
|
33
65
|
|
34
|
-
|
66
|
+
== Potential Features
|
35
67
|
|
36
|
-
|
68
|
+
These would all be easy to add if anyone needs support for them.
|
37
69
|
|
38
|
-
|
70
|
+
* {Base domain}[http://wiki.developers.facebook.com/index.php/Base_Domain]
|
71
|
+
* {Preload FQL}[http://wiki.developers.facebook.com/index.php/Preload_FQL]
|
72
|
+
* {Permissions API}[http://wiki.developers.facebook.com/index.php/Permissions_API]
|
73
|
+
* {Session Secret}[http://wiki.developers.facebook.com/index.php/Session_Secret]
|
74
|
+
* Converting the request method from the Facebook POST to the original HTTP method used by the client. (http://wiki.developers.facebook.com/index.php/Fb_sig_request_method)
|
39
75
|
|
40
|
-
|
41
|
-
* Batching (http://wiki.developers.facebook.com/index.php/Using_Batching_API)
|
42
|
-
* Base domain (http://wiki.developers.facebook.com/index.php/Base_Domain)
|
43
|
-
* Preload FQL (http://wiki.developers.facebook.com/index.php/Preload_FQL)
|
44
|
-
* Permissions API (http://wiki.developers.facebook.com/index.php/Permissions_API)
|
45
|
-
* Session Secret (http://wiki.developers.facebook.com/index.php/Session_Secret)
|
46
|
-
* Converting the request method from the Facebook POST to the original HTTP method used by the client. (http://wiki.developers.facebook.com/index.php/Fb_sig_request_method)
|
76
|
+
== Acknowledgements
|
47
77
|
|
78
|
+
The code for this project was initially derived from:
|
79
|
+
* {sinbook}[http://github.com/tmm1/sinbook]
|
80
|
+
* {MiniFB}[http://github.com/appoxy/mini_fb]
|
81
|
+
* {rack-facebook}[http://github.com/carlosparamio/rack-facebook]
|
82
|
+
* The official Facebook PHP library
|
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ spec = Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
# Change these as appropriate
|
23
23
|
s.name = "facebookrb"
|
24
|
-
s.version = "0.1.
|
24
|
+
s.version = "0.1.1"
|
25
25
|
s.summary = "Simple Facebook API client and middleware"
|
26
26
|
s.author = "John Mendonca"
|
27
27
|
s.email = "joaosinho@gmail.com"
|
@@ -40,7 +40,7 @@ spec = Gem::Specification.new do |s|
|
|
40
40
|
s.add_dependency("yajl-ruby")
|
41
41
|
|
42
42
|
# If your tests use any gems, include them here
|
43
|
-
s.add_development_dependency("rspec")
|
43
|
+
#s.add_development_dependency("rspec")
|
44
44
|
|
45
45
|
# If you want to publish automatically to rubyforge, you'll may need
|
46
46
|
# to tweak this, and the publishing task below too.
|
data/lib/facebookrb.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rack/request' unless defined?(Rack::Request)
|
1
2
|
require 'uri'
|
2
3
|
require 'net/http'
|
3
4
|
require 'digest/md5'
|
@@ -29,28 +30,41 @@ module FacebookRb
|
|
29
30
|
USER_FIELDS_STANDARD = [:uid, :first_name, :last_name, :name, :timezone,
|
30
31
|
:birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
|
31
32
|
|
32
|
-
attr_accessor :api_key, :secret, :
|
33
|
-
attr_accessor :last_response
|
33
|
+
attr_accessor :api_key, :secret, :canvas_url, :format
|
34
|
+
attr_accessor :pending_batch, :batch_queue, :last_response
|
34
35
|
|
35
|
-
def initialize(
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
def initialize(options = {})
|
37
|
+
self.api_key = options[:api_key] || options['api_key']
|
38
|
+
self.secret = options[:secret] || options['secret']
|
39
|
+
self.canvas_url = options[:canvas_url] || options['canvas_url']
|
40
|
+
self.format = options[:format] || options['format'] || 'JSON'
|
39
41
|
end
|
40
42
|
|
41
|
-
def
|
42
|
-
@
|
43
|
-
|
43
|
+
def params
|
44
|
+
@params ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](key)
|
48
|
+
params[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid?
|
52
|
+
!params.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def url(path = nil)
|
56
|
+
path ? "#{canvas_url}#{path}" : canvas_url
|
57
|
+
end
|
58
|
+
|
59
|
+
def addurl
|
60
|
+
"http://apps.facebook.com/add.php?api_key=#{self.api_key}"
|
44
61
|
end
|
45
62
|
|
46
63
|
#
|
47
64
|
# Call facebook server with a method request. Most keyword arguments
|
48
65
|
# are passed directly to the server with a few exceptions.
|
49
66
|
#
|
50
|
-
#
|
51
|
-
# Unless the 'format' and/or 'callback' arguments are given,
|
52
|
-
# in which case the raw text of the reply is returned. The string
|
53
|
-
# will always be returned, even during errors.
|
67
|
+
# Returns a parsed json object.
|
54
68
|
#
|
55
69
|
# If an error occurs, a FacebookError exception will be raised
|
56
70
|
# with the proper code and message.
|
@@ -60,42 +74,177 @@ module FacebookRb
|
|
60
74
|
|
61
75
|
# Prepare standard arguments for call
|
62
76
|
api_params['method'] ||= method
|
63
|
-
api_params['api_key'] ||= api_key
|
64
|
-
api_params['
|
65
|
-
api_params['
|
77
|
+
api_params['api_key'] ||= self.api_key
|
78
|
+
api_params['format'] ||= self.format
|
79
|
+
api_params['session_key'] ||= self.params['session_key']
|
80
|
+
api_params['call_id'] ||= Time.now.to_f.to_s
|
66
81
|
api_params['v'] ||= FB_API_VERSION
|
67
|
-
api_params['format'] ||= 'JSON'
|
68
82
|
|
69
|
-
|
70
|
-
json_encoder = Yajl::Encoder.new
|
71
|
-
api_params.each do |key, value|
|
72
|
-
if value.is_a?(Array) || value.is_a?(Hash)
|
73
|
-
api_params[key] = json_encoder.encode(value)
|
74
|
-
end
|
75
|
-
end
|
83
|
+
convert_outgoing_params(api_params)
|
76
84
|
|
77
85
|
api_params['sig'] = generate_signature(api_params, self.secret)
|
78
86
|
|
79
|
-
#
|
87
|
+
#If in a batch, stash the params in the queue and bail
|
88
|
+
if pending_batch
|
89
|
+
batch_queue << api_params.map { |k,v| "#{k}=#{v}" }.join('&')
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
# Call Facebook with POST request
|
80
94
|
response = Net::HTTP.post_form( URI.parse(FB_URL), api_params )
|
81
95
|
|
82
96
|
# Handle response
|
83
97
|
self.last_response = response.body
|
84
98
|
data = Yajl::Parser.parse(response.body)
|
85
99
|
|
86
|
-
if data.
|
100
|
+
if data.is_a?(Hash) && data['error_msg']
|
87
101
|
raise FacebookError.new(data['error_code'], data['error_msg'])
|
88
102
|
end
|
89
103
|
|
90
104
|
return data
|
91
105
|
end
|
92
106
|
|
107
|
+
#
|
108
|
+
# Performs a batch API operation (batch.run)
|
109
|
+
#
|
110
|
+
# Example:
|
111
|
+
# results = fb.batch do
|
112
|
+
# fb.application.getPublicInfo(...)
|
113
|
+
# fb.users.getInfo(...)
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# Options:
|
117
|
+
# * :raise_errors (default true) - since a batch returns results for
|
118
|
+
# multiple calls, some may return errors while others do not, by default
|
119
|
+
# an exception will be raised if any errors are found. Set to
|
120
|
+
# false to disable this and handle errors yourself
|
121
|
+
def batch(options = {})
|
122
|
+
return unless block_given?
|
123
|
+
#TODO: real error code/message
|
124
|
+
raise FacebookError.new('error_code', 'error_msg') if pending_batch
|
125
|
+
|
126
|
+
self.batch_queue = []
|
127
|
+
self.pending_batch = true
|
128
|
+
options[:raise_errors] = true if options[:raise_errors].nil?
|
129
|
+
|
130
|
+
yield self
|
131
|
+
|
132
|
+
self.pending_batch = false
|
133
|
+
results = call('batch.run',
|
134
|
+
:method_feed => self.batch_queue,
|
135
|
+
:serial_only => true)
|
136
|
+
self.batch_queue = nil
|
137
|
+
|
138
|
+
#Batch results are an array of JSON strings, parse each
|
139
|
+
results.map! { |json| Yajl::Parser.parse(json) }
|
140
|
+
|
141
|
+
results.each do |data|
|
142
|
+
if data.is_a?(Hash) && data['error_msg']
|
143
|
+
raise FacebookError.new(data['error_code'], data['error_msg'])
|
144
|
+
end
|
145
|
+
end if options[:raise_errors]
|
146
|
+
|
147
|
+
results
|
148
|
+
end
|
149
|
+
|
93
150
|
def add_special_params(method, params)
|
94
151
|
#call_as_apikey for Permissions API
|
95
152
|
#ss Session secret
|
96
153
|
#use_ssl_resources
|
97
154
|
end
|
98
155
|
|
156
|
+
#
|
157
|
+
# Extracts and validates any Facebook parameters from the request, and
|
158
|
+
# stores them in the client.
|
159
|
+
# We look for parameters from POST, GET, then cookies, in that order.
|
160
|
+
# POST and GET are always more up-to-date than cookies, so we prefer
|
161
|
+
# those if they are available.
|
162
|
+
#
|
163
|
+
# Parameters:
|
164
|
+
# env the Rack environment, or a Rack request
|
165
|
+
#
|
166
|
+
def extract_params(env)
|
167
|
+
#Accept a Request object or the env
|
168
|
+
if env.is_a?(Rack::Request)
|
169
|
+
request = env
|
170
|
+
else
|
171
|
+
request = Rack::Request.new(env)
|
172
|
+
end
|
173
|
+
|
174
|
+
#Fetch from POST
|
175
|
+
fb_params = get_params_from(request.POST, 'fb_sig')
|
176
|
+
|
177
|
+
#Fetch from GET
|
178
|
+
unless fb_params
|
179
|
+
# note that with preload FQL, it's possible to receive POST params in
|
180
|
+
# addition to GET, and a different prefix is used for each
|
181
|
+
fb_get_params = get_params_from(request.GET, 'fb_sig') || {}
|
182
|
+
fb_post_params = get_params_from(request.POST, 'fb_post_sig') || {}
|
183
|
+
fb_get_params.merge!(fb_post_params)
|
184
|
+
fb_params = fb_get_params unless fb_get_params.empty?
|
185
|
+
end
|
186
|
+
|
187
|
+
#Fetch from cookies
|
188
|
+
unless fb_params
|
189
|
+
fb_params = get_params_from(request.cookies, self.api_key)
|
190
|
+
end
|
191
|
+
|
192
|
+
@params = convert_incoming_params(fb_params)
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Converts some parameter values into more useful forms:
|
197
|
+
# * 0 or 1 into true/false
|
198
|
+
# * Time values into Time objects
|
199
|
+
# * Comma separated lists into arrays
|
200
|
+
#
|
201
|
+
def convert_incoming_params(params)
|
202
|
+
return nil unless params
|
203
|
+
|
204
|
+
params.each do |key, value|
|
205
|
+
case key
|
206
|
+
when 'friends', 'linked_account_ids'
|
207
|
+
params[key] = value.split(',')
|
208
|
+
when /(time|expires)$/
|
209
|
+
if value == '0'
|
210
|
+
params[key] = nil
|
211
|
+
else
|
212
|
+
params[key] = Time.at(value.to_f)
|
213
|
+
end
|
214
|
+
when /^(logged_out|position_|in_|is_)/, /added$/
|
215
|
+
params[key] = (value == '1')
|
216
|
+
else
|
217
|
+
params[key] = value
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
params
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Converts parameters being sent to Facebook from ruby objects to the
|
226
|
+
# appropriate text representation
|
227
|
+
#
|
228
|
+
def convert_outgoing_params(params)
|
229
|
+
json_encoder = Yajl::Encoder.new
|
230
|
+
params.each do |key, value|
|
231
|
+
params.delete(key) if value.nil?
|
232
|
+
|
233
|
+
case value
|
234
|
+
when Array, Hash
|
235
|
+
params[key] = json_encoder.encode(value)
|
236
|
+
when Time
|
237
|
+
params[key] = value.to_i.to_s
|
238
|
+
when TrueClass
|
239
|
+
params[key] = '1'
|
240
|
+
when FalseClass
|
241
|
+
params[key] = '0'
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
params
|
246
|
+
end
|
247
|
+
|
99
248
|
# Get the signed parameters that were sent from Facebook. Validates the set
|
100
249
|
# of parameters against the included signature.
|
101
250
|
#
|
@@ -115,7 +264,7 @@ module FacebookRb
|
|
115
264
|
# the subset of parameters containing the given prefix, and also matching
|
116
265
|
# the signature associated with them, or nil if the params do not validate
|
117
266
|
#
|
118
|
-
def
|
267
|
+
def get_params_from(params, namespace='fb_sig')
|
119
268
|
prefix = "#{namespace}_"
|
120
269
|
fb_params = Hash.new
|
121
270
|
params.each do |key, value|
|
@@ -124,23 +273,12 @@ module FacebookRb
|
|
124
273
|
end
|
125
274
|
end
|
126
275
|
|
127
|
-
|
128
|
-
|
276
|
+
param_sig = params[namespace]
|
277
|
+
gen_sig = generate_signature(fb_params, self.secret)
|
278
|
+
return fb_params if param_sig == gen_sig
|
129
279
|
nil
|
130
280
|
end
|
131
281
|
|
132
|
-
# Validates that a given set of parameters match their signature.
|
133
|
-
# Parameters all match a given input prefix, such as "fb_sig".
|
134
|
-
#
|
135
|
-
# Parameters:
|
136
|
-
# fb_params an array of all Facebook-sent parameters, not
|
137
|
-
# including the signature itself
|
138
|
-
# expected_sig the expected result to check against
|
139
|
-
#
|
140
|
-
def valid_signature?(fb_params, expected_sig)
|
141
|
-
expected_sig == generate_signature(fb_params, self.secret)
|
142
|
-
end
|
143
|
-
|
144
282
|
# Generate a signature using the application secret key.
|
145
283
|
#
|
146
284
|
# The only two entities that know your secret key are you and Facebook,
|
@@ -157,17 +295,38 @@ module FacebookRb
|
|
157
295
|
# a md5 hash to be checked against the signature provided by Facebook
|
158
296
|
#
|
159
297
|
def generate_signature(fb_params, secret)
|
160
|
-
|
161
|
-
#
|
162
|
-
|
163
|
-
|
298
|
+
str = fb_params.map { |k,v| "#{k}=#{v}" }.sort.join
|
299
|
+
Digest::MD5.hexdigest("#{str}#{secret}")
|
300
|
+
end
|
301
|
+
|
302
|
+
#
|
303
|
+
# Allows making calls like `client.users.getInfo`
|
304
|
+
#
|
305
|
+
class APIProxy
|
306
|
+
TYPES = %w[ admin application auth comments connect data events
|
307
|
+
fbml feed fql friends groups links liveMessage notes notifications
|
308
|
+
pages photos profile sms status stream users video ]
|
309
|
+
|
310
|
+
alias :__class__ :class
|
311
|
+
alias :__inspect__ :inspect
|
312
|
+
instance_methods.each { |m| undef_method m unless m =~ /^(__|object_id)/ }
|
313
|
+
alias :inspect :__inspect__
|
314
|
+
|
315
|
+
def initialize name, obj
|
316
|
+
@name, @obj = name, obj
|
164
317
|
end
|
165
318
|
|
166
|
-
|
167
|
-
|
168
|
-
str << "#{key.to_s}=#{value}"
|
319
|
+
def method_missing method, opts = {}
|
320
|
+
@obj.call "#{@name}.#{method}", opts
|
169
321
|
end
|
170
|
-
|
322
|
+
end
|
323
|
+
|
324
|
+
APIProxy::TYPES.each do |n|
|
325
|
+
class_eval %[
|
326
|
+
def #{n}
|
327
|
+
(@proxies||={})[:#{n}] ||= APIProxy.new(:#{n}, self)
|
328
|
+
end
|
329
|
+
]
|
171
330
|
end
|
172
331
|
end
|
173
332
|
|
@@ -189,19 +348,13 @@ module FacebookRb
|
|
189
348
|
end
|
190
349
|
|
191
350
|
def call(env)
|
192
|
-
|
193
|
-
|
194
|
-
client = Client.new(@options[:api_key], @options[:secret])
|
351
|
+
fb = Client.new(@options)
|
352
|
+
fb.extract_params(env)
|
195
353
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
fb_params = client.get_valid_fb_params(request.cookies, @options[:api_key])
|
201
|
-
end
|
202
|
-
client.fb_params = fb_params
|
203
|
-
env['facebook.params'] = fb_params
|
204
|
-
env['facebook.client'] = client
|
354
|
+
env['facebook.client'] = fb
|
355
|
+
|
356
|
+
#env["facebook.original_method"] = env["REQUEST_METHOD"]
|
357
|
+
#env["REQUEST_METHOD"] = fb_params['request_method']
|
205
358
|
|
206
359
|
return @app.call(env)
|
207
360
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: facebookrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mendonca
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-05 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,16 +22,6 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "0"
|
24
24
|
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: rspec
|
27
|
-
type: :development
|
28
|
-
version_requirement:
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: "0"
|
34
|
-
version:
|
35
25
|
description:
|
36
26
|
email: joaosinho@gmail.com
|
37
27
|
executables: []
|