facebookrb 0.1.0
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 +47 -0
- data/Rakefile +72 -0
- data/lib/facebookrb.rb +209 -0
- metadata +77 -0
data/README.rdoc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
= facebookrb
|
2
|
+
|
3
|
+
Facebookrb is currently a work in progress.
|
4
|
+
It aims to be a lightweight yet fully featured client for the [Facebook API](http://wiki.developers.facebook.com/index.php/API).
|
5
|
+
It is mostly based on MiniFB (http://github.com/appoxy/mini_fb) and includes some features inspired by rack-facebook (http://github.com/carlosparamio/rack-facebook).
|
6
|
+
In other instances, it emulates the official Facebook PHP library.
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
gem install facebookrb
|
11
|
+
|
12
|
+
== General Usage
|
13
|
+
|
14
|
+
require 'facebookrb'
|
15
|
+
|
16
|
+
You will want to create a client to make calls to Facebook
|
17
|
+
|
18
|
+
fb_client = FacebookRb::Client.new("API_KEY", "SECRET")
|
19
|
+
|
20
|
+
If you are using the middleware, there will be one created for you already,
|
21
|
+
available in `env['facebook.client']`.
|
22
|
+
|
23
|
+
user_hash = fb_client.call("Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)
|
24
|
+
|
25
|
+
Which simply returns the parsed json response from Facebook.
|
26
|
+
The client will ask for results in JSON by default.
|
27
|
+
|
28
|
+
== Middleware
|
29
|
+
|
30
|
+
The middleware checks the signature of Facebook params, and stores them in env.
|
31
|
+
|
32
|
+
require 'facebookrb'
|
33
|
+
|
34
|
+
use Rack::Facebook, :api_key => "APIKEY", :secret => "SECRET"
|
35
|
+
|
36
|
+
The Facebook parameters in the request are stored in `env['facebook.params']` and `env['facebook.client'].fb_params` if the request is valid (http://wiki.developers.facebook.com/index.php/Verifying_The_Signature)
|
37
|
+
|
38
|
+
== Planned Features
|
39
|
+
|
40
|
+
* Facebook Connect
|
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)
|
47
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake/gempackagetask"
|
3
|
+
require "rake/rdoctask"
|
4
|
+
|
5
|
+
require "spec"
|
6
|
+
require "spec/rake/spectask"
|
7
|
+
Spec::Rake::SpecTask.new do |t|
|
8
|
+
t.spec_opts = %w(--format specdoc --colour)
|
9
|
+
t.libs = ["spec", "lib"]
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
task :default => ["spec"]
|
14
|
+
|
15
|
+
# This builds the actual gem. For details of what all these options
|
16
|
+
# mean, and other ones you can add, check the documentation here:
|
17
|
+
#
|
18
|
+
# http://rubygems.org/read/chapter/20
|
19
|
+
#
|
20
|
+
spec = Gem::Specification.new do |s|
|
21
|
+
|
22
|
+
# Change these as appropriate
|
23
|
+
s.name = "facebookrb"
|
24
|
+
s.version = "0.1.0"
|
25
|
+
s.summary = "Simple Facebook API client and middleware"
|
26
|
+
s.author = "John Mendonca"
|
27
|
+
s.email = "joaosinho@gmail.com"
|
28
|
+
s.homepage = "http://github.com/johnmendonca/facebookrb"
|
29
|
+
|
30
|
+
s.has_rdoc = true
|
31
|
+
s.extra_rdoc_files = %w(README.rdoc)
|
32
|
+
s.rdoc_options = %w(--main README.rdoc)
|
33
|
+
|
34
|
+
# Add any extra files to include in the gem
|
35
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{spec,lib/**/*}")
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
|
38
|
+
# If you want to depend on other gems, add them here, along with any
|
39
|
+
# relevant versions
|
40
|
+
s.add_dependency("yajl-ruby")
|
41
|
+
|
42
|
+
# If your tests use any gems, include them here
|
43
|
+
s.add_development_dependency("rspec")
|
44
|
+
|
45
|
+
# If you want to publish automatically to rubyforge, you'll may need
|
46
|
+
# to tweak this, and the publishing task below too.
|
47
|
+
s.rubyforge_project = "facebookrb"
|
48
|
+
end
|
49
|
+
|
50
|
+
# This task actually builds the gem. We also regenerate a static
|
51
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
52
|
+
# be automatically building a gem for this project. If you're not
|
53
|
+
# using GitHub, edit as appropriate.
|
54
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
55
|
+
pkg.gem_spec = spec
|
56
|
+
|
57
|
+
# Generate the gemspec file for github.
|
58
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
59
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate documentation
|
63
|
+
Rake::RDocTask.new do |rd|
|
64
|
+
rd.main = "README.rdoc"
|
65
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
66
|
+
rd.rdoc_dir = "rdoc"
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'Clear out RDoc and generated packages'
|
70
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
71
|
+
rm "#{spec.name}.gemspec"
|
72
|
+
end
|
data/lib/facebookrb.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'yajl'
|
5
|
+
|
6
|
+
module FacebookRb
|
7
|
+
class FacebookError < RuntimeError
|
8
|
+
attr_accessor :fb_code, :fb_message
|
9
|
+
def initialize(error_code, error_msg)
|
10
|
+
self.fb_code = error_code
|
11
|
+
self.fb_message = error_msg
|
12
|
+
super("Facebook error #{error_code}: #{error_msg}" )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Client
|
17
|
+
FB_URL = "http://api.facebook.com/restserver.php"
|
18
|
+
FB_API_VERSION = "1.0"
|
19
|
+
USER_FIELDS = [:uid, :status, :political, :pic_small, :name, :quotes,
|
20
|
+
:is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info,
|
21
|
+
:timezone, :relationship_status, :hometown_location, :about_me,
|
22
|
+
:wall_count, :significant_other_id, :pic_big, :music, :work_history,
|
23
|
+
:sex, :religion, :notes_count, :activities, :pic_square, :movies,
|
24
|
+
:has_added_app, :education_history, :birthday, :birthday_date,
|
25
|
+
:first_name, :meeting_for, :last_name, :interests, :current_location,
|
26
|
+
:pic, :books, :affiliations, :locale, :profile_url, :proxied_email,
|
27
|
+
:email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo,
|
28
|
+
:pic_small_with_logo, :pic_square_with_logo]
|
29
|
+
USER_FIELDS_STANDARD = [:uid, :first_name, :last_name, :name, :timezone,
|
30
|
+
:birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
|
31
|
+
|
32
|
+
attr_accessor :api_key, :secret, :session_key, :fb_params
|
33
|
+
attr_accessor :last_response
|
34
|
+
|
35
|
+
def initialize(api_key, secret, session_key=nil)
|
36
|
+
@api_key = api_key
|
37
|
+
@secret = secret
|
38
|
+
@session_key = session_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def fb_params=(params)
|
42
|
+
@fb_params = params
|
43
|
+
@session_key = params['session_key'] if params
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Call facebook server with a method request. Most keyword arguments
|
48
|
+
# are passed directly to the server with a few exceptions.
|
49
|
+
#
|
50
|
+
# The default return is a parsed json object.
|
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.
|
54
|
+
#
|
55
|
+
# If an error occurs, a FacebookError exception will be raised
|
56
|
+
# with the proper code and message.
|
57
|
+
#
|
58
|
+
def call(method, params={})
|
59
|
+
api_params = params.dup
|
60
|
+
|
61
|
+
# Prepare standard arguments for call
|
62
|
+
api_params['method'] ||= method
|
63
|
+
api_params['api_key'] ||= api_key
|
64
|
+
api_params['session_key'] ||= session_key
|
65
|
+
api_params['call_id'] ||= Time.now.to_s
|
66
|
+
api_params['v'] ||= FB_API_VERSION
|
67
|
+
api_params['format'] ||= 'JSON'
|
68
|
+
|
69
|
+
# Encode any Array or Hash arguments into JSON
|
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
|
76
|
+
|
77
|
+
api_params['sig'] = generate_signature(api_params, self.secret)
|
78
|
+
|
79
|
+
# Call website with POST request
|
80
|
+
response = Net::HTTP.post_form( URI.parse(FB_URL), api_params )
|
81
|
+
|
82
|
+
# Handle response
|
83
|
+
self.last_response = response.body
|
84
|
+
data = Yajl::Parser.parse(response.body)
|
85
|
+
|
86
|
+
if data.include?('error_msg')
|
87
|
+
raise FacebookError.new(data['error_code'], data['error_msg'])
|
88
|
+
end
|
89
|
+
|
90
|
+
return data
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_special_params(method, params)
|
94
|
+
#call_as_apikey for Permissions API
|
95
|
+
#ss Session secret
|
96
|
+
#use_ssl_resources
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the signed parameters that were sent from Facebook. Validates the set
|
100
|
+
# of parameters against the included signature.
|
101
|
+
#
|
102
|
+
# Since Facebook sends data to your callback URL via unsecured means, the
|
103
|
+
# signature is the only way to make sure that the data actually came from
|
104
|
+
# Facebook. So if an app receives a request at the callback URL, it should
|
105
|
+
# always verify the signature that comes with against your own secret key.
|
106
|
+
# Otherwise, it's possible for someone to spoof a request by
|
107
|
+
# pretending to be someone else, i.e.:
|
108
|
+
# www.your-callback-url.com/?fb_user=10101
|
109
|
+
#
|
110
|
+
# Parameters:
|
111
|
+
# params A hash of parameters, i.e., from a HTTP request, or cookies
|
112
|
+
# namespace prefix string for the set of parameters we want to verify.
|
113
|
+
# i.e., fb_sig or fb_post_sig
|
114
|
+
# Returns:
|
115
|
+
# the subset of parameters containing the given prefix, and also matching
|
116
|
+
# the signature associated with them, or nil if the params do not validate
|
117
|
+
#
|
118
|
+
def get_valid_fb_params(params, namespace='fb_sig')
|
119
|
+
prefix = "#{namespace}_"
|
120
|
+
fb_params = Hash.new
|
121
|
+
params.each do |key, value|
|
122
|
+
if key =~ /^#{prefix}(.*)$/
|
123
|
+
fb_params[$1] = value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
signature = params[namespace]
|
128
|
+
return fb_params if signature && valid_signature?(fb_params, signature)
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
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
|
+
# Generate a signature using the application secret key.
|
145
|
+
#
|
146
|
+
# The only two entities that know your secret key are you and Facebook,
|
147
|
+
# according to the Terms of Service. Since nobody else can generate
|
148
|
+
# the signature, you can rely on it to verify that the information
|
149
|
+
# came from Facebook.
|
150
|
+
#
|
151
|
+
# Parameters:
|
152
|
+
# fb_params an array of all Facebook-sent parameters, NOT INCLUDING
|
153
|
+
# the signature itself
|
154
|
+
# secret your app's secret key
|
155
|
+
#
|
156
|
+
# Returns:
|
157
|
+
# a md5 hash to be checked against the signature provided by Facebook
|
158
|
+
#
|
159
|
+
def generate_signature(fb_params, secret)
|
160
|
+
#Convert any symbol keys to strings
|
161
|
+
#otherwise sort() bombs
|
162
|
+
fb_params.each_key do |key|
|
163
|
+
fb_params[key.to_s] = fb_params.delete(key) if key.is_a?(Symbol)
|
164
|
+
end
|
165
|
+
|
166
|
+
str = String.new
|
167
|
+
fb_params.sort.each do |key, value|
|
168
|
+
str << "#{key.to_s}=#{value}"
|
169
|
+
end
|
170
|
+
Digest::MD5.hexdigest("#{str}#{secret}")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# This Rack middleware checks the signature of Facebook params and
|
175
|
+
# converts them to Ruby objects when appropiate. Also, it converts
|
176
|
+
# the request method from the Facebook POST to the original HTTP
|
177
|
+
# method used by the client.
|
178
|
+
#
|
179
|
+
# == Usage
|
180
|
+
#
|
181
|
+
# In your rack builder:
|
182
|
+
#
|
183
|
+
# use Rack::Facebook, :api_key => "APIKEY", :secret => "SECRET"
|
184
|
+
#
|
185
|
+
class Middleware
|
186
|
+
def initialize(app, options={})
|
187
|
+
@app = app
|
188
|
+
@options = options
|
189
|
+
end
|
190
|
+
|
191
|
+
def call(env)
|
192
|
+
request = Rack::Request.new(env)
|
193
|
+
|
194
|
+
client = Client.new(@options[:api_key], @options[:secret])
|
195
|
+
|
196
|
+
if fb_params = client.get_valid_fb_params(request.params, 'fb_sig')
|
197
|
+
#env["facebook.original_method"] = env["REQUEST_METHOD"]
|
198
|
+
#env["REQUEST_METHOD"] = fb_params['request_method']
|
199
|
+
else
|
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
|
205
|
+
|
206
|
+
return @app.call(env)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: facebookrb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Mendonca
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-18 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: yajl-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
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
|
+
description:
|
36
|
+
email: joaosinho@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- lib/facebookrb.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/johnmendonca/facebookrb
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options:
|
53
|
+
- --main
|
54
|
+
- README.rdoc
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project: facebookrb
|
72
|
+
rubygems_version: 1.3.5
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Simple Facebook API client and middleware
|
76
|
+
test_files: []
|
77
|
+
|