facebookrb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|