tyler_koala 1.2.0beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +185 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE +22 -0
  7. data/Manifest +39 -0
  8. data/Rakefile +16 -0
  9. data/autotest/discover.rb +1 -0
  10. data/koala.gemspec +50 -0
  11. data/lib/koala.rb +119 -0
  12. data/lib/koala/batch_operation.rb +74 -0
  13. data/lib/koala/graph_api.rb +281 -0
  14. data/lib/koala/graph_batch_api.rb +87 -0
  15. data/lib/koala/graph_collection.rb +54 -0
  16. data/lib/koala/http_service.rb +161 -0
  17. data/lib/koala/oauth.rb +181 -0
  18. data/lib/koala/realtime_updates.rb +89 -0
  19. data/lib/koala/rest_api.rb +95 -0
  20. data/lib/koala/test_users.rb +102 -0
  21. data/lib/koala/uploadable_io.rb +180 -0
  22. data/lib/koala/utils.rb +7 -0
  23. data/readme.md +160 -0
  24. data/spec/cases/api_base_spec.rb +101 -0
  25. data/spec/cases/error_spec.rb +30 -0
  26. data/spec/cases/graph_and_rest_api_spec.rb +48 -0
  27. data/spec/cases/graph_api_batch_spec.rb +600 -0
  28. data/spec/cases/graph_api_spec.rb +42 -0
  29. data/spec/cases/http_service_spec.rb +420 -0
  30. data/spec/cases/koala_spec.rb +21 -0
  31. data/spec/cases/oauth_spec.rb +428 -0
  32. data/spec/cases/realtime_updates_spec.rb +198 -0
  33. data/spec/cases/rest_api_spec.rb +41 -0
  34. data/spec/cases/test_users_spec.rb +281 -0
  35. data/spec/cases/uploadable_io_spec.rb +206 -0
  36. data/spec/cases/utils_spec.rb +8 -0
  37. data/spec/fixtures/beach.jpg +0 -0
  38. data/spec/fixtures/cat.m4v +0 -0
  39. data/spec/fixtures/facebook_data.yml +61 -0
  40. data/spec/fixtures/mock_facebook_responses.yml +439 -0
  41. data/spec/spec_helper.rb +43 -0
  42. data/spec/support/graph_api_shared_examples.rb +502 -0
  43. data/spec/support/json_testing_fix.rb +42 -0
  44. data/spec/support/koala_test.rb +163 -0
  45. data/spec/support/mock_http_service.rb +98 -0
  46. data/spec/support/ordered_hash.rb +205 -0
  47. data/spec/support/rest_api_shared_examples.rb +285 -0
  48. data/spec/support/uploadable_io_shared_examples.rb +70 -0
  49. metadata +221 -0
