Module: MiniFB

Defined in:
lib/mini_fb.rb

Defined Under Namespace

Classes: FaceBookError, FaceBookSecret, GraphObject, OAuthSession, Photos, Session, User

Constant Summary collapse

FB_URL =

Global constants

"http://api.facebook.com/restserver.php"
FB_API_VERSION =
"1.0"
BAD_JSON_METHODS =
["users.getloggedinuser", "auth.promotesession", "users.hasapppermission",
                    "Auth.revokeExtendedPermission", "pages.isAdmin", "pages.isFan",
                    "stream.publish",
                    "dashboard.addNews", "dashboard.addGlobalNews", "dashboard.publishActivity",
                    "dashboard.incrementcount", "dashboard.setcount"
].collect { |x| x.downcase }
@@logging =

Define here the values for your application FB_URL = “api.facebook.com/restserver.php” FB_API_VERSION = “1.0”

false

Class Method Summary collapse

Class Method Details

.call(api_key, secret, method, kwargs) ⇒ Object

The secret argument should be an instance of FacebookSecret to hide value from simple introspection.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/mini_fb.rb', line 158

def MiniFB.call(api_key, secret, method, kwargs)

    puts 'kwargs=' + kwargs.inspect if @@logging

    if secret.is_a? String
        secret = FaceBookSecret.new(secret)
    end

    # Prepare arguments for call
    call_id = kwargs.fetch("call_id", true)
    if call_id == true
        kwargs["call_id"] = Time.now.tv_sec.to_s
    else
        kwargs.delete("call_id")
    end

    custom_format = kwargs.include?("format") || kwargs.include?("callback")
    kwargs["format"] ||= "JSON"
    kwargs["v"] ||= FB_API_VERSION
    kwargs["api_key"]||= api_key
    kwargs["method"] ||= method

    file_name = kwargs.delete("filename")

    kwargs["sig"] = signature_for(kwargs, secret.value.call)

    fb_method = kwargs["method"].downcase
    if fb_method == "photos.upload"
        # Then we need a multipart post
        response = MiniFB.post_upload(file_name, kwargs)
    else

        begin
            response = Net::HTTP.post_form(URI.parse(FB_URL), post_params(kwargs))
        rescue SocketError => err
            # why are we catching this and throwing as different error?  hmmm..
            # raise IOError.new( "Cannot connect to the facebook server: " + err )
            raise err
        end
    end

    # Handle response
    return response.body if custom_format

    body = response.body

    puts 'response=' + body.inspect if @@logging
    begin
        data = JSON.parse(body)
        if data.include?("error_msg")
            raise FaceBookError.new(data["error_code"] || 1, data["error_msg"])
        end

    rescue JSON::ParserError => ex
        if BAD_JSON_METHODS.include?(fb_method) # Little hack because this response isn't valid JSON
            if body == "0" || body == "false"
                return false
            end
            return body
        else
            raise ex
        end
    end
    return data
end

.disable_loggingObject



35
36
37
# File 'lib/mini_fb.rb', line 35

def self.disable_logging
    @@logging = false
end

.enable_loggingObject



31
32
33
# File 'lib/mini_fb.rb', line 31

def self.enable_logging
    @@logging = true
end

.fetch(url, options = {}) ⇒ Object



534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/mini_fb.rb', line 534

def self.fetch(url, options={})

    begin
        if options[:method] == :post
            puts 'url_post=' + url if @@logging
            resp = RestClient.post url, options[:params]
        else
            if options[:params] && options[:params].size > 0
                url += '?' + options[:params].map { |k, v| URI.escape("%s=%s" % [k, v]) }.join('&')
            end
            puts 'url_get=' + url if @@logging
            resp = RestClient.get url
        end

        puts 'resp=' + resp.to_s if @@logging

        begin
            res_hash = JSON.parse(resp.to_s)
        rescue
            # quick fix for things like stream.publish that don't return json
            res_hash = JSON.parse("{\"response\": #{resp.to_s}}")
        end

        if res_hash.is_a? Array # fql  return this
            res_hash.collect! { |x| Hashie::Mash.new(x) }
        else
            res_hash = Hashie::Mash.new(res_hash)
        end

        if res_hash.include?("error_msg")
            raise FaceBookError.new(res_hash["error_code"] || 1, res_hash["error_msg"])
        end

        return res_hash
    rescue RestClient::Exception => ex
        puts ex.http_code.to_s
        puts 'ex.http_body=' + ex.http_body if @@logging
        res_hash = JSON.parse(ex.http_body) # probably should ensure it has a good response
        raise MiniFB::FaceBookError.new(ex.http_code, "#{res_hash["error"]["type"]}: #{res_hash["error"]["message"]}")
    end

end

.fql(access_token, fql_query, options = {}) ⇒ Object

Executes an FQL query



494
495
496
497
498
499
500
501
502
503
# File 'lib/mini_fb.rb', line 494

def self.fql(access_token, fql_query, options={})
    url = "https://api.facebook.com/method/fql.query"
    params = options[:params] || {}
    params["access_token"] = "#{(access_token)}"
    params["metadata"] = "1" if options[:metadata]
    params["query"] = fql_query
    params["format"] = "JSON"
    options[:params] = params
    return fetch(url, options)
