require 'httparty'
require 'cgi'
require 'fgraph/client'
module FGraph
include HTTParty
base_uri 'https://graph.facebook.com'
format :json
# Facebook Error
class FacebookError < StandardError
attr_reader :data
def initialize(data)
@data = data
super("(#{data['type']}) #{data['message']}")
end
end
class QueryParseError < FacebookError; end
class GraphMethodError < FacebookError; end
class OAuthError < FacebookError; end
class OAuthAccessTokenError < OAuthError; end
# Collection objects for Graph response with array data.
#
class Collection < Array
attr_reader :next_url, :previous_url, :next_options, :previous_options
# Initialize Facebook response object with 'data' array value.
def initialize(response)
return super unless response
super(response['data'])
paging = response['paging'] || {}
self.next_url = paging['next']
self.previous_url = paging['previous']
end
def next_url=(url)
@next_url = url
@next_options = self.url_options(url)
end
def previous_url=(url)
@previous_url = url
@previous_options = self.url_options(url)
end
def first?
@previous_url.blank? and not @next_url.blank?
end
def next?
not @next_url.blank?
end
def previous?
not @previous_url.blank?
end
def url_options(url)
return unless url
uri = URI.parse(url)
options = {}
uri.query.split('&').each do |param_set|
param_set = param_set.split('=')
options[param_set[0]] = CGI.unescape(param_set[1])
end
options
end
end
class << self
attr_accessor :config
# Single object query.
#
# # Users: https://graph.facebook.com/btaylor (Bret Taylor)
# FGraph.object('btaylor')
#
# # Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
# FGraph.object('cocacola')
#
# # Fields selection with metadata
# FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
#
# # Page photos
# FGraph.object('/cocacola/photos')
# photos = FGraph.object_photos('cocacola')
#
# # Support id from object hash
# friend = { 'name' => 'Mark Zuckerberg', 'id' => '4'}
# friend_details = FGraph.object(friend)
def object(id, options={})
id = self.get_id(id)
perform_get("/#{id}", options)
end
# call-seq:
# FGraph.objects(id, id)
# FGraph.objects(id, id, options_hash)
#
# Multiple objects query.
#
# # Multiple users select: https://graph.facebook.com?ids=arjun,vernal
# FGraph.objects('arjun', 'vernel')
#
# # Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
# FGraph.objects('arjun', 'vernel', :fields => 'id,name,picture')
#
def objects(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
# If first input before option is an array
if args.length == 1 and args.first.is_a?(Array)
args = args.first.map do |arg|
self.get_id(arg)
end
end
options = options.merge(:ids => args.join(','))
perform_get("/", options)
end
# call-seq:
# FGraph.me(category)
# FGraph.me(category, options_hash)
#
# Returns current user object details.
#
# category - friends|home|feed|likes|movies|books|notes|photos|videos|events|groups
#
# # Current user: https://graph.facebook.com/me?access_token=...
# FGraph.me(:access_token => '...')
#
# # Current user's friends: https://graph.facebook.com/me/friends?access_token=...
# FGraph.me('friends', :access_token => '...')
# FGraph.me_friends(:access_token => '...')
#
def me(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
category = args.shift
path = "me"
path += "/#{category}" unless category.blank?
self.object(path, options)
end
# Request authorization from Facebok to fetch private data in the profile or permission to publish on a
# user's behalf. Returns Oauth Authorization URL, redirect to this URL to allow user to authorize your
# application from Facebook.
#
# client_id - Application ID
# redirect_uri - Needs to begin with your app's Connect URL. For instance, if your Connect URL
# is http://www.example.com then your redirect URI could be http://www.example.com/oauth_redirect.
# scope (optional) -
#
# ==== Options
# * scope - Extended permission required to fetch private data or request permision to
# publish to Facebook on a user's behalf.
# * display - Other display type for authentication/authorization form, i.e. popup, touch.
#
# # https://graph.facebook.com/oauth/authorize?
# # client_id=...&
# # redirect_uri=http://www.example.com/oauth_redirect&
# # scope=publish_stream
#
# FGraph.oauth_authorize_url('[client id]', 'http://www.example.com/oauth_redirect', :scope =>
# 'publish_stream')
#
def oauth_authorize_url(client_id, redirect_uri, options={})
self.format_url('/oauth/authorize', {
:client_id => client_id,
:redirect_uri => redirect_uri
}.merge(options))
end
# Return OAuth access_token. There are two types of access token, user access token and application
# access token.
#
# User access_token requires code and and redirect_uri options. code is
# the autorization code appended as query string to redirect URI when accessing oauth authorization URL.
#
# # https://graph.facebook.com/oauth/access_token?
# # client_id=...&
# # client_secret=...&
# # redirect_uri=http://www.example.com/oauth_redirect&
# # code=...
# FGraph.oauth_access_token('[client id]', '[client secret]',
# :redirect_uri => ''http://www.example.com/oauth_redirect',
# :code => '[authorization code]')
#
# Application access token requires :type => 'client_cred' option. Used to access application
# insights data.
#
# # https://graph.facebook.com/oauth/access_token?
# # client_id=...&
# # client_secret=...&
# # type=client_cred
# FGraph.oauth_access_token('[client id]', '[client secret]', :type => 'client_cred')
#
def oauth_access_token(client_id, client_secret, options={})
url = self.format_url('/oauth/access_token', {
:client_id => client_id,
:client_secret => client_secret
}.merge(options || {}))
response = self.perform_get(url)
response_hash = {}
response.split('&').each do |value|
value_pair = value.split('=')
response_hash[value_pair[0]] = value_pair[1]
end
response_hash
end
# Shortcut to retrieve application access token.
def oauth_app_access_token(client_id, client_secret)
self.oauth_access_token(client_id, client_secret, :type => 'client_cred')
end
# Publish to Facebook, you would need to be authorized and provide access token.
#
# # Post to user's feed.
# # curl -F 'access_token=...' \
# # -F 'message=Hello, Arjun. I like this new API.' \
# # https://graph.facebook.com/arjun/feed
# FGraph.publish('arjun/feed', :message => 'Hello, Arjun. I like this new API.',
# :access_token => '...')
# FGraph.publish_feed('arjun', :message => '...', :access_token => '...')
# FGraph.publish_feed('me', ':message => '...', :access_token => '...')
#
# ==== Options
#
# Method Description Options
# -------------------------------------------------------------------------------------
# /PROFILE_ID/feed write to the given profile's feed/wall :message, :picture,
# :link, :name, description
# /POST_ID/comments comment on the given post :message
# /POST_ID/likes like the given post none
# /PROFILE_ID/notes write a note on the given profile :message, :subject
# /PROFILE_ID/links write a link on the given profile :link, :message
# /EVENT_ID/attending attend the given event none
# /EVENT_ID/maybe maybe attend the given event none
# /EVENT_ID/declined decline the given event none
#
def publish(id, options={})
id = self.get_id(id)
self.perform_post("/#{id}", options)
end
# Delete objects in the graph.
#
# # DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
#
# FGraph.remove('[ID]')
# FGraph.remove('[ID]/likes')
# FGraph.remove_likes('[ID]')
#
def remove(id, options={})
id = self.get_id(id)
self.perform_delete("/#{id}", options)
end
# Search over all public objects in the social graph.
#
# # https://graph.facebook.com/search?q=watermelon&type=post
# FGraph.search('watermelon', :type => 'post')
# FGraph.search_post('watermelon')
#
# ==== Options
# * type - album|event|group|link|note|page|photo|post|status|user|video
# * limit - max no of records
# * offset - offset
# * until - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
def search(query, options={})
self.perform_get("/search", {
:q => query
}.merge(options|| {}))
end
# Download insights data for your application.
#
# # https://graph.facebook.com/[client_id]/insights?access_token=...
# FGraph.insights('[client_id]', '[app_access_token]')
#
# # https://graph.facebook.com/[client_id]/insights/application_api_call/day?access_token=...
# FGraph.insights('[client_id]', '[app_access_token]', :metric_path => 'application_api_call/day')
#
# ==== Options
# * metric_path - e.g. application_api_calls/day
# * since - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
# * until - until (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
def insights(client_id, app_access_token, options={})
metric_path = options.delete(:metric_path)
path = "/#{client_id}/insights"
path += "/#{metric_path}" if metric_path
self.perform_get(path, {
:access_token => app_access_token
}.merge(options || {}))
end
def perform_get(uri, options = {})
handle_response(get(uri, {:query => options}))
end
def perform_post(uri, options = {})
handle_response(post(uri, {:body => options}))
end
def perform_delete(uri, options = {})
handle_response(delete(uri, {:body => options}))
end
def handle_response(response)
unless response['error']
return FGraph::Collection.new(response) if response['data']
response
else
case response['error']['type']
when 'QueryParseException'
raise QueryParseError, response['error']
when 'GraphMethodException'
raise GraphMethodError, response['error']
when 'OAuthException'
raise OAuthError, response['error']
when 'OAuthAccessTokenException'
raise OAuthAccessTokenError, response['error']
else
raise FacebookError, response['error']
end
end
end
def format_url(path, options={})
url = self.base_uri.dup
url << path
unless options.blank?
url << "?"
option_count = 0
stringified_options = {}
options.each do |key, value|
stringified_options[key.to_s] = value
end
options = stringified_options
options.each do |option|
next if option[1].blank?
url << "&" if option_count > 0
url << "#{option[0]}=#{CGI.escape(option[1].to_s)}"
option_count += 1
end
end
url
end
def method_missing(name, *args, &block)
names = name.to_s.split('_')
super unless names.length > 1
case names.shift
when 'object'
# object_photos
self.object("#{args[0]}/#{names[0]}", args[1])
when 'me'
# me_photos
self.me(names[0], args[0])
when 'publish'
# publish_feed(id)
self.publish("#{args[0]}/#{names[0]}", args[1])
when 'remove'
# remove_feed(id)
self.remove("#{args[0]}/#{names[0]}", args[1])
when 'search'
# search_user(query)
options = args[1] || {}
options[:type] = names[0]
self.search(args[0], options)
else
super
end
end
# Return ID['id'] if ID is a hash object
#
def get_id(id)
return unless id
id = id['id'] || id[:id] if id.is_a?(Hash)
id
end
end
end