Class: FacebookRb::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/facebookrb.rb

Defined Under Namespace

Classes: APIProxy

Constant Summary collapse

FB_URL =
"http://api.facebook.com/restserver.php"
FB_API_VERSION =
"1.0"
USER_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]
USER_FIELDS_STANDARD =
[:uid, :first_name, :last_name, :name, :timezone, 
:birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Client

Returns a new instance of Client.



36
37
38
39
40
41
# File 'lib/facebookrb.rb', line 36

def initialize(options = {})
  self.api_key = options[:api_key] || options['api_key']
  self.secret = options[:secret] || options['secret']
  self.canvas_url = options[:canvas_url] || options['canvas_url']
  self.format = options[:format] || options['format'] || 'JSON'
end

Instance Attribute Details

#api_keyObject

Returns the value of attribute api_key.



33
34
35
# File 'lib/facebookrb.rb', line 33

def api_key
  @api_key
end

#batch_queueObject

Returns the value of attribute batch_queue.



34
35
36
# File 'lib/facebookrb.rb', line 34

def batch_queue
  @batch_queue
end

#canvas_urlObject

Returns the value of attribute canvas_url.



33
34
35
# File 'lib/facebookrb.rb', line 33

def canvas_url
  @canvas_url
end

#formatObject

Returns the value of attribute format.



33
34
35
# File 'lib/facebookrb.rb', line 33

def format
  @format
end

#last_responseObject

Returns the value of attribute last_response.



34
35
36
# File 'lib/facebookrb.rb', line 34

def last_response
  @last_response
end

#pending_batchObject

Returns the value of attribute pending_batch.



34
35
36
# File 'lib/facebookrb.rb', line 34

def pending_batch
  @pending_batch
end

#secretObject

Returns the value of attribute secret.



33
34
35
# File 'lib/facebookrb.rb', line 33

def secret
  @secret
end

Instance Method Details

#[](key) ⇒ Object



47
48
49
# File 'lib/facebookrb.rb', line 47

def [](key)
  params[key]
end

#add_special_params(method, params) ⇒ Object



150
151
152
153
154
# File 'lib/facebookrb.rb', line 150

def add_special_params(method, params)
  #call_as_apikey for Permissions API
  #ss Session secret
  #use_ssl_resources
end

#addurlObject



59
60
61
# File 'lib/facebookrb.rb', line 59

def addurl
  "http://apps.facebook.com/add.php?api_key=#{self.api_key}"
end

#batch(options = {}) {|_self| ... } ⇒ Object

Performs a batch API operation (batch.run)

Example:

results = fb.batch do 
  fb.application.getPublicInfo(...)
  fb.users.getInfo(...)
end

Options:

* :raise_errors (default true) - since a batch returns results for
  multiple calls, some may return errors while others do not, by default
  an exception will be raised if any errors are found.  Set to 
  false to disable this and handle errors yourself

Yields:

  • (_self)

Yield Parameters:

Raises:



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/facebookrb.rb', line 121

def batch(options = {})
  return unless block_given?
  #TODO: real error code/message
  raise FacebookError.new('error_code', 'error_msg') if pending_batch 

  self.batch_queue = []
  self.pending_batch = true
  options[:raise_errors] = true if options[:raise_errors].nil?

  yield self

  self.pending_batch = false
  results = call('batch.run', 
                 :method_feed => self.batch_queue, 
                 :serial_only => true)
  self.batch_queue = nil

  #Batch results are an array of JSON strings, parse each
  results.map! { |json| Yajl::Parser.parse(json) }

  results.each do |data|
    if data.is_a?(Hash) && data['error_msg']
      raise FacebookError.new(data['error_code'], data['error_msg'])
    end
  end if options[:raise_errors]

  results
end

#call(method, params = {}) ⇒ Object

Call facebook server with a method request. Most keyword arguments are passed directly to the server with a few exceptions.

Returns a parsed json object.

If an error occurs, a FacebookError exception will be raised with the proper code and message.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/facebookrb.rb', line 72

def call(method, params={})
  api_params = params.dup

  # Prepare standard arguments for call
  api_params['method']      ||= method
  api_params['api_key']     ||= self.api_key
  api_params['format']      ||= self.format
  api_params['session_key'] ||= self.params['session_key']
  api_params['call_id']     ||= Time.now.to_f.to_s
  api_params['v']           ||= FB_API_VERSION

  convert_outgoing_params(api_params)

  api_params['sig'] = generate_signature(api_params, self.secret)

  #If in a batch, stash the params in the queue and bail
  if pending_batch
    batch_queue << api_params.map { |k,v| "#{k}=#{v}" }.join('&')
    return
  end

  # Call Facebook with POST request
  response = Net::HTTP.post_form( URI.parse(FB_URL), api_params )

  # Handle response
  self.last_response = response.body
  data = Yajl::Parser.parse(response.body)

  if data.is_a?(Hash) && data['error_msg']
    raise FacebookError.new(data['error_code'], data['error_msg'])
  end

  return data
end

#convert_incoming_params(params) ⇒ Object

Converts some parameter values into more useful forms:

* 0 or 1 into true/false
* Time values into Time objects
* Comma separated lists into arrays


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/facebookrb.rb', line 201

def convert_incoming_params(params)
  return nil unless params

  params.each do |key, value|
    case key
    when 'friends', 'linked_account_ids'
      params[key] = value.split(',')
    when /(time|expires)$/
      if value == '0'
        params[key] = nil
      else
        params[key] = Time.at(value.to_f)
      end
    when /^(logged_out|position_|in_|is_)/, /added$/
      params[key] = (value == '1')
    else
      params[key] = value
    end
  end

  params
end

#convert_outgoing_params(params) ⇒ Object

Converts parameters being sent to Facebook from ruby objects to the appropriate text representation



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/facebookrb.rb', line 228

def convert_outgoing_params(params)
  json_encoder = Yajl::Encoder.new
  params.each do |key, value|
    params.delete(key) if value.nil?

    case value
    when Array, Hash
      params[key] = json_encoder.encode(value)
    when Time
      params[key] = value.to_i.to_s
    when TrueClass
      params[key] = '1'
    when FalseClass
      params[key] = '0'
    end
  end

  params
end

#extract_params(env) ⇒ Object

Extracts and validates any Facebook parameters from the request, and stores them in the client. We look for parameters from POST, GET, then cookies, in that order. POST and GET are always more up-to-date than cookies, so we prefer those if they are available.

Parameters:

env   the Rack environment, or a Rack request


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
# File 'lib/facebookrb.rb', line 166

def extract_params(env)
  #Accept a Request object or the env
  if env.is_a?(Rack::Request)
    request = env
  else
    request = Rack::Request.new(env)
  end

  #Fetch from POST
  fb_params = get_params_from(request.POST, 'fb_sig')

  #Fetch from GET
  unless fb_params
    # note that with preload FQL, it's possible to receive POST params in
    # addition to GET, and a different prefix is used for each
    fb_get_params = get_params_from(request.GET, 'fb_sig') || {}
    fb_post_params = get_params_from(request.POST, 'fb_post_sig') || {}
    fb_get_params.merge!(fb_post_params) 
    fb_params = fb_get_params unless fb_get_params.empty?
  end

  #Fetch from cookies
  unless fb_params
    fb_params = get_params_from(request.cookies, self.api_key)
  end
  
  @params = convert_incoming_params(fb_params)
end

#generate_signature(fb_params, secret) ⇒ Object

Generate a signature using the application secret key.

The only two entities that know your secret key are you and Facebook, according to the Terms of Service. Since nobody else can generate the signature, you can rely on it to verify that the information came from Facebook.

Parameters:

fb_params   an array of all Facebook-sent parameters, NOT INCLUDING 
            the signature itself
secret      your app's secret key

Returns:

a md5 hash to be checked against the signature provided by Facebook


297
298
299
300
# File 'lib/facebookrb.rb', line 297

def generate_signature(fb_params, secret)
  str = fb_params.map { |k,v| "#{k}=#{v}" }.sort.join 
  Digest::MD5.hexdigest("#{str}#{secret}")
end

#get_params_from(params, namespace = 'fb_sig') ⇒ Object

Get the signed parameters that were sent from Facebook. Validates the set of parameters against the included signature.

Since Facebook sends data to your callback URL via unsecured means, the signature is the only way to make sure that the data actually came from Facebook. So if an app receives a request at the callback URL, it should always verify the signature that comes with against your own secret key. Otherwise, it’s possible for someone to spoof a request by pretending to be someone else, i.e.:

www.your-callback-url.com/?fb_user=10101

Parameters:

params     A hash of parameters, i.e., from a HTTP request, or cookies
namespace  prefix string for the set of parameters we want to verify. 
           i.e., fb_sig or fb_post_sig

Returns:

the subset of parameters containing the given prefix, and also matching
the signature associated with them, or nil if the params do not validate


267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/facebookrb.rb', line 267

def get_params_from(params,  namespace='fb_sig')
  prefix = "#{namespace}_"
  fb_params = Hash.new
  params.each do |key, value|
    if key =~ /^#{prefix}(.*)$/
      fb_params[$1] = value
    end
  end
  
  param_sig = params[namespace]
  gen_sig = generate_signature(fb_params, self.secret)
  return fb_params if param_sig == gen_sig
  nil
end

#paramsObject



43
44
45
# File 'lib/facebookrb.rb', line 43

def params
  @params ||= {}
end

#url(path = nil) ⇒ Object



55
56
57
# File 'lib/facebookrb.rb', line 55

def url(path = nil)
  path ? "#{canvas_url}#{path}" : canvas_url
end

#valid?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/facebookrb.rb', line 51

def valid?
  !params.empty?
end