end

.get(access_token, id, options = {}) ⇒ Object

Gets data from the Facebook Graph API options:

- type: eg: feed, home, etc
- metadata: to include metadata in response. true/false
- params: Any additional parameters you would like to submit


458
459
460
461
462
463
464
465
466
# File 'lib/mini_fb.rb', line 458

def self.get(access_token, id, options={})
    url = "#{graph_base}#{id}"
    url << "/#{options[:type]}" if options[:type]
    params = options[:params] || {}
    params["access_token"] = "#{(access_token)}"
    params["metadata"] = "1" if options[:metadata]
    options[:params] = params
    return fetch(url, options)
end

.graph_baseObject



420
421
422
# File 'lib/mini_fb.rb', line 420

def self.graph_base
    "https://graph.facebook.com/"
end

.login_url(api_key, options = {}) ⇒ Object

Returns the login/add app url for your application.

options:

- :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
- :canvas => true/false - to say whether this is a canvas app or not


312
313
314
315
316
317
# File 'lib/mini_fb.rb', line 312

def self.(api_key, options={})
     = "http://api.facebook.com/login.php?api_key=#{api_key}"
     << "&next=#{options[:next]}" if options[:next]
     << "&canvas" if options[:canvas]
    
end

.multifql(access_token, fql_queries, options = {}) ⇒ Object

Executes multiple FQL queries Example:

MiniFB.multifql(access_token, { :statuses => “SELECT status_id, message FROM status WHERE uid = 12345”,

:privacy => "SELECT object_id, description FROM privacy WHERE object_id IN (SELECT status_id FROM #statuses)" })


510
511
512
513
514
515
516
517
518
519
# File 'lib/mini_fb.rb', line 510

def self.multifql(access_token, fql_queries, options={})
    url = "https://api.facebook.com/method/fql.multiquery"
    params = options[:params] || {}
    params["access_token"] = "#{(access_token)}"
    params["metadata"] = "1" if options[:metadata]
    params["queries"] = JSON[fql_queries]
    params[:format] = "JSON"
    options[:params] = params
    return fetch(url, options)
end

.oauth_access_token(app_id, redirect_uri, secret, code) ⇒ Object

returns a hash with one value being ‘access_token’, the other being ‘expires’



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/mini_fb.rb', line 436

def self.oauth_access_token(app_id, redirect_uri, secret, code)
    oauth_url = "#{graph_base}oauth/access_token"
    oauth_url << "?client_id=#{app_id}"
    oauth_url << "&redirect_uri=#{URI.escape(redirect_uri)}"
    oauth_url << "&client_secret=#{secret}"
    oauth_url << "&code=#{URI.escape(code)}"
    resp = RestClient.get oauth_url
    puts 'resp=' + resp.body.to_s if @@logging
    params = {}
    params_array = resp.split("&")
    params_array.each do |p|
        ps = p.split("=")
        params[ps[0]] = ps[1]
    end
    return params
end

.oauth_url(app_id, redirect_uri, options = {}) ⇒ Object

options:

- scope: comma separated list of extends permissions. see http://developers.facebook.com/docs/authentication/permissions


426
427
428
429
430
431
432
433
# File 'lib/mini_fb.rb', line 426

def self.oauth_url(app_id, redirect_uri, options={})
    oauth_url = "#{graph_base}oauth/authorize"
    oauth_url << "?client_id=#{app_id}"
    oauth_url << "&redirect_uri=#{URI.escape(redirect_uri)}"
#        oauth_url << "&scope=#{options[:scope]}" if options[:scope]
    oauth_url << ("&" + options.map { |k, v| "%s=%s" % [k, v] }.join('&')) unless options.empty?
    oauth_url
end

Parses cookies in order to extract the facebook cookie and parse it into a useable hash

options:

  • app_id - the connect applications app_id (some users may find they have to use their facebook API key)

  • secret - the connect application secret

  • cookies - the cookies given by facebook - it is ok to just pass all of the cookies, the method will do the filtering for you.



281
282
283
284
# File 'lib/mini_fb.rb', line 281

def MiniFB.parse_cookie_information(app_id, cookies)
    return nil if cookies["fbs_#{app_id}"].nil?
    Hash[*cookies["fbs_#{app_id}"].split('&').map { |v| v.gsub('"', '').split('=', 2) }.flatten]
end

.post(access_token, id, options = {}) ⇒ Object

Posts data to the Facebook Graph API options:

- type: eg: feed, home, etc
- metadata: to include metadata in response. true/false
- params: Any additional parameters you would like to submit


473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/mini_fb.rb', line 473

def self.post(access_token, id, options={})
    url = "#{graph_base}#{id}"
    url << "/#{options[:type]}" if options[:type]
    options.delete(:type)
    params = options[:params] || {}
    options.each do |key, value|
        if value.kind_of?(File)
            params[key] = value
        else
            params[key] = "#{value}"
        end
    end
    params["access_token"] = "#{(access_token)}"
    params["metadata"] = "1" if options[:metadata]
    options[:params] = params
    options[:method] = :post
    return fetch(url, options)

