/lib/fgraph.rb
Ruby | 401 lines | 197 code | 43 blank | 161 comment | 10 complexity | d3e919a8a8a1fbd151cf29e6a0aaad3e MD5 | raw file
- 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
-
- # Note: FB pass access token with '|' character, which cause URI::InvalidURIError
- uri = URI.parse(url.gsub('|', '%7C'))
-
- 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.
- #
- # <tt>category</tt> - <tt>friends|home|feed|likes|movies|books|notes|photos|videos|events|groups</tt>
- #
- # # 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.
- #
- # <tt>client_id</tt> - Application ID
- # <tt>redirect_uri</tt> - 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.
- # <tt>scope (optional)</tt> -
- #
- # ==== Options
- # * <tt>scope</tt> - Extended permission required to fetch private data or request permision to
- # publish to Facebook on a user's behalf.
- # * <tt>display</tt> - 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 <tt>code</tt> and and <tt>redirect_uri</tt> options. <tt>code</tt> 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 <tt>:type => 'client_cred'</td> 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,
- :redirect_uri => ''
- }.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
- # * <tt>type</tt> - <tt>album|event|group|link|note|page|photo|post|status|user|video</tt>
- # * <tt>limit</tt> - max no of records
- # * <tt>offset</tt> - offset
- # * <tt>until</tt> - 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
- # * <tt>metric_path</tt> - e.g. application_api_calls/day
- # * <tt>since</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
- # * <tt>until</tt> - 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 unless option[0]
- 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