mjfreshyfresh-mini_fb 0.1.15 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/mini_fb.rb +212 -256
  2. data/test/test_mini_fb.rb +64 -20
  3. metadata +3 -3
data/lib/mini_fb.rb CHANGED
@@ -1,290 +1,246 @@
1
1
  require 'digest/md5'
2
2
  require 'erb'
3
3
  require 'json' unless defined? JSON
4
+ require 'net/http'
5
+ require 'cgi' unless defined? Rack
4
6
 
5
7
  module MiniFB
8
+
9
+ FB_URL = "http://api.facebook.com/restserver.php"
10
+ FB_API_VERSION = "1.0"
11
+ RETRY_ATTEMPTS = 10
12
+
13
+ BAD_JSON_METHODS = %w{ users.getLoggedInUser }
14
+
15
+ @@logging = false
16
+
17
+ def enable_logging
18
+ @@logging = true
19
+ end
20
+
21
+ def disable_logging
22
+ @@logging = false
23
+ end
24
+
25
+ class BadJSONDataError < StandardError
26
+ # Error that happens when facebook sends back bad JSON
27
+ def initialize(returned_data)
28
+ super "Facebook returned bad JSON data - #{returned_data}"
29
+ end
30
+ end
6
31
 
7
- # Global constants
8
- FB_URL = "http://api.facebook.com/restserver.php"
9
- FB_API_VERSION = "1.0"
10
-
11
- RETRY_ATTEMPTS = 10
32
+ class FaceBookError < StandardError
33
+ # Error that happens during a facebook call.
34
+ def initialize( error_code, error_msg )
35
+ super("Facebook error #{error_code}: #{error_msg}" )
36
+ end
37
+ end
12
38
 
13
- @@logging = false
39
+ class Session
40
+ attr_accessor :api_key, :secret_key, :session_key, :uid
14
41
 
15
- def enable_logging
16
- @@logging = true
42
+ def initialize(api_key, secret_key, session_key, uid)
43
+ @api_key = api_key
44
+ @secret_key = FaceBookSecret.new secret_key
45
+ @session_key = session_key
46
+ @uid = uid
17
47
  end
18
48
 
19
- def disable_logging
20
- @@logging = false
49
+ # returns current user
50
+ def user
51
+ return @user unless @user.nil?
52
+ @user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
53
+ @user
21
54
  end
22
55
 
23
- class FaceBookError < StandardError
24
- # Error that happens during a facebook call.
25
- def initialize( error_code, error_msg )
26
- super("Facebook error #{error_code}: #{error_msg}" )
27
- end
56
+ def photos
57
+ Photos.new(self)
28
58
  end
29
59
 
30
- class Session
31
- attr_accessor :api_key, :secret_key, :session_key, :uid
32
-
33
-
34
- def initialize(api_key, secret_key, session_key, uid)
35
- @api_key = api_key
36
- @secret_key = FaceBookSecret.new secret_key
37
- @session_key = session_key
38
- @uid = uid
39
- end
40
-
41
- # returns current user
42
- def user
43
- return @user unless @user.nil?
44
- @user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
45
- @user
46
- end
47
-
48
- def photos
49
- Photos.new(self)
50
- end
51
-
52
-
53
- def call(method, params={})
54
- return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
55
- end
56
-
57
-
60
+ def call(method, params={})
61
+ return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
58
62
  end
