photo_mini_fb 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.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