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.
Files changed (4) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +72 -0
  3. data/lib/facebookrb.rb +209 -0
  4. 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
+