lib/koala.rb in koala-0.4 vs lib/koala.rb in koala-0.4.1

- old
+ new

@@ -7,301 +7,242 @@ # include default http services require 'http_services' module Koala - # Ruby client library for the Facebook Platform. - # Copyright 2010 Facebook - # Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional - # - # Licensed under the Apache License, Version 2.0 (the "License"); you may - # not use this file except in compliance with the License. You may obtain - # a copy of the License at - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - # License for the specific language governing permissions and limitations - # under the License. - # - # This client library is designed to support the Graph API and the official - # Facebook JavaScript SDK, which is the canonical way to implement - # Facebook authentication. Read more about the Graph API at - # http://developers.facebook.com/docs/api. You can download the Facebook - # JavaScript SDK at http://github.com/facebook/connect-js/. + + module Facebook + # Ruby client library for the Facebook Platform. + # Copyright 2010 Facebook + # Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # This client library is designed to support the Graph API and the official + # Facebook JavaScript SDK, which is the canonical way to implement + # Facebook authentication. Read more about the Graph API at + # http://developers.facebook.com/docs/api. You can download the Facebook + # JavaScript SDK at http://github.com/facebook/connect-js/. - FACEBOOK_GRAPH_SERVER = "graph.facebook.com" + GRAPH_SERVER = "graph.facebook.com" - class GraphAPI - # A client for the Facebook Graph API. - # - # See http://developers.facebook.com/docs/api for complete documentation - # for the API. - # - # The Graph API is made up of the objects in Facebook (e.g., people, pages, - # events, photos) and the connections between them (e.g., friends, - # photo tags, and event RSVPs). This client provides access to those - # primitive types in a generic way. For example, given an OAuth access - # token, this will fetch the profile of the active user and the list - # of the user's friends: - # - # graph = Facebook::GraphAPI.new(access_token) - # user = graph.get_object("me") - # friends = graph.get_connections(user["id"], "friends") - # - # You can see a list of all of the objects and connections supported - # by the API at http://developers.facebook.com/docs/reference/api/. - # - # You can obtain an access token via OAuth or by using the Facebook - # JavaScript SDK. See http://developers.facebook.com/docs/authentication/ - # for details. - # - # If you are using the JavaScript SDK, you can use the - # Facebook::get_user_from_cookie() method below to get the OAuth access token - # for the active user from the cookie saved by the SDK. - - # initialize with an access token - def initialize(access_token = nil) - @access_token = access_token - end - - def get_object(id, args = {}) - # Fetchs the given object from the graph. - request(id, args) - end - - def get_objects(ids, args = {}) - # Fetchs all of the given object from the graph. - # We return a map from ID to object. If any of the IDs are invalid, - # we raise an exception. - request("", args.merge("ids" => ids.join(","))) - end - - def get_connections(id, connection_name, args = {}) - # Fetchs the connections for given object. - request("#{id}/#{connection_name}", args) - end - - def put_object(parent_object, connection_name, args = {}) - # Writes the given object to the graph, connected to the given parent. + class GraphAPI + # A client for the Facebook Graph API. # - # For example, + # See http://developers.facebook.com/docs/api for complete documentation + # for the API. # - # graph.put_object("me", "feed", :message => "Hello, world") + # The Graph API is made up of the objects in Facebook (e.g., people, pages, + # events, photos) and the connections between them (e.g., friends, + # photo tags, and event RSVPs). This client provides access to those + # primitive types in a generic way. For example, given an OAuth access + # token, this will fetch the profile of the active user and the list + # of the user's friends: # - # writes "Hello, world" to the active user's wall. Likewise, this - # will comment on a the first post of the active user's feed: + # graph = Koala::Facebook::GraphAPI.new(access_token) + # user = graph.get_object("me") + # friends = graph.get_connections(user["id"], "friends") # - # feed = graph.get_connections("me", "feed") - # post = feed["data"][0] - # graph.put_object(post["id"], "comments", :message => "First!") + # You can see a list of all of the objects and connections supported + # by the API at http://developers.facebook.com/docs/reference/api/. # - # See http://developers.facebook.com/docs/api#publishing for all of - # the supported writeable objects. + # You can obtain an access token via OAuth or by using the Facebook + # JavaScript SDK. See http://developers.facebook.com/docs/authentication/ + # for details. # - # Most write operations require extended permissions. For example, - # publishing wall posts requires the "publish_stream" permission. See - # http://developers.facebook.com/docs/authentication/ for details about - # extended permissions. + # If you are using the JavaScript SDK, you can use the + # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token + # for the active user from the cookie saved by the SDK. + + # initialize with an access token + def initialize(access_token = nil) + @access_token = access_token + end + + def get_object(id, args = {}) + # Fetchs the given object from the graph. + api(id, args) + end + + def get_objects(ids, args = {}) + # Fetchs all of the given object from the graph. + # We return a map from ID to object. If any of the IDs are invalid, + # we raise an exception. + api("", args.merge("ids" => ids.join(","))) + end + + def get_connections(id, connection_name, args = {}) + # Fetchs the connections for given object. + api("#{id}/#{connection_name}", args) + end + + def put_object(parent_object, connection_name, args = {}) + # Writes the given object to the graph, connected to the given parent. + # + # For example, + # + # graph.put_object("me", "feed", :message => "Hello, world") + # + # writes "Hello, world" to the active user's wall. Likewise, this + # will comment on a the first post of the active user's feed: + # + # feed = graph.get_connections("me", "feed") + # post = feed["data"][0] + # graph.put_object(post["id"], "comments", :message => "First!") + # + # See http://developers.facebook.com/docs/api#publishing for all of + # the supported writeable objects. + # + # Most write operations require extended permissions. For example, + # publishing wall posts requires the "publish_stream" permission. See + # http://developers.facebook.com/docs/authentication/ for details about + # extended permissions. - raise GraphAPIError.new(nil, "Write operations require an access token") unless @access_token - request("#{parent_object}/#{connection_name}", args, "post") - end + raise GraphAPIError.new(nil, "Write operations require an access token") unless @access_token + api("#{parent_object}/#{connection_name}", args, "post") + end - def put_wall_post(message, attachment = {}, profile_id = "me") - # Writes a wall post to the given profile's wall. - # - # We default to writing to the authenticated user's wall if no - # profile_id is specified. - # - # attachment adds a structured attachment to the status message being - # posted to the Wall. It should be a dictionary of the form: - # - # {"name": "Link name" - # "link": "http://www.example.com/", - # "caption": "{*actor*} posted a new review", - # "description": "This is a longer description of the attachment", - # "picture": "http://www.example.com/thumbnail.jpg"} + def put_wall_post(message, attachment = {}, profile_id = "me") + # Writes a wall post to the given profile's wall. + # + # We default to writing to the authenticated user's wall if no + # profile_id is specified. + # + # attachment adds a structured attachment to the status message being + # posted to the Wall. It should be a dictionary of the form: + # + # {"name": "Link name" + # "link": "http://www.example.com/", + # "caption": "{*actor*} posted a new review", + # "description": "This is a longer description of the attachment", + # "picture": "http://www.example.com/thumbnail.jpg"} - self.put_object(profile_id, "feed", attachment.merge({:message => message})) - end + self.put_object(profile_id, "feed", attachment.merge({:message => message})) + end - def put_comment(object_id, message) - # Writes the given comment on the given post. - self.put_object(object_id, "comments", {:message => message}) - end + def put_comment(object_id, message) + # Writes the given comment on the given post. + self.put_object(object_id, "comments", {:message => message}) + end - def put_like(object_id) - # Likes the given post. - self.put_object(object_id, "likes") - end + def put_like(object_id) + # Likes the given post. + self.put_object(object_id, "likes") + end - def delete_object(id) - # Deletes the object with the given ID from the graph. - request(id, {}, "delete") - end + def delete_object(id) + # Deletes the object with the given ID from the graph. + api(id, {}, "delete") + end - def search(search_terms, args = {}) - # Searches for a given term - request("search", args.merge({:q => search_terms})) - end + def search(search_terms, args = {}) + # Searches for a given term + api("search", args.merge({:q => search_terms})) + end - def request(path, args = {}, verb = "get") - # Fetches the given path in the Graph API. - args["access_token"] = @access_token if @access_token + def api(path, args = {}, verb = "get") + # Fetches the given path in the Graph API. + args["access_token"] = @access_token if @access_token - # make the request via the provided service - result = make_request(path, args, verb) + # make the request via the provided service + result = Koala.make_request(path, args, verb) - # Facebook sometimes sends results like "true" and "false", which aren't strictly object - # and cause JSON.parse to fail - # so we account for that - response = JSON.parse("[#{result}]")[0] + # Facebook sometimes sends results like "true" and "false", which aren't strictly object + # and cause JSON.parse to fail + # so we account for that + response = JSON.parse("[#{result}]")[0] - # check for errors - if response.is_a?(Hash) && error = response["error"] - raise GraphAPIError.new(error["code"], error["message"]) - end + # check for errors + if response.is_a?(Hash) && error = response["error"] + raise GraphAPIError.new(error["code"], error["message"]) + end - response + response + end end - - # set up the http service used to make requests - # you can use your own (for HTTParty, etc.) by calling Koala::API.http_service = YourModule - def self.http_service=(service) - self.send(:include, service) - end - - # by default, try requiring Typhoeus -- if that works, use it - begin - require 'typhoeus' - Koala::GraphAPI.http_service = TyphoeusService - rescue LoadError - Koala::GraphAPI.http_service = NetHTTPService - end - end - class GraphAPIError < Exception - attr_accessor :code - def initialize(code, message) - super(message) - self.code = code + class GraphAPIError < Exception + attr_accessor :code + def initialize(code, message) + super(message) + self.code = code + end end - end - class OAuth - def initialize(app_id, app_secret, oauth_callback_url = nil) - @app_id = app_id - @app_secret = app_secret - @oauth_callback_url = oauth_callback_url - end + class OAuth + def initialize(app_id, app_secret, oauth_callback_url = nil) + @app_id = app_id + @app_secret = app_secret + @oauth_callback_url = oauth_callback_url + end - def get_user_from_cookie(cookie_hash) - # Parses the cookie set by the official Facebook JavaScript SDK. - # - # cookies should be a dictionary-like object mapping cookie names to - # cookie values. - # - # If the user is logged in via Facebook, we return a dictionary with the - # keys "uid" and "access_token". The former is the user's Facebook ID, - # and the latter can be used to make authenticated requests to the Graph API. - # If the user is not logged in, we return None. - # - # Download the official Facebook JavaScript SDK at - # http://github.com/facebook/connect-js/. Read more about Facebook - # authentication at http://developers.facebook.com/docs/authentication/. + def get_user_from_cookie(cookie_hash) + # Parses the cookie set by the official Facebook JavaScript SDK. + # + # cookies should be a dictionary-like object mapping cookie names to + # cookie values. + # + # If the user is logged in via Facebook, we return a dictionary with the + # keys "uid" and "access_token". The former is the user's Facebook ID, + # and the latter can be used to make authenticated requests to the Graph API. + # If the user is not logged in, we return None. + # + # Download the official Facebook JavaScript SDK at + # http://github.com/facebook/connect-js/. Read more about Facebook + # authentication at http://developers.facebook.com/docs/authentication/. - if fb_cookie = cookie_hash["fbs_" + @app_id.to_s] - # remove the opening/closing quote - fb_cookie = fb_cookie.gsub(/\"/, "") + if fb_cookie = cookie_hash["fbs_" + @app_id.to_s] + # remove the opening/closing quote + fb_cookie = fb_cookie.gsub(/\"/, "") - # since we no longer get individual cookies, we have to separate out the components ourselves - components = {} - fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]} + # since we no longer get individual cookies, we have to separate out the components ourselves + components = {} + fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]} - auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("") - sig = Digest::MD5.hexdigest(auth_string + @app_secret) + auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("") + sig = Digest::MD5.hexdigest(auth_string + @app_secret) - sig == components["sig"] && Time.now.to_i < components["expires"].to_i ? components : nil + sig == components["sig"] && (components["expires"].to_i == 0 || Time.now.to_i < components["expires"].to_i) ? components : nil + end end - end - def url_for_oauth_code(options = {}) - callback = options[:callback] || @oauth_callback_url - permissions = options[:permissions] - scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : "" + def url_for_oauth_code(options = {}) + # for permissions, see http://developers.facebook.com/docs/authentication/permissions + permissions = options[:permissions] + scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : "" - # Creates the URL for oauth authorization for a given callback and optional set of permissions - "https://#{FACEBOOK_GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}" - end + callback = options[:callback] || @oauth_callback_url + + # Creates the URL for oauth authorization for a given callback and optional set of permissions + "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}" + end - def url_for_access_token(code, callback = @oauth_callback_url) - # Creates the URL for the token corresponding to a given code generated by Facebook - "https://#{FACEBOOK_GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}" + def url_for_access_token(code, callback = @oauth_callback_url) + # Creates the URL for the token corresponding to a given code generated by Facebook + "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}" + end end - - # for more details and up to date information, see http://developers.facebook.com/docs/authentication/permissions - def self.all_permissions - USER_PERMISSIONS.concat(FRIEND_PERMISSIONS) - end - - USER_PERMISSIONS = [ - # PUBLISHING - "publish_stream", # Enables your application to post content, comments, and likes to a user's stream and to the streams of the user's friends, without prompting the user each time. - "create_event", # Enables your application to create and modify events on the user's behalf - "rsvp_event", # Enables your application to RSVP to events on the user's behalf - "sms", # Enables your application to send messages to the user and respond to messages from the user via text message - "offline_access", # Enables your application to perform authorized requests on behalf of the user at any time (e.g. permanent access token) - - # DATA ACCESS - "email", # Provides access to the user's primary email address in the email property - "read_stream", # Provides access to all the posts in the user's News Feed and enables your application to perform searches against the user's News Feed - "user_about_me", # Provides access to the "About Me" section of the profile in the about property - "user_activities", # Provides access to the user's list of activities as the activities connection - "user_birthday", # Provides access to the full birthday with year as the birthday_date property - "user_education_history", # Provides access to education history as the education property - "user_events", # Provides access to the list of events the user is attending as the events connection - "user_groups", # Provides access to the list of groups the user is a member of as the groups connection - "user_hometown", # Provides access to the user's hometown in the hometown property - "user_interests", # Provides access to the user's list of interests as the interests connection - "user_likes", # Provides access to the list of all of the pages the user has liked as the likes connection - "user_location", # Provides access to the user's current location as the current_location property - "user_notes", # Provides access to the user's notes as the notes connection - "user_online_presence", # Provides access to the user's online/offline presence - "user_photo_video_tags", # Provides access to the photos the user has been tagged in as the photos connection - "user_photos", # Provides access to the photos the user has uploaded - "user_relationships", # Provides access to the user's family and personal relationships and relationship status - "user_religion_politics", # Provides access to the user's religious and political affiliations - "user_status", # Provides access to the user's most recent status message - "user_videos", # Provides access to the videos the user has uploaded - "user_website", # Provides access to the user's web site URL - "user_work_history", # Provides access to work history as the work property - "read_friendlists", # Provides read access to the user's friend lists - "read_requests" # Provides read access to the user's friend requests - ] - - FRIEND_PERMISSIONS = [ - # DATA ACCESS - "friends_about_me", # Provides access to the "About Me" section of the profile in the about property - "friends_activities", # Provides access to the user's list of activities as the activities connection - "friends_birthday", # Provides access to the full birthday with year as the birthday_date property - "friends_education_history", # Provides access to education history as the education property - "friends_events", # Provides access to the list of events the user is attending as the events connection - "friends_groups", # Provides access to the list of groups the user is a member of as the groups connection - "friends_hometown", # Provides access to the user's hometown in the hometown property - "friends_interests", # Provides access to the user's list of interests as the interests connection - "friends_likes", # Provides access to the list of all of the pages the user has liked as the likes connection - "friends_location", # Provides access to the user's current location as the current_location property - "friends_notes", # Provides access to the user's notes as the notes connection - "friends_online_presence", # Provides access to the user's online/offline presence - "friends_photo_video_tags", # Provides access to the photos the user has been tagged in as the photos connection - "friends_photos", # Provides access to the photos the user has uploaded - "friends_relationships", # Provides access to the user's family and personal relationships and relationship status - "friends_religion_politics", # Provides access to the user's religious and political affiliations - "friends_status", # Provides access to the user's most recent status message - "friends_videos", # Provides access to the videos the user has uploaded - "friends_website", # Provides access to the user's web site URL - "friends_work_history" # Provides access to work history as the work property - ] + end + + # finally, set up the http service Koala methods used to make requests + # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule + def self.http_service=(service) + self.send(:include, service) + end + + # by default, try requiring Typhoeus -- if that works, use it + begin + require 'typhoeus' + Koala.http_service = TyphoeusService + rescue LoadError + Koala.http_service = NetHTTPService end end \ No newline at end of file