facebookrb 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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: []
|