end

.post_upload(filename, kwargs) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/mini_fb.rb', line 224

def MiniFB.post_upload(filename, kwargs)
    content = File.open(filename, 'rb') { |f| f.read }
    boundary = Digest::MD5.hexdigest(content)
    header = {'Content-type' => "multipart/form-data, boundary=#{boundary}"}

    # Build query
    query = ''
    kwargs.each { |a, v|
        query <<
                "--#{boundary}\r\n" <<
                "Content-Disposition: form-data; name=\"#{a}\"\r\n\r\n" <<
                "#{v}\r\n"
    }
    query <<
            "--#{boundary}\r\n" <<
            "Content-Disposition: form-data; filename=\"#{File.basename(filename)}\"\r\n" <<
            "Content-Transfer-Encoding: binary\r\n" <<
            "Content-Type: image/jpeg\r\n\r\n" <<
            content <<
            "\r\n" <<
            "--#{boundary}--"

    # Call Facebook with POST multipart/form-data request
    uri = URI.parse(FB_URL)
    Net::HTTP.start(uri.host) { |http| http.post uri.path, query, header }
end

.rest(access_token, api_method, options = {}) ⇒ Object

Uses new Oauth 2 authentication against old Facebook REST API options:

- params: Any additional parameters you would like to submit


524
525
526
527
528
529
530
531
# File 'lib/mini_fb.rb', line 524

def self.rest(access_token, api_method, options={})
    url = "https://api.facebook.com/method/#{api_method}"
    params = options[:params] || {}
    params[:access_token] = access_token
    params[:format] = "JSON"
    options[:params] = params
    return fetch(url, options)
end

.scopesObject

Returns all available scopes.



578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/mini_fb.rb', line 578

def self.scopes
    scopes = %w{
        about_me activities birthday education_history events groups
        hometown interests likes location notes online_presence
        photo_video_tags photos relationships religion_politics
        status videos website work_history
    }
    scopes.map! do |scope|
        ["user_#{scope}", "friends_#{scope}"]
    end.flatten!

    scopes += %w{
      read_insights read_stream read_mailbox read_friendlists read_requests
      email ads_management xmpp_login
      publish_stream create_event rsvp_event sms offline_access
    }
end

.validate(secret, arguments) ⇒ Object

DEPRECATED, use verify_signature instead



614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
# File 'lib/mini_fb.rb', line 614

def MiniFB.validate(secret, arguments)

    signature = arguments.delete("fb_sig")
    return arguments if signature.nil?

    unsigned = Hash.new
    signed = Hash.new

    arguments.each do |k, v|
        if k =~ /^fb_sig_(.*)/ then
            signed[$1] = v
        else
            unsigned[k] = v
        end
    end

    arg_string = String.new
    signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
    if Digest::MD5.hexdigest(arg_string + secret) != signature
        unsigned # Hash is incorrect, return only unsigned fields.
    else
        unsigned.merge signed
    end
end

.verify_connect_signature(api_key, secret, cookies) ⇒ Object

DEPRECATED: Please use verify_cookie_signature instead.



302
303
304
305
# File 'lib/mini_fb.rb', line 302

def MiniFB.verify_connect_signature(api_key, secret, cookies)
    warn "DEPRECATION WARNING: 'verify_connect_signature' has been renamed to 'verify_cookie_signature' as Facebook no longer calls this 'connect'"
    MiniFB.verify_cookie_signature(api_key, secret, cookies)
end

Validates that the cookies sent by the user are those that were set by facebook. Since your secret is only known by you and facebook it is used to sign all of the cookies set.

options:

  • app_id - the connect applications app_id (some users may find they have to use their facebook API key)

  • secret - the connect application secret

  • cookies - the cookies given by facebook - it is ok to just pass all of the cookies, the method will do the filtering for you.



293
294
295
296
297
298
299
# File 'lib/mini_fb.rb', line 293

def MiniFB.verify_cookie_signature(app_id, secret, cookies)
    fb_keys = MiniFB.parse_cookie_information(app_id, cookies)
    return false if fb_keys.nil?

    signature = fb_keys.delete('sig')
    return signature == Digest::MD5.hexdigest(fb_keys.map { |k, v| "#{k}=#{v}" }.sort.join + secret)
end

.verify_signature(secret, arguments) ⇒ Object

Returns true is signature is valid, false otherwise.



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/mini_fb.rb', line 252

def MiniFB.verify_signature(secret, arguments)
    signature = arguments.delete("fb_sig")
    return false if signature.nil?

    unsigned = Hash.new
    signed = Hash.new

    arguments.each do |k, v|
        if k =~ /^fb_sig_(.*)/ then
            signed[$1] = v
        else
            unsigned[k] = v
        end
    end

    arg_string = String.new
    signed.sort.each { |kv| arg_string << kv[0] << "=" << kv[1] }
    if Digest::MD5.hexdigest(arg_string + secret) == signature
        return true
    end
    return false
end