Skip to content

Instantly share code, notes, and snippets.

@joffilyfe
Created October 18, 2016 02:48
Show Gist options
  • Save joffilyfe/f9bb30e03fb96c747f409d63caec8c22 to your computer and use it in GitHub Desktop.
Save joffilyfe/f9bb30e03fb96c747f409d63caec8c22 to your computer and use it in GitHub Desktop.
#MiniFB - the simple miniature facebook library
#MiniFB is a small, lightweight Ruby library for interacting with the Facebook API.
#
#Brought to you by: www.appoxy.com
#
#Support
#
#Join our Discussion Group at: http://groups.google.com/group/mini_fb
#
#Demo Rails Application
#
#There is a demo Rails app that uses mini_fb graph api at: http://github.com/appoxy/mini_fb_demo
require 'digest/md5'
require 'erb'
require 'json' unless defined? JSON
require 'httpclient'
require 'hashie'
require 'base64'
require 'openssl'
require 'logger'
module MiniFB
# Global constants
FB_URL = "http://api.facebook.com/restserver.php"
FB_API_VERSION = "1.0"
@@logging = false
@@log = Logger.new(STDOUT)
@@http = HTTPClient.new
def self.log_level=(level)
if level.is_a? Numeric
@@log.level = level
else
@@log.level = case level
when :fatal
@@log.level = Logger::FATAL
when :error
@@log.level = Logger::ERROR
when :warn
@@log.level = Logger::WARN
when :info
@@log.level = Logger::INFO
when :debug
@@log.level = Logger::DEBUG
end
end
end
def self.enable_logging
@@logging = true
@@log.level = Logger::DEBUG
end
def self.disable_logging
@@logging = false
@@log.level = Logger::ERROR
end
class FaceBookError < StandardError
attr_accessor :code
# Error that happens during a facebook call.
def initialize(error_code, error_msg)
@code = error_code
super("Facebook error #{error_code}: #{error_msg}")
end
end
class Session
attr_accessor :api_key, :secret_key, :session_key, :uid
def initialize(api_key, secret_key, session_key, uid)
@api_key = api_key
@secret_key = FaceBookSecret.new secret_key
@session_key = session_key
@uid = uid
end
# returns current user
def user
return @user unless @user.nil?
@user = User.new(MiniFB.call(@api_key, @secret_key, "Users.getInfo", "session_key"=>@session_key, "uids"=>@uid, "fields"=>User.all_fields)[0], self)
@user
end
def photos
Photos.new(self)
end
def call(method, params={})
return MiniFB.call(api_key, secret_key, method, params.update("session_key"=>session_key))
end
end
class 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, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo]
STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email, :email]
def self.all_fields
FIELDS.join(",")
end
def self.standard_fields
STANDARD_FIELDS.join(",")
end
def initialize(fb_hash, session)
@fb_hash = fb_hash
@session = session
end
def [](key)
@fb_hash[key]
end
def uid
return self["uid"]
end
def profile_photos
@session.photos.get("uid"=>uid, "aid"=>profile_pic_album_id)
end
def profile_pic_album_id
merge_aid(-3, uid)
end
def merge_aid(aid, uid)
uid = uid.to_i
ret = (uid << 32) + (aid & 0xFFFFFFFF)
# puts 'merge_aid=' + ret.inspect
return ret
end
end
class Photos
def initialize(session)
@session = session
end
def get(params)
pids = params["pids"]
if !pids.nil? && pids.is_a?(Array)
pids = pids.join(",")
params["pids"] = pids
end
@session.call("photos.get", params)
end
end
BAD_JSON_METHODS = ["users.getloggedinuser", "auth.promotesession", "users.hasapppermission",
"Auth.revokeExtendedPermission", "auth.revokeAuthorization",
"pages.isAdmin", "pages.isFan",
"stream.publish",
"dashboard.addNews", "dashboard.addGlobalNews", "dashboard.publishActivity",
"dashboard.incrementcount", "dashboard.setcount"
].collect { |x| x.downcase }
# THIS IS FOR THE OLD FACEBOOK API, NOT THE GRAPH ONE. See MiniFB.get and MiniFB.post for Graph API
#
# Call facebook server with a method request. Most keyword arguments
# are passed directly to the server with a few exceptions.
# The 'sig' value will always be computed automatically.
# The 'v' version will be supplied automatically if needed.
# The 'call_id' defaults to True, which will generate a valid
# number. Otherwise it should be a valid number or False to disable.
# The default return is a parsed json object.
# Unless the 'format' and/or 'callback' arguments are given,
# in which case the raw text of the reply is returned. The string
# will always be returned, even during errors.
# If an error occurs, a FacebookError exception will be raised
# with the proper code and message.
# The secret argument should be an instance of FacebookSecret
# to hide value from simple introspection.
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
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
# Returns true is signature is valid, false otherwise.
def MiniFB.verify_signature(secret, arguments)
if arguments.is_a? String
#new way: params[:session]
session = JSON.parse(arguments)
signature = session.delete('sig')
return false if signature.nil?
arg_string = String.new
session.sort.each { |k, v| arg_string << "#{k}=#{v}" }
if Digest::MD5.hexdigest(arg_string + secret) == signature
return true
end
else
#old way
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
end
return false
end
# This function takes the app secret and the signed request, and verifies if the request is valid.
def self.verify_signed_request(secret, req)
s, p = req.split(".")
sig = base64_url_decode(s)
expected_sig = OpenSSL::HMAC.digest('SHA256', secret, p)
return sig == expected_sig
end
# This function decodes the data sent by Facebook and returns a Hash.
# See: http://developers.facebook.com/docs/authentication/canvas
def self.signed_request_params(secret, req)
s, p = req.split(".")
p = base64_url_decode(p)
h = JSON.parse(p)
h.delete('algorithm') if h['algorithm'] == 'HMAC-SHA256'
h
end
# Ruby's implementation of base64 decoding seems to be reading the string in multiples of 4 and ignoring
# any extra characters if there are no white-space characters at the end. Since facebook does not take this
# into account, this function fills any string with white spaces up to the point where it becomes divisible
# by 4, then it replaces '-' with '+' and '_' with '/' (URL-safe decoding), and decodes the result.
def self.base64_url_decode(str)
str = str + "=" * (4 - str.size % 4) unless str.size % 4 == 0
return Base64.decode64(str.tr("-_", "+/"))
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.
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
# 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.
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
# <b>DEPRECATED:</b> Please use <tt>verify_cookie_signature</tt> instead.
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
# 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
def self.login_url(api_key, options={})
login_url = "http://api.facebook.com/login.php?api_key=#{api_key}"
login_url << "&next=#{options[:next]}" if options[:next]
login_url << "&canvas" if options[:canvas]
login_url
end
# Manages access_token and locale params for an OAuth connection
class OAuthSession
def initialize(access_token, locale="en_US")
@access_token = access_token
@locale = locale
end
def get(id, options={})
MiniFB.get(@access_token, id, session_options(options))
end
def post(id, options={})
MiniFB.post(@access_token, id, session_options(options))
end
def fql(fql_query, options={})
MiniFB.fql(@access_token, fql_query, session_options(options))
end
def multifql(fql_queries, options={})
MiniFB.multifql(@access_token, fql_queries, session_options(options))
end
def rest(api_method, options={})
MiniFB.rest(@access_token, api_method, session_options(options))
end
# Returns a GraphObject for the given id
def graph_object(id)
MiniFB::GraphObject.new(self, id)
end
# Returns and caches a GraphObject for the user
def me
@me ||= graph_object('me')
end
private
def session_options(options)
(options[:params] ||= {})[:locale] ||= @locale
options
end
end
# Wraps a graph object for easily accessing its connections
class GraphObject
# Creates a GraphObject using an OAuthSession or access_token
def initialize(session_or_token, id)
@oauth_session = if session_or_token.is_a?(MiniFB::OAuthSession)
session_or_token
else
MiniFB::OAuthSession.new(session_or_token)
end
@id = id
@object = @oauth_session.get(id, :metadata => true)
@connections_cache = {}
end
def inspect
"<##{self.class.name} #{@object.inspect}>"
end
def connections
@object.metadata.connections.keys
end
unless RUBY_VERSION >= '1.9'
undef :id, :type
end
def methods
super + @object.keys.include?(key) + connections.include?(key)
end
def respond_to?(method)
@object.keys.include?(key) || connections.include?(key) || super
end
def keys
@object.keys
end
def [](key)
@object[key]
end
def method_missing(method, *args, &block)
key = method.to_s
if @object.keys.include?(key)
@object[key]
elsif @connections_cache.has_key?(key)
@connections_cache[key]
elsif connections.include?(key)
@connections_cache[key] = @oauth_session.get(@id, :type => key)
else
super
end
end
end
def self.graph_base
"https://graph.facebook.com/"
end
# options:
# - scope: comma separated list of extends permissions. see http://developers.facebook.com/docs/authentication/permissions
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=#{CGI.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
# returns a hash with one value being 'access_token', the other being 'expires'
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=#{CGI.escape(redirect_uri)}"
oauth_url << "&client_secret=#{secret}"
oauth_url << "&code=#{CGI.escape(code)}"
resp = @@http.get oauth_url
puts 'resp=' + resp.body.to_s if @@logging
params = {}
params_array = resp.body.to_s.split("&")
params_array.each do |p|
ps = p.split("=")
params[ps[0]] = ps[1]
end
return params
end
# returns a hash with one value being 'access_token', the other being 'expires'
def self.fb_exchange_token(app_id, secret, access_token)
oauth_url = "#{graph_base}oauth/access_token"
oauth_url << "?client_id=#{app_id}"
oauth_url << "&client_secret=#{secret}"
oauth_url << "&grant_type=fb_exchange_token"
oauth_url << "&fb_exchange_token=#{CGI.escape(access_token)}"
resp = @@http.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
# Return a JSON object of working Oauth tokens from working session keys, returned in order given
def self.oauth_exchange_session(app_id, secret, session_keys)
url = "#{graph_base}oauth/exchange_sessions"
params = {}
params["client_id"] = "#{app_id}"
params["client_secret"] = "#{secret}"
params["sessions"] = "#{session_keys}"
options = {}
options[:params] = params
options[:method] = :post
return fetch(url, options)
end
# Return a JSON object of working Oauth tokens from working session keys, returned in order given
def self.authenticate_as_app(app_id, secret)
url = "#{graph_base}oauth/access_token"
params = {}
params["type"] = "client_cred"
params["client_id"] = "#{app_id}"
params["client_secret"] = "#{secret}"
options = {}
options[:params] = params
options[:method] = :get
options[:response_type] = :params
resp = fetch(url, options)
puts 'resp=' + resp.to_s if @@logging
resp
end
# 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
def self.get(access_token, id, options={})
url = graph_base
url << "v#{options[:version]}/" if options[:version]
url << id
url << "/#{options[:type]}" if options[:type]
params = options[:params] || {}
params["access_token"] = "#{(access_token)}"
params["metadata"] = "1" if options[:metadata]
params["fields"] = options[:fields].join(",") if options[:fields]
options[:params] = params
return fetch(url, options)
end
# Gets multiple 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
# Example:
#
# MiniFB.multiget(access_token, [123, 234])
#
# Can throw a connection Timeout if there is too many items
def self.multiget(access_token, ids, options={})
url = graph_base
url << "v#{options[:version]}/" if options[:version]
url << "#{options[:type]}" if options[:type]
params = options[:params] || {}
params["ids"] = ids.join(',')
params["access_token"] = "#{(access_token)}"
params["metadata"] = "1" if options[:metadata]
params["fields"] = options[:fields].join(",") if options[:fields]
options[:params] = params
return fetch(url, options)
end
# 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
def self.post(access_token, id, options={})
url = graph_base
url << "v#{options[:version]}/" if options[:version]
url << 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
# Sends a DELETE request 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
def self.delete(access_token, ids, options={})
url = graph_base
url << "v#{options[:version]}/" if options[:version]
params = options[:params] || {}
if ids.is_a?(Array)
params["ids"] = ids.join(',')
else
url << "#{ids}"
end
url << "/#{options[:type]}" if options[:type]
options.delete(:type)
options.each do |key, value|
params[key] = "#{value}"
end
params["access_token"] = "#{(access_token)}"
options[:params] = params
options[:method] = :delete
return fetch(url, options)
end
# Executes an FQL query
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
# 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)" })
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
# Uses new Oauth 2 authentication against old Facebook REST API
# options:
# - params: Any additional parameters you would like to submit
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
def self.fetch(url, options={})
case options[:method]
when :post
@@log.debug 'url_post=' + url if @@logging
response = @@http.post url, options[:params]
when :delete
if options[:params] && options[:params].size > 0
url += '?' + options[:params].map { |k, v| CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s) }.join('&')
end
@@log.debug 'url_delete=' + url if @@logging
response = @@http.delete url
else
if options[:params] && options[:params].size > 0
url += '?' + options[:params].map { |k, v| CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s) }.join('&')
end
@@log.debug 'url_get=' + url if @@logging
response = @@http.get url
end
resp = response.body
@@log.debug "Response = #{resp}. Status = #{response.status}" if @@logging
@@log.debug 'API Version =' + response.headers["Facebook-API-Version"].to_s if @@logging
# res_hash = JSON.parse(resp.to_s.size > 2 ? resp.to_s : {response: resp.to_s}.to_json)
# unless response.ok?
# raise MiniFB::FaceBookError.new(response.status, "#{res_hash["error"]["type"]}: #{res_hash["error"]["message"]}")
# end
if options[:response_type] == :params
# Some methods return a param like string, for example: access_token=11935261234123|rW9JMxbN65v_pFWQl5LmHHABC
params = {}
#params_array = resp.split("&")
params_array = resp.to_s.split("&")
params_array.each do |p|
ps = p.split("=")
params[ps[0]] = ps[1]
end
return params
else
res_hash = JSON.parse(resp.to_s.size > 2 ? resp.to_s : {response: resp.to_s}.to_json)
unless response.ok?
raise MiniFB::FaceBookError.new(response.status, "#{res_hash["error"]["type"]}: #{res_hash["error"]["message"]}")
end
end
if res_hash.is_a? Array # fql return this
res_hash.collect! { |x| x.is_a?(Hash) ? Hashie::Mash.new(x) : x }
else
res_hash = { response: res_hash } unless res_hash.is_a? Hash
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
res_hash
end
# Returns all available scopes.
def self.scopes
scopes = %w{
about_me activities birthday checkins 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
# This function expects arguments as a hash, so
# it is agnostic to different POST handling variants in ruby.
#
# Validate the arguments received from facebook. This is usually
# sent for the iframe in Facebook's canvas. It is not necessary
# to use this on the auth_token and uid passed to callbacks like
# post-add and post-remove.
#
# The arguments must be a mapping of to string keys and values
# or a string of http request data.
#
# If the data is invalid or not signed properly, an empty
# dictionary is returned.
#
# The secret argument should be an instance of FacebookSecret
# to hide value from simple introspection.
#
# DEPRECATED, use verify_signature instead
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
class FaceBookSecret
# Simple container that stores a secret value.
# Proc cannot be dumped or introspected by normal tools.
attr_reader :value
def initialize(value)
@value = Proc.new { value }
end
end
private
def self.post_params(params)
post_params = {}
params.each do |k, v|
k = k.to_s unless k.is_a?(String)
if Array === v || Hash === v
post_params[k] = JSON.dump(v)
else
post_params[k] = v
end
end
post_params
end
def self.signature_for(params, secret)
params.delete_if { |k, v| v.nil? }
raw_string = params.inject([]) do |collection, pair|
collection << pair.map { |x|
Array === x ? JSON.dump(x) : x
}.join("=")
collection
end.sort.join
Digest::MD5.hexdigest([raw_string, secret].join)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment