photo_mini_fb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.markdown +80 -0
  2. data/lib/mini_fb.rb +323 -0
  3. data/test/test_mini_fb.rb +29 -0
  4. metadata +57 -0
data/README.markdown ADDED
@@ -0,0 +1,80 @@
1
+ MiniFB - the simple miniature facebook library
2
+ ==============================================
3
+
4
+ MiniFB is a small, lightweight Ruby library for interacting with the [Facebook API](http://wiki.developers.facebook.com/index.php/API).
5
+
6
+ Installation
7
+ -------------
8
+
9
+ We're using gemcutter so be sure to have gemcutter as a source, then:
10
+
11
+ gem install mini_fb
12
+
13
+ General Usage
14
+ -------------
15
+
16
+ The most general case is to use MiniFB.call method:
17
+
18
+ user_hash = MiniFB.call(FB_API_KEY, FB_SECRET, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)
19
+
20
+ Which simply returns the parsed json response from Facebook.
21
+
22
+ Some Higher Level Objects for Common Uses
23
+ ----------------------
24
+
25
+ Get a MiniFB::Session:
26
+
27
+ @fb = MiniFB::Session.new(FB_API_KEY, FB_SECRET, @fb_session, @fb_uid)
28
+
29
+ Then it makes it a bit easier to use call for a particular user/session.
30
+
31
+ response = @fb.call("stream.get")
32
+
33
+ With the session, you can then get the user information for the session/uid.
34
+
35
+ user = @fb.user
36
+
37
+ Then get info from the user:
38
+
39
+ first_name = user["first_name"]
40
+
41
+ Or profile photos:
42
+
43
+ photos = user.profile_photos
44
+
45
+ Or if you want other photos, try:
46
+
47
+ photos = @fb.photos("pids"=>[12343243,920382343,9208348])
48
+
49
+ Facebook Connect
50
+ ----------------
51
+
52
+ This is actually very easy, first follow these instructions: http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site
53
+
54
+ Then add the following script to the page where you put the login button so it looks like this:
55
+
56
+ <script>
57
+ function facebook_onlogin(){
58
+ document.location.href = "<%= url_for :action=>"fb_connect" %>";
59
+ }
60
+ </script>
61
+ <fb:login-button onlogin="facebook_onlogin();"></fb:login-button>
62
+
63
+ Define an fb_connect method in your login/sessions controller like so:
64
+
65
+ def fb_connect
66
+ @fb_uid = cookies[FB_API_KEY + "_user"]
67
+ @fb_session = cookies[FB_API_KEY + "_session_key"]
68
+ puts "uid=#{@fb_uid}"
69
+ puts "session=#{@fb_session}"
70
+
71
+ # And here you would create the user if it doesn't already exist, then redirect them to wherever you want.
72
+
73
+ end
74
+
75
+
76
+ Support
77
+ --------
78
+
79
+ Join our Discussion Group at: http://groups.google.com/group/mini_fb
80
+
data/lib/mini_fb.rb ADDED
@@ -0,0 +1,323 @@
1
+ require 'digest/md5'
2
+ require 'erb'
3
+ require 'json' unless defined? JSON
4
+ require 'socket'
5
+ require 'uri'
6
+ require 'net/http'
7
+
8
+ module MiniFB
9
+
10
+ # Global constants
11
+ FB_URL = "http://api.facebook.com/restserver.php"
12
+ FB_API_VERSION = "1.0"
13
+
14
+ @@logging = false
15
+
16
+ def self.enable_logging
17
+ @@logging = true
18
+ end
19
+
20
+ def self.disable_logging
21
+ @@logging = false
22
+ end
23
+
24
+ class FaceBookError < StandardError
25
+ attr_accessor :code
26
+ # Error that happens during a facebook call.
27
+ def initialize( error_code, error_msg )
28
+ @code = error_code
29
+ super("Facebook error #{error_code}: #{error_msg}" )
30
+ end
31
+ end
32
+
33
+ class Session
34
+ attr_accessor :api_key, :secret_key, :session_key, :uid
35
+
36
+
37
+ def initialize(api_key, secret_key, session_key, uid)
38
+ @api_key = api_key
39
+ @secret_key = FaceBookSecret.new secret_key
40
+ @session_key = session_key
41
+ @uid = uid
42
+ end
43
+
44
+ # returns current user
45
+ def user
46
+ return @user unless @user.nil?
47
+ @user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
48
+ @user
49
+ end
50
+
51
+ def photos
52
+ Photos.new(self)
53
+ end
54
+
55
+
56
+ def call(method, params={})
57
+ return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
58
+ end
59
+
60
+
61
+ end
62
+ class User
63
+ 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, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
64
+ STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email, :email]
65
+
66
+ def self.all_fields
67
+ FIELDS.join(",")
68
+ end
69
+
70
+ def self.standard_fields
71
+ STANDARD_FIELDS.join(",")
72
+ end
73
+
74
+ def initialize(fb_hash, session)
75
+ @fb_hash = fb_hash
76
+ @session = session
77
+ end
78
+
79
+ def [](key)
80
+ @fb_hash[key]
81
+ end
82
+
83
+ def uid
84
+ return self["uid"]
85
+ end
86
+
87
+ def profile_photos
88
+ @session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
89
+ end
90
+
91
+ def profile_pic_album_id
92
+ merge_aid(-3, uid)
93
+ end
94
+
95
+ def merge_aid(aid, uid)
96
+ uid = uid.to_i
97
+ ret = (uid << 32) + (aid & 0xFFFFFFFF)
98
+ # puts 'merge_aid=' + ret.inspect
99
+ return ret
100
+ end
101
+ end
102
+
103
+ class Photos
104
+
105
+ def initialize(session)
106
+ @session = session
107
+ end
108
+
109
+ def get(params)
110
+ pids = params["pids"]
111
+ if !pids.nil? && pids.is_a?(Array)
112
+ pids = pids.join(",")
113
+ params["pids"] = pids
114
+ end
115
+ @session.call("photos.get", params)
116
+ end
117
+ end
118
+
119
+ BAD_JSON_METHODS = ["users.getloggedinuser", "auth.promotesession", "users.hasapppermission", "Auth.revokeExtendedPermission", "pages.isAdmin"].collect { |x| x.downcase }
120
+
121
+ # Call facebook server with a method request. Most keyword arguments
122
+ # are passed directly to the server with a few exceptions.
123
+ # The 'sig' value will always be computed automatically.
124
+ # The 'v' version will be supplied automatically if needed.
125
+ # The 'call_id' defaults to True, which will generate a valid
126
+ # number. Otherwise it should be a valid number or False to disable.
127
+
128
+ # The default return is a parsed json object.
129
+ # Unless the 'format' and/or 'callback' arguments are given,
130
+ # in which case the raw text of the reply is returned. The string
131
+ # will always be returned, even during errors.
132
+
133
+ # If an error occurs, a FacebookError exception will be raised
134
+ # with the proper code and message.
135
+
136
+ # The secret argument should be an instance of FacebookSecret
137
+ # to hide value from simple introspection.
138
+ def MiniFB.call( api_key, secret, method, kwargs )
139
+
140
+ puts 'kwargs=' + kwargs.inspect if @@logging
141
+
142
+ if secret.is_a? String
143
+ secret = FaceBookSecret.new(secret)
144
+ end
145
+
146
+ # Prepare arguments for call
147
+ call_id = kwargs.fetch("call_id", true)
148
+ if call_id == true then
149
+ kwargs["call_id"] = Time.now.tv_sec.to_s
150
+ else
151
+ kwargs.delete("call_id")
152
+ end
153
+
154
+ if method == "photos.upload"
155
+ filename = kwargs.fetch("filename")
156
+ kwargs.delete("filename")
157
+ end
158
+
159
+ custom_format = kwargs.include?("format") or kwargs.include?("callback")
160
+ kwargs["format"] ||= "JSON"
161
+ kwargs["v"] ||= FB_API_VERSION
162
+ kwargs["api_key"]||= api_key
163
+ kwargs["method"] ||= method
164
+
165
+ # Hash with secret
166
+ arg_string = String.new
167
+ # todo: convert symbols to strings, symbols break the next line
168
+ kwargs.sort.each { |kv| arg_string << kv[0] << "=" << kv[1].to_s }
169
+ kwargs["sig"] = Digest::MD5.hexdigest( arg_string + secret.value.call )
170
+
171
+ # Call website with POST request
172
+ begin
173
+ if method == "photos.upload"
174
+ response = MiniFB.post_upload(filename, kwargs)
175
+ else
176
+ response = Net::HTTP.post_form( URI.parse(FB_URL), kwargs )
177
+ end
178
+ rescue SocketError => err
179
+ raise IOError.new( "Cannot connect to the facebook server: " + err )
180
+ end
181
+
182
+ # Handle response
183
+ return response.body if custom_format
184
+
185
+ fb_method = kwargs["method"].downcase
186
+ body = response.body
187
+
188
+ puts 'response=' + body.inspect if @@logging
189
+ begin
190
+ data = JSON.parse( body )
191
+ if data.include?( "error_msg" ) then
192
+ raise FaceBookError.new( data["error_code"] || 1, data["error_msg"] )
193
+ end
194
+
195
+ rescue JSON::ParserError => ex
196
+ if BAD_JSON_METHODS.include?(fb_method) # Little hack because this response isn't valid JSON
197
+ if body == "0" || body == "false"
198
+ return false
199
+ end
200
+ return body
201
+ else
202
+ raise ex
203
+ end
204
+ end
205
+ return data
206
+ end
207
+
208
+ def MiniFB.post_upload(filename, kwargs)
209
+ content = File.open(filename, 'rb') { |f| f.read }
210
+ boundary = Digest::MD5.hexdigest(content)
211
+ header = {'Content-type' => "multipart/form-data, boundary=#{boundary}"}
212
+
213
+ # Build query
214
+ query = ''
215
+ kwargs.each { |a, v|
216
+ query <<
217
+ "--#{boundary}\r\n" <<
218
+ "Content-Disposition: form-data; name=\"#{a}\"\r\n\r\n" <<
219
+ "#{v}\r\n"
220
+ }
221
+ query <<
222
+ "--#{boundary}\r\n" <<
223
+ "Content-Disposition: form-data; filename=\"#{File.basename(filename)}\"\r\n" <<
224
+ "Content-Transfer-Encoding: binary\r\n" <<
225
+ "Content-Type: image/jpeg\r\n\r\n" <<
226
+ content <<
227
+ "\r\n" <<
228
+ "--#{boundary}--"
229
+
230
+ # Call Facebook with POST multipart/form-data request
231
+ uri = URI.parse(FB_URL)
232
+ Net::HTTP.start(uri.host) {|http| http.post uri.path, query, header}
233
+ end
234
+
235
+ # Returns true is signature is valid, false otherwise.
236
+ def MiniFB.verify_signature( secret, arguments )
237
+ signature = arguments.delete( "fb_sig" )
238
+ return false if signature.nil?
239
+
240
+ unsigned = Hash.new
241
+ signed = Hash.new
242
+
243
+ arguments.each do |k, v|
244
+ if k =~ /^fb_sig_(.*)/ then
245
+ signed[$1] = v
246
+ else
247
+ unsigned[k] = v
248
+ end
249
+ end
250
+
251
+ arg_string = String.new
252
+ signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
253
+ if Digest::MD5.hexdigest( arg_string + secret ) == signature
254
+ return true
255
+ end
256
+ return false
257
+ end
258
+
259
+ # Returns the login/add app url for your application.
260
+ #
261
+ # options:
262
+ # - :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
263
+ # - :canvas => true/false - to say whether this is a canvas app or not
264
+ def self.login_url(api_key, options={})
265
+ login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
266
+ login_url << "&next=#{options[:next]}" if options[:next]
267
+ login_url << "&canvas" if options[:canvas]
268
+ login_url
269
+ end
270
+
271
+ # This function expects arguments as a hash, so
272
+ # it is agnostic to different POST handling variants in ruby.
273
+ #
274
+ # Validate the arguments received from facebook. This is usually
275
+ # sent for the iframe in Facebook's canvas. It is not necessary
276
+ # to use this on the auth_token and uid passed to callbacks like
277
+ # post-add and post-remove.
278
+ #
279
+ # The arguments must be a mapping of to string keys and values
280
+ # or a string of http request data.
281
+ #
282
+ # If the data is invalid or not signed properly, an empty
283
+ # dictionary is returned.
284
+ #
285
+ # The secret argument should be an instance of FacebookSecret
286
+ # to hide value from simple introspection.
287
+ #
288
+ # DEPRECATED, use verify_signature instead
289
+ def MiniFB.validate( secret, arguments )
290
+
291
+ signature = arguments.delete( "fb_sig" )
292
+ return arguments if signature.nil?
293
+
294
+ unsigned = Hash.new
295
+ signed = Hash.new
296
+
297
+ arguments.each do |k, v|
298
+ if k =~ /^fb_sig_(.*)/ then
299
+ signed[$1] = v
300
+ else
301
+ unsigned[k] = v
302
+ end
303
+ end
304
+
305
+ arg_string = String.new
306
+ signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
307
+ if Digest::MD5.hexdigest( arg_string + secret ) != signature
308
+ unsigned # Hash is incorrect, return only unsigned fields.
309
+ else
310
+ unsigned.merge signed
311
+ end
312
+ end
313
+
314
+ class FaceBookSecret
315
+ # Simple container that stores a secret value.
316
+ # Proc cannot be dumped or introspected by normal tools.
317
+ attr_reader :value
318
+
319
+ def initialize( value )
320
+ @value = Proc.new { value }
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,29 @@
1
+ require 'test/unit'
2
+ class MiniFBTests < Test::Unit::TestCase
3
+
4
+
5
+ def setup
6
+ end
7
+
8
+ def teardown
9
+
10
+ end
11
+
12
+ # Test signature verification.
13
+ def test_signature
14
+
15
+ end
16
+
17
+ def test_basic_calls
18
+
19
+ end
20
+
21
+ def test_session
22
+
23
+ end
24
+
25
+ def test_photos
26
+
27
+ end
28
+
29
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: photo_mini_fb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Travis Reeder
8
+ - Aaron Hurley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-03-02 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Tiny facebook library
18
+ email: travis@appoxy.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.markdown
25
+ files:
26
+ - lib/mini_fb.rb
27
+ - README.markdown
28
+ has_rdoc: true
29
+ homepage: http://github.com/appoxy/mini_fb
30
+ licenses: []
31
+
32
+ post_install_message:
33
+ rdoc_options:
34
+ - --charset=UTF-8
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project:
52
+ rubygems_version: 1.3.5
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: Tiny facebook library
56
+ test_files:
57
+ - test/test_mini_fb.rb