cotweet_koala 0.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,136 @@
1
+ module Koala
2
+ module Facebook
3
+ GRAPH_SERVER = "graph.facebook.com"
4
+
5
+ module GraphAPIMethods
6
+ # A client for the Facebook Graph API.
7
+ #
8
+ # See http://developers.facebook.com/docs/api for complete documentation
9
+ # for the API.
10
+ #
11
+ # The Graph API is made up of the objects in Facebook (e.g., people, pages,
12
+ # events, photos) and the connections between them (e.g., friends,
13
+ # photo tags, and event RSVPs). This client provides access to those
14
+ # primitive types in a generic way. For example, given an OAuth access
15
+ # token, this will fetch the profile of the active user and the list
16
+ # of the user's friends:
17
+ #
18
+ # graph = Koala::Facebook::GraphAPI.new(access_token)
19
+ # user = graph.get_object("me")
20
+ # friends = graph.get_connections(user["id"], "friends")
21
+ #
22
+ # You can see a list of all of the objects and connections supported
23
+ # by the API at http://developers.facebook.com/docs/reference/api/.
24
+ #
25
+ # You can obtain an access token via OAuth or by using the Facebook
26
+ # JavaScript SDK. See http://developers.facebook.com/docs/authentication/
27
+ # for details.
28
+ #
29
+ # If you are using the JavaScript SDK, you can use the
30
+ # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
31
+ # for the active user from the cookie saved by the SDK.
32
+
33
+ def get_object(id, args = {})
34
+ # Fetchs the given object from the graph.
35
+ graph_call(id, args)
36
+ end
37
+
38
+ def get_objects(ids, args = {})
39
+ # Fetchs all of the given object from the graph.
40
+ # We return a map from ID to object. If any of the IDs are invalid,
41
+ # we raise an exception.
42
+ graph_call("", args.merge("ids" => ids.join(",")))
43
+ end
44
+
45
+ def get_connections(id, connection_name, args = {})
46
+ # Fetchs the connections for given object.
47
+ graph_call("#{id}/#{connection_name}", args)["data"]
48
+ end
49
+
50
+ def get_picture(object, args = {})
51
+ result = graph_call("#{object}/picture", args, "get", :http_component => :headers)
52
+ result["Location"]
53
+ end
54
+
55
+ def put_object(parent_object, connection_name, args = {})
56
+ # Writes the given object to the graph, connected to the given parent.
57
+ #
58
+ # For example,
59
+ #
60
+ # graph.put_object("me", "feed", :message => "Hello, world")
61
+ #
62
+ # writes "Hello, world" to the active user's wall. Likewise, this
63
+ # will comment on a the first post of the active user's feed:
64
+ #
65
+ # feed = graph.get_connections("me", "feed")
66
+ # post = feed["data"][0]
67
+ # graph.put_object(post["id"], "comments", :message => "First!")
68
+ #
69
+ # See http://developers.facebook.com/docs/api#publishing for all of
70
+ # the supported writeable objects.
71
+ #
72
+ # Most write operations require extended permissions. For example,
73
+ # publishing wall posts requires the "publish_stream" permission. See
74
+ # http://developers.facebook.com/docs/authentication/ for details about
75
+ # extended permissions.
76
+
77
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
78
+ graph_call("#{parent_object}/#{connection_name}", args, "post")
79
+ end
80
+
81
+ def put_object_multipart(parent_object, connection_name, args = {})
82
+ raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
83
+ graph_call("#{parent_object}/#{connection_name}", args, "post", :multipart => true)
84
+ end
85
+
86
+ def put_wall_post(message, attachment = {}, profile_id = "me")
87
+ # Writes a wall post to the given profile's wall.
88
+ #
89
+ # We default to writing to the authenticated user's wall if no
90
+ # profile_id is specified.
91
+ #
92
+ # attachment adds a structured attachment to the status message being
93
+ # posted to the Wall. It should be a dictionary of the form:
94
+ #
95
+ # {"name": "Link name"
96
+ # "link": "http://www.example.com/",
97
+ # "caption": "{*actor*} posted a new review",
98
+ # "description": "This is a longer description of the attachment",
99
+ # "picture": "http://www.example.com/thumbnail.jpg"}
100
+
101
+ self.put_object(profile_id, "feed", attachment.merge({:message => message}))
102
+ end
103
+
104
+ def put_comment(object_id, message)
105
+ # Writes the given comment on the given post.
106
+ self.put_object(object_id, "comments", {:message => message})
107
+ end
108
+
109
+ def put_like(object_id)
110
+ # Likes the given post.
111
+ self.put_object(object_id, "likes")
112
+ end
113
+
114
+ def delete_object(id)
115
+ # Deletes the object with the given ID from the graph.
116
+ graph_call(id, {}, "delete")
117
+ end
118
+
119
+ def search(search_terms, args = {})
120
+ # Searches for a given term
121
+ graph_call("search", args.merge({:q => search_terms}))
122
+ end
123
+
124
+ def graph_call(*args)
125
+ response = api(*args) do |response|
126
+ # check for Graph API-specific errors
127
+ if response.is_a?(Hash) && error_details = response["error"]
128
+ raise APIError.new(error_details)
129
+ end
130
+ end
131
+
132
+ response
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,85 @@
1
+ module Koala
2
+ class Response
3
+ attr_reader :status, :body, :headers
4
+ def initialize(status, body, headers)
5
+ @status = status
6
+ @body = body
7
+ @headers = headers
8
+ end
9
+ end
10
+
11
+ module NetHTTPService
12
+ # this service uses Net::HTTP to send requests to the graph
13
+ def self.included(base)
14
+ base.class_eval do
15
+ require 'net/http' unless defined?(Net::HTTP)
16
+ require 'net/https'
17
+ require 'net/http_multipart_post'
18
+
19
+ def self.make_request(path, args, verb, options = {})
20
+ # We translate args to a valid query string. If post is specified,
21
+ # we send a POST request to the given path with the given arguments.
22
+
23
+ # if the verb isn't get or post, send it as a post argument
24
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
25
+
26
+ server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
27
+ http = Net::HTTP.new(server, 443)
28
+ http.use_ssl = true
29
+ # we turn off certificate validation to avoid the
30
+ # "warning: peer certificate won't be verified in this SSL session" warning
31
+ # not sure if this is the right way to handle it
32
+ # see http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
33
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
34
+
35
+ result = http.start { |http|
36
+ response, body = if (verb == "post")
37
+ if has_multipart_files?(args)
38
+ req = Net::HTTP::MultipartPost.new(URI.parse("https://#{server}/#{path}"), args)
39
+ req.post
40
+ else
41
+ http.post(path, encode_params(args))
42
+ end
43
+ else
44
+ http.get("#{path}?#{encode_params(args)}")
45
+ end
46
+ Koala::Response.new(response.code.to_i, body, response)
47
+ }
48
+ end
49
+
50
+ protected
51
+ def self.has_multipart_files?(param_hash)
52
+ (param_hash || {}).values.select {|x| x.is_a?(Net::HTTP::MultipartPostFile)}.any?
53
+ end
54
+
55
+ def self.encode_params(param_hash)
56
+ # TODO investigating whether a built-in method handles this
57
+ # if no hash (e.g. no auth token) return empty string
58
+ ((param_hash || {}).collect do |key_and_value|
59
+ key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
60
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
61
+ end).join("&")
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ module TyphoeusService
68
+ # this service uses Typhoeus to send requests to the graph
69
+ def self.included(base)
70
+ base.class_eval do
71
+ require 'typhoeus' unless defined?(Typhoeus)
72
+ include Typhoeus
73
+
74
+ def self.make_request(path, args, verb, options = {})
75
+ # if the verb isn't get or post, send it as a post argument
76
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
77
+ server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
78
+ response = self.send(verb, "https://#{server}/#{path}", :params => args)
79
+ Koala::Response.new(response.code, response.body, response.headers_hash)
80
+ end
81
+ end # class_eval
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,95 @@
1
+ require 'koala'
2
+
3
+ module Koala
4
+ module Facebook
5
+ module RealtimeUpdateMethods
6
+ # note: to subscribe to real-time updates, you must have an application access token
7
+
8
+ def self.included(base)
9
+ # make the attributes readable
10
+ base.class_eval do
11
+ attr_reader :app_id, :app_access_token, :secret
12
+
13
+ # parses the challenge params and makes sure the call is legitimate
14
+ # returns the challenge string to be sent back to facebook if true
15
+ # returns false otherwise
16
+ # this is a class method, since you don't need to know anything about the app
17
+ # saves a potential trip fetching the app access token
18
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
19
+ if params["hub.mode"] == "subscribe" &&
20
+ # you can make sure this is legitimate through two ways
21
+ # if your store the token across the calls, you can pass in the token value
22
+ # and we'll make sure it matches
23
+ (verify_token && params["hub.verify_token"] == verify_token) ||
24
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
25
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
26
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
27
+ (verification_block && yield(params["hub.verify_token"]))
28
+ params["hub.challenge"]
29
+ else
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def initialize(options = {})
37
+ @app_id = options[:app_id]
38
+ @app_access_token = options[:app_access_token]
39
+ @secret = options[:secret]
40
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
41
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
42
+ end
43
+
44
+ # fetch the access token if we're provided a secret
45
+ if @secret && !@app_access_token
46
+ oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
47
+ @app_access_token = oauth.get_app_access_token
48
+ end
49
+ end
50
+
51
+ # subscribes for realtime updates
52
+ # your callback_url must be set up to handle the verification request or the subscription will not be set up
53
+ # http://developers.facebook.com/docs/api/realtime
54
+ def subscribe(object, fields, callback_url, verify_token)
55
+ args = {
56
+ :object => object,
57
+ :fields => fields,
58
+ :callback_url => callback_url,
59
+ :verify_token => verify_token
60
+ }
61
+ # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
62
+ api(subscription_path, args, 'post', :http_component => :status) == 200
63
+ end
64
+
65
+ # removes subscription for object
66
+ # if object is nil, it will remove all subscriptions
67
+ def unsubscribe(object = nil)
68
+ args = {}
69
+ args[:object] = object if object
70
+ api(subscription_path, args, 'delete', :http_component => :status) == 200
71
+ end
72
+
73
+ def list_subscriptions
74
+ api(subscription_path)["data"]
75
+ end
76
+
77
+ def api(*args) # same as GraphAPI
78
+ response = super(*args) do |response|
79
+ # check for subscription errors
80
+ if response.is_a?(Hash) && error_details = response["error"]
81
+ raise APIError.new(error_details)
82
+ end
83
+ end
84
+
85
+ response
86
+ end
87
+
88
+ protected
89
+
90
+ def subscription_path
91
+ @subscription_path ||= "#{@app_id}/subscriptions"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ module Koala
2
+ module Facebook
3
+ REST_SERVER = "api.facebook.com"
4
+
5
+ module RestAPIMethods
6
+ def fql_query(fql)
7
+ rest_call('fql.query', 'query' => fql)
8
+ end
9
+
10
+ def rest_call(method, args = {})
11
+ response = api("method/#{method}", args.merge('format' => 'json'), 'get', :rest_api => true) do |response|
12
+ # check for REST API-specific errors
13
+ if response.is_a?(Hash) && response["error_code"]
14
+ raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
15
+ end
16
+ end
17
+
18
+ response
19
+ end
20
+ end
21
+
22
+ end # module Facebook
23
+ end # module Koala
@@ -0,0 +1,104 @@
1
+ Koala
2
+ ====
3
+ Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook library for Ruby, supporting the Graph API, the old REST API, realtime updates, and OAuth validation. We wrote Koala with four goals:
4
+
5
+ * Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 500 lines of code.)
6
+ * Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
7
+ * Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
8
+ * Tested: Koala should have complete test coverage, so you can rely on it. (Our complete test coverage can be run against either mocked responses or the live Facebook servers.)
9
+
10
+ Graph API
11
+ ----
12
+ The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
13
+ graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
14
+ profile = graph.get_object("me")
15
+ friends = graph.get_connection("me", "friends")
16
+ graph.put_object("me", "feed", :message => "I am writing on my wall!")
17
+
18
+ Check out the wiki for more examples.
19
+
20
+ The old-school REST API
21
+ -----
22
+ Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls -- including fql.query -- that can't yet be done via the Graph.
23
+
24
+ Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
25
+
26
+ @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
27
+ @rest.fql_query(my_fql_query) # convenience method
28
+ @rest.rest_call("stream.publish", arguments_hash) # generic version
29
+
30
+ We reserve the right to expand the built-in REST API coverage to additional convenience methods in the future, depending on how fast Facebook moves to fill in the gaps.
31
+
32
+ (If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
33
+
34
+ OAuth
35
+ -----
36
+ You can use the Graph and REST APIs without an OAuth access token, but the real magic happens when you provide Facebook an OAuth token to prove you're authenticated. Koala provides an OAuth class to make that process easy:
37
+ @oauth = Koala::Facebook::OAuth.new(app_id, code, callback_url)
38
+
39
+ If your application uses Koala and the Facebook [JavaScript SDK](http://github.com/facebook/connect-js) (formerly Facebook Connect), you can use the OAuth class to parse the cookies:
40
+ @oauth.get_user_from_cookie(cookies)
41
+
42
+ And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
43
+ # generate authenticating URL
44
+ @oauth.url_for_oauth_code
45
+ # fetch the access token once you have the code
46
+ @oauth.get_access_token(code)
47
+
48
+ You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
49
+ @oauth.get_app_access_token
50
+
51
+ That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
52
+
53
+ *Exchanging session keys:* Stuck building tab applications on Facebook? Wishing you had an OAuth token so you could use the Graph API? You're in luck! Koala now allows you to exchange session keys for OAuth access tokens:
54
+ @oauth.get_token_from_session_key(session_key)
55
+ @oauth.get_tokens_from_session_keys(array_of_session_keys)
56
+
57
+ Real-time Updates
58
+ -----
59
+ The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
60
+
61
+ Currently, Facebook only supports subscribing to users, permissions and errors. On top of that, there are limitations on what attributes and connections for each of these objects you can subscribe to updates for. Check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details.
62
+
63
+ Koala makes it easy to interact with your applications using the RealTimeUpdates class:
64
+
65
+ @updates = Koala::Facebook::RealTimeUpdates.new(:app_id => app_id, :secret => secret)
66
+
67
+ You can do just about anything with your real-time update subscriptions using the RealTimeUpdates class:
68
+
69
+ # Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
70
+ @updates.subscribe("user", "first_name, last_name", callback_token, verify_token)
71
+
72
+ # Get an array of your current subscriptions (one hash for each object you've subscribed to)
73
+ @updates.list_subscriptions
74
+
75
+ # Unsubscribe from updates for an object
76
+ @updates.unsubscribe("user")
77
+
78
+ And to top it all off, RealTimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
79
+
80
+ # Returns the hub.challenge parameter in params if the verify token in params matches verify_token
81
+ Koala::Facebook::RealTimeUpdates.meet_challenge(params, your_verify_token)
82
+
83
+ For more information about meet_challenge and the RealTimeUpdates class, check out the Real-Time Updates page on the wiki.
84
+
85
+ See examples, ask questions
86
+ -----
87
+ Some resources to help you as you play with Koala and the Graph API:
88
+
89
+ * Complete Koala documentation <a href="http://wiki.github.com/arsduo/koala/">on the wiki</a>
90
+ * The <a href="http://groups.google.com/group/koala-users">Koala users group</a> on Google Groups, the place for your Koala and API questions
91
+ * The Koala-powered <a href="http://oauth.twoalex.com" target="_blank">OAuth Playground</a>, where you can easily generate OAuth access tokens and any other data needed to test out the APIs or OAuth
92
+
93
+ Testing
94
+ -----
95
+
96
+ Unit tests are provided for all of Koala's methods. By default, these tests run against mock responses and hence are ready out of the box:
97
+ # From the spec directory
98
+ spec koala_spec.rb
99
+
100
+ You can also run live tests against Facebook's servers:
101
+ # Again from the spec directory
102
+ spec koala_spec_without_mocks.rb
103
+
104
+ Important Note: to run the live tests, you have to provide some of your own data: a valid OAuth access token with publish\_stream and read\_stream permissions and an OAuth code that can be used to generate an access token. You can get these data at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
@@ -0,0 +1,44 @@
1
+ # Check out http://oauth.twoalex.com/ to easily generate tokens, cookies, etc.
2
+ # Those values will work with the default settings in this yaml.
3
+ # Of course, you can change this to work with your own app.
4
+ # Just remember to update all fields!
5
+
6
+ # You must supply this value yourself to test the GraphAPI class.
7
+ # Your OAuth token should have publish_stream and read_stream permissions.
8
+ oauth_token:
9
+
10
+ # for testing the OAuth class
11
+ # baseline app
12
+ oauth_test_data:
13
+ # You must supply this value yourself, since they will expire.
14
+ code:
15
+ # easiest way to get session keys: use multiple test accounts with the Javascript login at http://oauth.twoalex.com
16
+ session_key:
17
+ multiple_session_keys:
18
+ -
19
+ -
20
+
21
+ # These values will work out of the box
22
+ app_id: 119908831367602
23
+ secret: e45e55a333eec232d4206d2703de1307
24
+ callback_url: http://oauth.twoalex.com/
25
+ app_access_token: 119908831367602|o3wswWQ88LYjEC9-ukR_gjRIOMw.
26
+ raw_token_string: "access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.&expires=6621"
27
+ raw_offline_access_token_string: access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.
28
+ valid_cookies:
29
+ # note: the tests stub the time class so these default cookies are always valid (if you're using the default app)
30
+ # if not you may want to remove the stubbing to test expiration
31
+ fbs_119908831367602: '"access_token=119908831367602|2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623|CMpi0AYbn03Oukzv94AUha2qbO4.&expires=1273363200&secret=lT_9zm5r5IbJ6Aa5O54nFw__&session_key=2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623&sig=9515e93113921f9476a4efbdd4a3c746&uid=2905623"'
32
+ expired_cookies:
33
+ fbs_119908831367602: '"access_token=119908831367602|2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623|yVt5WH_S6J5p3gFa5_5lBzckhws.&expires=1273287600&secret=V_E79ovQnXqxGctFuC_n5A__&session_key=2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623&sig=eeef60838c0c800258d89b7e6ddddddb&uid=2905623"'
34
+ offline_access_cookies:
35
+ # note: I've revoked the offline access for security reasons, so you can't make calls against this :)
36
+ fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
37
+
38
+ subscription_test_data:
39
+ subscription_path: http://oauth.twoalex.com/subscriptions
40
+ verify_token: "myverificationtoken|1f54545d5f722733e17faae15377928f"
41
+ challenge_data:
42
+ "hub.challenge": "1290024882"
43
+ "hub.verify_token": "myverificationtoken|1f54545d5f722733e17faae15377928f"
44
+ "hub.mode": "subscribe"