59
- class User
60
- FIELDS = [:uid, :status, :political, :pic_small, :name, :quotes, :is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info, :timezone, :relationship_status, :hometown_location, :about_me, :wall_count, :significant_other_id, :pic_big, :music, :work_history, :sex, :religion, :notes_count, :activities, :pic_square, :movies, :has_added_app, :education_history, :birthday, :birthday_date, :first_name, :meeting_for, :last_name, :interests, :current_location, :pic, :books, :affiliations, :locale, :profile_url, :proxied_email, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
61
- STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
62
-
63
- def self.all_fields
64
- FIELDS.join(",")
65
- end
66
-
67
- def self.standard_fields
68
- STANDARD_FIELDS.join(",")
69
- end
70
-
71
- def initialize(fb_hash, session)
72
- @fb_hash = fb_hash
73
- @session = session
74
- end
75
-
76
- def [](key)
77
- @fb_hash[key]
78
- end
79
-
80
- def uid
81
- return self["uid"]
82
- end
83
-
84
- def profile_photos
85
- @session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
86
- end
87
-
88
- def profile_pic_album_id
89
- merge_aid(-3, uid)
90
- end
91
-
92
- def merge_aid(aid, uid)
93
- uid = uid.to_i
94
- ret = (uid << 32) + (aid & 0xFFFFFFFF)
95
- # puts 'merge_aid=' + ret.inspect
96
- return ret
97
- end
63
+ end
64
+
65
+ class User
66
+ FIELDS = [:uid, :status, :political, :pic_small, :name, :quotes, :is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info, :timezone, :relationship_status, :hometown_location, :about_me, :wall_count, :significant_other_id, :pic_big, :music, :work_history, :sex, :religion, :notes_count, :activities, :pic_square, :movies, :has_added_app, :education_history, :birthday, :birthday_date, :first_name, :meeting_for, :last_name, :interests, :current_location, :pic, :books, :affiliations, :locale, :profile_url, :proxied_email, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
67
+ STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]
68
+
69
+ def self.all_fields
70
+ FIELDS.join(",")
98
71
  end
99
72
 
100
- class Photos
101
-
102
- def initialize(session)
103
- @session = session
104
- end
105
-
106
- def get(params)
107
- pids = params["pids"]
108
- if !pids.nil? && pids.is_a?(Array)
109
- pids = pids.join(",")
110
- params["pids"] = pids
111
- end
112
- @session.call("photos.get", params)
113
- end
73
+ def self.standard_fields
74
+ STANDARD_FIELDS.join(",")
114
75
  end
115
76
 
116
- BAD_JSON_METHODS = ["users.getLoggedInUser","auth.promoteSession"]
117
-
118
- # Call facebook server with a method request. Most keyword arguments
119
- # are passed directly to the server with a few exceptions.
120
- # The 'sig' value will always be computed automatically.
121
- # The 'v' version will be supplied automatically if needed.
122
- # The 'call_id' defaults to True, which will generate a valid
123
- # number. Otherwise it should be a valid number or False to disable.
124
-
125
- # The default return is a parsed json object.
126
- # Unless the 'format' and/or 'callback' arguments are given,
127
- # in which case the raw text of the reply is returned. The string
128
- # will always be returned, even during errors.
129
-
130
- # If an error occurs, a FacebookError exception will be raised
131
- # with the proper code and message.
132
-
133
- # The secret argument should be an instance of FacebookSecret
134
- # to hide value from simple introspection.
135
- def MiniFB.call( api_key, secret, method, kwargs )
136
-
137
- puts 'kwargs=' + kwargs.inspect if @@logging
138
-
139
- if secret.is_a? String
140
- secret = FaceBookSecret.new(secret)
141
- end
142
-
143
- # Prepare arguments for call
144
- call_id = kwargs.fetch("call_id", true)
145
- if call_id == true then
146
- kwargs["call_id"] = Time.now.tv_sec.to_s
147
- else
148
- kwargs.delete("call_id")
149
- end
150
-
151
- custom_format = kwargs.include?("format") or kwargs.include?("callback")
152
- kwargs["format"] ||= "JSON"
153
- kwargs["v"] ||= FB_API_VERSION
154
- kwargs["api_key"]||= api_key
155
- kwargs["method"] ||= method
156
-
157
- # Hash with secret
158
- arg_string = String.new
159
- kwargs.each { |k,v| (kwargs[k.to_s] = v ; kwargs.delete(k)) if k.is_a? Symbol }
160
- kwargs.sort.each { |kv| arg_string << kv[0] << "=" << kv[1].to_s }
161
- kwargs["sig"] = Digest::MD5.hexdigest( arg_string + secret.value.call )
162
-
163
- # Call website with POST request
164
- attempt = 0
165
- begin
166
- response = Net::HTTP.post_form( URI.parse(FB_URL), kwargs )
167
- rescue SocketError, Errno::ECONNRESET, EOFError => err
168
- if attempt < RETRY_ATTEMPTS
169
- attempt += 1
170
- retry
171
- else
172
- raise
173
- end
174
- # raise IOError.new( "Cannot connect to the facebook server: " + err )
175
- rescue
176
- raise
177
- end
178
-
179
- # Handle response
180
- return response.body if custom_format
181
-
182
- fb_method = kwargs["method"]
183
- body = response.body
184
-
185
- begin
186
- data = JSON.parse( body )
187
- puts 'response=' + data.inspect if @@logging
188
- if data.include?( "error_msg" ) then
189
- raise FaceBookError.new( data["error_code"] || 1, data["error_msg"] )
190
- end
191
-
192
- rescue JSON::ParserError => ex
193
- if BAD_JSON_METHODS.include?(fb_method) # Little hack because this response isn't valid JSON
194
- return body
195
- else
196
- raise ex
197
- end
198
- end
199
- return data
77
+ def initialize(fb_hash, session)
78
+ @fb_hash = fb_hash
79
+ @session = session
200
80
  end
201
81
 
202
- # Returns true is signature is valid, false otherwise.
203
- def MiniFB.verify_signature( secret, arguments )
204
- signature = arguments.delete( "fb_sig" )
205
- return false if signature.nil?
82
+ def [](key)
83
+ @fb_hash[key]
84
+ end
206
85
 
207
- unsigned = Hash.new
208
- signed = Hash.new
86
+ def uid
87
+ return self["uid"]
88
+ end
209
89
 
210
- arguments.each do |k, v|
211
- if k =~ /^fb_sig_(.*)/ then
212
- signed[$1] = v
213
- else
214
- unsigned[k] = v
215
- end
216
- end
90
+ def profile_photos
91
+ @session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
92
+ end
217
93
 
218
- arg_string = String.new
219
- signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
220
- if Digest::MD5.hexdigest( arg_string + secret ) == signature
221
- return true
222
- end
223
- return false
94
+ def profile_pic_album_id
95
+ merge_aid(-3, uid)
224
96
  end
225
97
 
226
- # Returns the login/add app url for your application.
227
- #
228
- # options:
229
- # - :next => a relative next page to go to. relative to your facebook connect url or if :canvas is true, then relative to facebook app url
230
- # - :canvas => true/false - to say whether this is a canvas app or not
231
- def self.login_url(api_key, options={})
232
- login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
233
- login_url << "&next=#{options[:next]}" if options[:next]
234
- login_url << "&canvas" if options[:canvas]
235
- login_url
98
+ def merge_aid(aid, uid)
99
+ uid = uid.to_i
100
+ ret = (uid << 32) + (aid & 0xFFFFFFFF)
101
+ # puts 'merge_aid=' + ret.inspect
102
+ return ret
236
103
  end
104
+ end
237
105
 
238
- # This function expects arguments as a hash, so
239
- # it is agnostic to different POST handling variants in ruby.
240
- #
241
- # Validate the arguments received from facebook. This is usually
242
- # sent for the iframe in Facebook's canvas. It is not necessary
243
- # to use this on the auth_token and uid passed to callbacks like
244
- # post-add and post-remove.
245
- #
246
- # The arguments must be a mapping of to string keys and values
247
- # or a string of http request data.
248
- #
249
- # If the data is invalid or not signed properly, an empty
250
- # dictionary is returned.
251
- #
252
- # The secret argument should be an instance of FacebookSecret
253
- # to hide value from simple introspection.
254
- #
255
- # DEPRECATED, use verify_signature instead
256
- def MiniFB.validate( secret, arguments )
257
-
258
- signature = arguments.delete( "fb_sig" )
259
- return arguments if signature.nil?
260
-
261
- unsigned = Hash.new
262
- signed = Hash.new
263
-
264
- arguments.each do |k, v|
265
- if k =~ /^fb_sig_(.*)/ then
266
- signed[$1] = v
267
- else
268
- unsigned[k] = v
269
- end
270
- end
106
+ class Photos
271
107
 
272
- arg_string = String.new
273
- signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
274
- if Digest::MD5.hexdigest( arg_string + secret ) != signature
275
- unsigned # Hash is incorrect, return only unsigned fields.
108
+ def initialize(session)
109
+ @session = session
110
+ end
111
+
112
+ def get(params)
113
+ pids = params["pids"]
114
+ if !pids.nil? && pids.is_a?(Array)
115
+ pids = pids.join(",")
116
+ params["pids"] = pids
117
+ end
118
+ @session.call("photos.get", params)
119
+ end
120
+ end
121
+
122
+ # Call facebook server with a method request. Most keyword arguments
123
+ # are passed directly to the server with a few exceptions.
124
+ # The 'sig' value will always be computed automatically.
125
+ # The 'v' version will be supplied automatically if needed.
126
+ # The 'call_id' defaults to True, which will generate a valid
127
+ # number. Otherwise it should be a valid number or False to disable.
128
+
129
+ # The default return is a parsed json object.
130
+ # Unless the 'format' and/or 'callback' arguments are given,
131
+ # in which case the raw text of the reply is returned. The string
132
+ # will always be returned, even during errors.
133
+
134
+ # If an error occurs, a FacebookError exception will be raised
135
+ # with the proper code and message.
136
+
137
+ # The secret argument should be an instance of FacebookSecret
138
+ # to hide value from simple introspection.
139
+ def MiniFB.call( api_key, secret, method, kwargs )
140
+
141
+ puts 'kwargs=' + kwargs.inspect if @@logging
142
+
143
+ if secret.is_a? String
144
+ secret = FaceBookSecret.new(secret)
145
+ end
146
+
147
+ # Prepare arguments for call
148
+ call_id = kwargs.fetch("call_id", true)
149
+ if call_id == true then
150
+ kwargs["call_id"] = Time.now.tv_sec.to_s
151
+ else
152
+ kwargs.delete("call_id")
153
+ end
154
+
155
+ custom_format = kwargs.include?("format") or kwargs.include?("callback")
156
+ kwargs["format"] ||= "JSON"
157
+ kwargs["v"] ||= FB_API_VERSION
158
+ kwargs["api_key"]||= api_key
159
+ kwargs["method"] ||= method
160
+
161
+ # Hash with secret
162
+ arg_string = String.new
163
+ kwargs.each { |k,v| (kwargs[k.to_s] = v ; kwargs.delete(k)) if k.is_a? Symbol }
164
+ kwargs.sort.each { |kv| arg_string << kv[0] << "=" << kv[1].to_s }
165
+ kwargs["sig"] = Digest::MD5.hexdigest( arg_string + secret.value.call )
166
+
167
+ # Call website with POST request
168
+ attempt = 0
169
+ begin
170
+ response = Net::HTTP.post_form( URI.parse(FB_URL), kwargs )
171
+ rescue SocketError, Errno::ECONNRESET, EOFError => err
172
+ if attempt < RETRY_ATTEMPTS
173
+ attempt += 1
174
+ retry
276
175
  else
277
- unsigned.merge signed
176
+ raise
278
177
  end
279
- end
178
+ # raise IOError.new( "Cannot connect to the facebook server: " + err )
179
+ rescue
180
+ raise
181
+ end
280
182
 
281
- class FaceBookSecret
282
- # Simple container that stores a secret value.
283
- # Proc cannot be dumped or introspected by normal tools.
284
- attr_reader :value
183
+ # Handle response
184
+ return response.body if custom_format
285
185
 
286
- def initialize( value )
287
- @value = Proc.new { value }
288
- end
289
- end
186
+ begin
187
+ data = JSON.parse( response.body )
188
+ puts 'response=' + data.inspect if @@logging
189
+ if data.include?( "error_msg" ) then
190
+ raise FaceBookError.new( data["error_code"] || 1, data["error_msg"] )
191
+ end
192
+
193
+ rescue JSON::ParserError => ex
194
+ return response.body if BAD_JSON_METHODS.include? kwargs["method"]
195
+ return (response.body == 'true') if %w{true false}.include?(response.body) # Hack for Facebook boolean API calls with BAD JSON.
196
+ raise BadJSONDataError, "#{kwargs["method"]} returned \"#{response.body}\""
197
+ end
198
+ return data
199
+ end
200
+
201
+ # Returns true is signature is valid, false otherwise.
202
+ def MiniFB.verify_signature( secret, arguments )
203
+ signature = arguments.delete( "fb_sig" )
204
+ return false if signature.nil?
205
+
206
+ unsigned = Hash.new
207
+ signed = Hash.new
208
+
209
+ arguments.each do |k, v|
210
+ if k =~ /^fb_sig_(.*)/ then
211
+ signed[$1] = v
212
+ else
213
+ unsigned[k] = v
214
+ end
215
+ end
216
+
217
+ arg_string = String.new
218
+ signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
219
+ if Digest::MD5.hexdigest( arg_string + secret ) == signature
220
+ return true
221
+ end
222
+ return false
223
+ end
224
+
225
+ # Returns the login/add app url for your application.
226
+ #
227
+ # options:
228
+ # - :next => a relative next page to go to. relative to your facebook connect url or if :canvas is true, then relative to facebook app url
229
+ # - :canvas => true/false - to say whether this is a canvas app or not
230
+ def self.login_url(api_key, options={})
231
+ login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
232
+ login_url << "&next=#{defined?(Rack) ? Rack::Utils.escape(Rack::Utils.unescape(options[:next])) : CGI.escape(CGI.unescape(options[:next])) if options[:next]}" if options[:next]
233
+ login_url << "&canvas" if options[:canvas]
234
+ login_url
235
+ end
236
+
237
+ class FaceBookSecret
238
+ # Simple container that stores a secret value.
239
+ # Proc cannot be dumped or introspected by normal tools.
240
+ attr_reader :value
241
+
242
+ def initialize( value )
243
+ @value = Proc.new { value }
244
+ end
245
+ end
290
246
  end
data/test/test_mini_fb.rb CHANGED
@@ -1,29 +1,73 @@
1
+ require 'rubygems'
1
2
  require 'test/unit'
2
- class MiniFBTests < Test::Unit::TestCase
3
-
3
+ require 'contest'
4
+ require 'lib/mini_fb'
5
+ require 'cgi'
6
+ require 'rexml/document'
4
7
 
5
- def setup
6
- end
8
+ API_KEY = ''
9
+ SECRET_KEY = ''
10
+ SESSION_KEY = ''
11
+ PAGE_ID = '' # If you're not an Admin of this page, you'll need to change the test below so it will pass.
12
+ FB_USER_ID = ''
7
13
 
8
- def teardown
14
+ raise 'Missing API_KEY and/or SECRET_KEY' unless API_KEY && SECRET_KEY
9
15
 
16
+ class MiniFBTests < Test::Unit::TestCase
17
+
18
+ context 'A Facebook session' do
19
+ setup do
20
+ @facebook_session = MiniFB::Session.new(API_KEY, SECRET_KEY, SESSION_KEY, FB_USER_ID)
10
21
  end
11
-
12
- # Test signature verification.
13
- def test_signature
14
-
22
+
23
+ test 'users.getLoggedInUser should return successfully' do
24
+ result = @facebook_session.call 'users.getLoggedInUser'
25
+ assert result.is_a?(String)
26
+ assert result.to_i > 0
15
27
  end
16
-
17
- def test_basic_calls
18
-
28
+
29
+ test 'should call friends.get and return an array' do
30
+ results = @facebook_session.call 'Friends.get'
31
+ assert results.is_a? Array
32
+ assert results.length > 0
33
+ assert results[0].to_i > 0
19
34
  end
20
-
21
- def test_session
22
-
35
+
36
+ test 'should return a boolean for boolean call, and not get caught by invalid JSON returns from boolean calls on Facebook' do
37
+ result = @facebook_session.call 'Pages.isAdmin', 'page_id' => PAGE_ID
38
+ assert result == true
23
39
  end
24
-
25
- def test_photos
26
-
40
+
41
+ test 'should return raw JSON if JSON format is directly specified' do
42
+ results = @facebook_session.call 'Friends.get'
43
+ results_raw = @facebook_session.call 'Friends.get', 'format' => 'JSON'
44
+ assert results == JSON.parse(results_raw)
27
45
  end
28
-
29
- end
46
+
47
+ test 'should return XML if format is XML' do
48
+ results = @facebook_session.call 'Friends.get', 'format' => 'xml'
49
+ returned_at_least_one_response_element = false
50
+ returned_uids = true
51
+ REXML::Document.new(results).root.each_recursive do |element|
52
+ returned_at_least_one_response_element = true if element.name == 'Friends_get_response_elt'
53
+ returned_uids = false if element.text.to_s.to_i == 0 && element.name != 'Friends_get_response_elt'
54
+ end
55
+ assert returned_uids
56
+ assert returned_at_least_one_response_element
57
+ end
58
+
59
+ test 'should return the login url' do
60
+ login_url = MiniFB.login_url(API_KEY)
61
+ assert login_url == "http://api.facebook.com/login.php?api_key=#{API_KEY}"
62
+ end
63
+
64
+ test 'should return the login url with next url even if escaped' do
65
+ next_url = 'http://apps.facebook.com/farmexplosion'
66
+ login_url = MiniFB.login_url API_KEY, :next => next_url
67
+ login_url_escaped = MiniFB.login_url API_KEY, :next => CGI.escape(next_url)
68
+ assert login_url == "http://api.facebook.com/login.php?api_key=#{API_KEY}&next=#{CGI.escape next_url}"
69
+ assert login_url == "http://api.facebook.com/login.php?api_key=#{API_KEY}&next=#{CGI.escape next_url}"
70
+ end
71
+
72
+ end
73
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mjfreshyfresh-mini_fb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Travis Reeder
@@ -10,11 +10,11 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-01-29 00:00:00 -08:00
13
+ date: 2010-02-22 00:00:00 -08:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
17
- description: Tiny facebook library
17
+ description: Tiny facebook library for direct queries to the API
18
18
  email: michael@stepchangegroup.com
19
19
  executables: []
20
20