@@ -0,0 +1,161 @@
1
+ require 'faraday'
2
+ require 'faraday_stack'
3
+
4
+ module Koala
5
+ class Response
6
+ attr_reader :status, :body, :headers
7
+ def initialize(status, body, headers)
8
+ @status = status
9
+ @body = body
10
+ @headers = headers
11
+ end
12
+ end
13
+
14
+ module HTTPService
15
+ # common functionality for all HTTP services
16
+
17
+ class << self
18
+ attr_accessor :faraday_middleware, :faraday_options
19
+ end
20
+
21
+ @faraday_options ||= {}
22
+
23
+ DEFAULT_MIDDLEWARE = Proc.new do |builder|
24
+ builder.request :multipart
25
+ builder.request :url_encoded
26
+ builder.adapter Faraday.default_adapter
27
+ end
28
+
29
+ def self.server(options = {})
30
+ server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
31
+ server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
32
+ "#{options[:use_ssl] ? "https" : "http"}://#{options[:beta] ? "beta." : ""}#{server}"
33
+ end
34
+
35
+ def self.make_request(path, args, verb, options = {})
36
+ # if the verb isn't get or post, send it as a post argument
37
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
38
+
39
+ # turn all the keys to strings (Faraday has issues with symbols under 1.8.7) and resolve UploadableIOs
40
+ params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last.is_a?(UploadableIO) ? kv.last.to_upload_io : kv.last; hash}
41
+
42
+ # figure out our options for this request
43
+ http_options = {:params => (verb == "get" ? params : {})}.merge(faraday_options || {}).merge(process_options(options))
44
+ http_options[:use_ssl] = true if args["access_token"] # require http if there's a token
45
+
46
+ # set up our Faraday connection
47
+ # we have to manually assign params to the URL or the
48
+ conn = Faraday.new(server(http_options), http_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
49
+
50
+ response = conn.send(verb, path, (verb == "post" ? params : {}))
51
+ Koala::Response.new(response.status.to_i, response.body, response.headers)
52
+ end
53
+
54
+ def self.encode_params(param_hash)
55
+ # unfortunately, we can't use to_query because that's Rails, not Ruby
56
+ # if no hash (e.g. no auth token) return empty string
57
+ # this is used mainly by the Batch API nowadays
58
+ ((param_hash || {}).collect do |key_and_value|
59
+ key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
60
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
61
+ end).join("&")
62
+ end
63
+
64
+ # deprecations
65
+ # not elegant or compact code, but temporary
66
+
67
+ def self.always_use_ssl
68
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.faraday_options[:use_ssl]; always_use_ssl will be removed in a future version.")
69
+ faraday_options[:use_ssl]
70
+ end
71
+
72
+ def self.always_use_ssl=(value)
73
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.faraday_options[:use_ssl]; always_use_ssl will be removed in a future version.")
74
+ faraday_options[:use_ssl] = value
75
+ end
76
+
77
+ def self.timeout
78
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
79
+ faraday_options[:timeout]
80
+ end
81
+
82
+ def self.timeout=(value)
83
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
84
+ faraday_options[:timeout] = value
85
+ end
86
+
87
+ def self.timeout
88
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
89
+ faraday_options[:timeout]
90
+ end
91
+
92
+ def self.timeout=(value)
93
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.faraday_options[:timeout]; .timeout will be removed in a future version.")
94
+ faraday_options[:timeout] = value
95
+ end
96
+
97
+ def self.proxy
98
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.faraday_options[:proxy]; .proxy will be removed in a future version.")
99
+ faraday_options[:proxy]
100
+ end
101
+
102
+ def self.proxy=(value)
103
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.faraday_options[:proxy]; .proxy will be removed in a future version.")
104
+ faraday_options[:proxy] = value
105
+ end
106
+
107
+ def self.ca_path
108
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
109
+ (faraday_options[:ssl] || {})[:ca_path]
110
+ end
111
+
112
+ def self.ca_path=(value)
113
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
114
+ (faraday_options[:ssl] ||= {})[:ca_path] = value
115
+ end
116
+
117
+ def self.ca_file
118
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
119
+ (faraday_options[:ssl] || {})[:ca_file]
120
+ end
121
+
122
+ def self.ca_file=(value)
123
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.faraday_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
124
+ (faraday_options[:ssl] ||= {})[:ca_file] = value
125
+ end
126
+
127
+ def self.verify_mode
128
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.faraday_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
129
+ (faraday_options[:ssl] || {})[:verify_mode]
130
+ end
131
+
132
+ def self.verify_mode=(value)
133
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.faraday_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
134
+ (faraday_options[:ssl] ||= {})[:verify_mode] = value
135
+ end
136
+
137
+ def self.process_options(options)
138
+ if typhoeus_options = options.delete(:typhoeus_options)
139
+ Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
140
+ options = options.merge(typhoeus_options)
141
+ end
142
+
143
+ if ca_file = options.delete(:ca_file)
144
+ Koala::Utils.deprecate("http_options[:ca_file] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_file]. Support for this key will be removed in a future version.")
145
+ (options[:ssl] ||= {})[:ca_file] = ca_file
146
+ end
147
+
148
+ if ca_path = options.delete(:ca_path)
149
+ Koala::Utils.deprecate("http_options[:ca_path] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_path]. Support for this key will be removed in a future version.")
150
+ (options[:ssl] ||= {})[:ca_path] = ca_path
151
+ end
152
+
153
+ if verify_mode = options.delete(:verify_mode)
154
+ Koala::Utils.deprecate("http_options[:verify_mode] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:verify_mode]. Support for this key will be removed in a future version.")
155
+ (options[:ssl] ||= {})[:verify_mode] = verify_mode
156
+ end
157
+
158
+ options
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,181 @@
1
+ module Koala
2
+ module Facebook
3
+ class OAuth
4
+ attr_reader :app_id, :app_secret, :oauth_callback_url
5
+ def initialize(app_id, app_secret, oauth_callback_url = nil)
6
+ @app_id = app_id
7
+ @app_secret = app_secret
8
+ @oauth_callback_url = oauth_callback_url
9
+ end
10
+
11
+ def get_user_info_from_cookie(cookie_hash)
12
+ # Parses the cookie set by the official Facebook JavaScript SDK.
13
+ #
14
+ # cookies should be a Hash, like the one Rails provides
15
+ #
16
+ # If the user is logged in via Facebook, we return a dictionary with the
17
+ # keys "uid" and "access_token". The former is the user's Facebook ID,
18
+ # and the latter can be used to make authenticated requests to the Graph API.
19
+ # If the user is not logged in, we return None.
20
+ #
21
+ # Download the official Facebook JavaScript SDK at
22
+ # http://github.com/facebook/connect-js/. Read more about Facebook
23
+ # authentication at http://developers.facebook.com/docs/authentication/.
24
+
25
+ if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
26
+ # remove the opening/closing quote
27
+ fb_cookie = fb_cookie.gsub(/\"/, "")
28
+
29
+ # since we no longer get individual cookies, we have to separate out the components ourselves
30
+ components = {}
31
+ fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
32
+
33
+ # generate the signature and make sure it matches what we expect
34
+ auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
35
+ sig = Digest::MD5.hexdigest(auth_string + @app_secret)
36
+ sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
37
+ end
38
+ end
39
+ alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
40
+
41
+ def get_user_from_cookie(cookies)
42
+ if info = get_user_info_from_cookies(cookies)
43
+ string = info["uid"]
44
+ end
45
+ end
46
+ alias_method :get_user_from_cookies, :get_user_from_cookie
47
+
48
+ # URLs
49
+
50
+ def url_for_oauth_code(options = {})
51
+ # for permissions, see http://developers.facebook.com/docs/authentication/permissions
52
+ permissions = options[:permissions]
53
+ scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
54
+ display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
55
+
56
+ callback = options[:callback] || @oauth_callback_url
57
+ raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
58
+
59
+ # Creates the URL for oauth authorization for a given callback and optional set of permissions
60
+ "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
61
+ end
62
+
63
+ def url_for_access_token(code, options = {})
64
+ # Creates the URL for the token corresponding to a given code generated by Facebook
65
+ callback = options[:callback] || @oauth_callback_url
66
+ raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
67
+ "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
68
+ end
69
+
70
+ def get_access_token_info(code, options = {})
71
+ # convenience method to get a parsed token from Facebook for a given code
72
+ # should this require an OAuth callback URL?
73
+ get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
74
+ end
75
+
76
+ def get_access_token(code, options = {})
77
+ # upstream methods will throw errors if needed
78
+ if info = get_access_token_info(code, options)
79
+ string = info["access_token"]
80
+ end
81
+ end
82
+
83
+ def get_app_access_token_info(options = {})
84
+ # convenience method to get a the application's sessionless access token
85
+ get_token_from_server({:type => 'client_cred'}, true, options)
86
+ end
87
+
88
+ def get_app_access_token(options = {})
89
+ if info = get_app_access_token_info(options)
90
+ string = info["access_token"]
91
+ end
92
+ end
93
+
94
+ # Originally provided directly by Facebook, however this has changed
95
+ # as their concept of crypto changed. For historic purposes, this is their proposal:
96
+ # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
97
+ # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
98
+ # for a more accurate reference implementation strategy.
99
+ def parse_signed_request(input)
100
+ encoded_sig, encoded_envelope = input.split('.', 2)
101
+ signature = base64_url_decode(encoded_sig).unpack("H*").first
102
+ envelope = MultiJson.decode(base64_url_decode(encoded_envelope))
103
+
104
+ raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
105
+
106
+ # now see if the signature is valid (digest, key, data)
107
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope.tr("-_", "+/"))
108
+ raise 'SignedRequest: Invalid signature' if (signature != hmac)
109
+
110
+ return envelope
111
+ end
112
+
113
+ # from session keys
114
+ def get_token_info_from_session_keys(sessions, options = {})
115
+ # fetch the OAuth tokens from Facebook
116
+ response = fetch_token_string({
117
+ :type => 'client_cred',
118
+ :sessions => sessions.join(",")
119
+ }, true, "exchange_sessions", options)
120
+
121
+ # Facebook returns an empty body in certain error conditions
122
+ if response == ""
123
+ raise APIError.new({
124
+ "type" => "ArgumentError",
125
+ "message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
126
+ })
127
+ end
128
+
129
+ MultiJson.decode(response)
130
+ end
131
+
132
+ def get_tokens_from_session_keys(sessions, options = {})
133
+ # get the original hash results
134
+ results = get_token_info_from_session_keys(sessions, options)
135
+ # now recollect them as just the access tokens
136
+ results.collect { |r| r ? r["access_token"] : nil }
137
+ end
138
+
139
+ def get_token_from_session_key(session, options = {})
140
+ # convenience method for a single key
141
+ # gets the overlaoded strings automatically
142
+ get_tokens_from_session_keys([session], options)[0]
143
+ end
144
+
145
+ protected
146
+
147
+ def get_token_from_server(args, post = false, options = {})
148
+ # fetch the result from Facebook's servers
149
+ result = fetch_token_string(args, post, "access_token", options)
150
+
151
+ # if we have an error, parse the error JSON and raise an error
152
+ raise APIError.new((MultiJson.decode(result)["error"] rescue nil) || {}) if result =~ /error/
153
+
154
+ # otherwise, parse the access token
155
+ parse_access_token(result)
156
+ end
157
+
158
+ def parse_access_token(response_text)
159
+ components = response_text.split("&").inject({}) do |hash, bit|
160
+ key, value = bit.split("=")
161
+ hash.merge!(key => value)
162
+ end
163
+ components
164
+ end
165
+
166
+ def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
167
+ Koala.make_request("/oauth/#{endpoint}", {
168
+ :client_id => @app_id,
169
+ :client_secret => @app_secret
170
+ }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
171
+ end
172
+
173
+ # base 64
174
+ # directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
175
+ def base64_url_decode(str)
176
+ str += '=' * (4 - str.length.modulo(4))
177
+ Base64.decode64(str.tr('-_', '+/'))
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,89 @@
1
+ module Koala
2
+ module Facebook
3
+ module RealtimeUpdateMethods
4
+ # note: to subscribe to real-time updates, you must have an application access token
5
+
6
+ def self.included(base)
7
+ # make the attributes readable
8
+ base.class_eval do
9
+ attr_reader :api, :app_id, :app_access_token, :secret
10
+
11
+ # parses the challenge params and makes sure the call is legitimate
12
+ # returns the challenge string to be sent back to facebook if true
13
+ # returns false otherwise
14
+ # this is a class method, since you don't need to know anything about the app
15
+ # saves a potential trip fetching the app access token
16
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
17
+ if params["hub.mode"] == "subscribe" &&
18
+ # you can make sure this is legitimate through two ways
19
+ # if your store the token across the calls, you can pass in the token value
20
+ # and we'll make sure it matches
21
+ (verify_token && params["hub.verify_token"] == verify_token) ||
22
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
23
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
24
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
25
+ (verification_block && yield(params["hub.verify_token"]))
26
+ params["hub.challenge"]
27
+ else
28
+ false
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def initialize(options = {})
35
+ @app_id = options[:app_id]
36
+ @app_access_token = options[:app_access_token]
37
+ @secret = options[:secret]
38
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
39
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
40
+ end
41
+
42
+ # fetch the access token if we're provided a secret
43
+ if @secret && !@app_access_token
44
+ oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
45
+ @app_access_token = oauth.get_app_access_token
46
+ end
47
+
48
+ @graph_api = API.new(@app_access_token)
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
+ @graph_api.graph_call(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
+ @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
71
+ end
72
+
73
+ def list_subscriptions
74
+ @graph_api.graph_call(subscription_path)["data"]
75
+ end
76
+
77
+ def graph_api
78
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
+ @api
80
+ end
81
+
82
+ protected
83
+
84
+ def subscription_path
85
+ @subscription_path ||= "#{@app_id}/subscriptions"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,95 @@
1
+ module Koala
2
+ module Facebook
3
+ REST_SERVER = "api.facebook.com"
4
+
5
+ module RestAPIMethods
6
+ def fql_query(fql, args = {}, options = {})
7
+ rest_call('fql.query', args.merge(:query => fql), options)
8
+ end
9
+
10
+ def fql_multiquery(queries = {}, args = {}, options = {})
11
+ if results = rest_call('fql.multiquery', args.merge(:queries => MultiJson.encode(queries)), options)
12
+ # simplify the multiquery result format
13
+ results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
14
+ end
15
+ end
16
+
17
+ def rest_call(fb_method, args = {}, options = {}, method = "get")
18
+ options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(fb_method.to_s))
19
+
20
+ api("method/#{fb_method}", args.merge('format' => 'json'), method, options) do |response|
21
+ # check for REST API-specific errors
22
+ if response.is_a?(Hash) && response["error_code"]
23
+ raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
24
+ end
25
+ end
26
+ end
27
+
28
+ # read-only methods for which we can use API-read
29
+ # taken directly from the FB PHP library (https://github.com/facebook/php-sdk/blob/master/src/facebook.php)
30
+ READ_ONLY_METHODS = [
31
+ 'admin.getallocation',
32
+ 'admin.getappproperties',
33
+ 'admin.getbannedusers',
34
+ 'admin.getlivestreamvialink',
35
+ 'admin.getmetrics',
36
+ 'admin.getrestrictioninfo',
37
+ 'application.getpublicinfo',
38
+ 'auth.getapppublickey',
39
+ 'auth.getsession',
40
+ 'auth.getsignedpublicsessiondata',
41
+ 'comments.get',
42
+ 'connect.getunconnectedfriendscount',
43
+ 'dashboard.getactivity',
44
+ 'dashboard.getcount',
45
+ 'dashboard.getglobalnews',
46
+ 'dashboard.getnews',
47
+ 'dashboard.multigetcount',
48
+ 'dashboard.multigetnews',
49
+ 'data.getcookies',
50
+ 'events.get',
51
+ 'events.getmembers',
52
+ 'fbml.getcustomtags',
53
+ 'feed.getappfriendstories',
54
+ 'feed.getregisteredtemplatebundlebyid',
55
+ 'feed.getregisteredtemplatebundles',
56
+ 'fql.multiquery',
57
+ 'fql.query',
58
+ 'friends.arefriends',
59
+ 'friends.get',
60
+ 'friends.getappusers',
61
+ 'friends.getlists',
62
+ 'friends.getmutualfriends',
63
+ 'gifts.get',
64
+ 'groups.get',
65
+ 'groups.getmembers',
66
+ 'intl.gettranslations',
67
+ 'links.get',
68
+ 'notes.get',
69
+ 'notifications.get',
70
+ 'pages.getinfo',
71
+ 'pages.isadmin',
72
+ 'pages.isappadded',
73
+ 'pages.isfan',
74
+ 'permissions.checkavailableapiaccess',
75
+ 'permissions.checkgrantedapiaccess',
76
+ 'photos.get',
77
+ 'photos.getalbums',
78
+ 'photos.gettags',
79
+ 'profile.getinfo',
80
+ 'profile.getinfooptions',
81
+ 'stream.get',
82
+ 'stream.getcomments',
83
+ 'stream.getfilters',
84
+ 'users.getinfo',
85
+ 'users.getloggedinuser',
86
+ 'users.getstandardinfo',
87
+ 'users.hasapppermission',
88
+ 'users.isappuser',
89
+ 'users.isverified',
90
+ 'video.getuploadlimits'
91
+ ]
92
+ end
93
+
94
+ end # module Facebook
95
+ end # module Koala