rest-graph 1.4.6 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +67 -0
- data/CONTRIBUTORS +1 -0
- data/Gemfile +9 -1
- data/Gemfile.lock +13 -3
- data/README +10 -3
- data/README.rdoc +10 -3
- data/Rakefile +5 -2
- data/example/{rails → rails2}/README +0 -0
- data/example/{rails → rails2}/Rakefile +0 -0
- data/example/{rails → rails2}/app/controllers/application_controller.rb +4 -0
- data/example/{rails → rails2}/config/boot.rb +6 -2
- data/example/{rails → rails2}/config/environment.rb +2 -2
- data/example/{rails → rails2}/config/environments/development.rb +0 -0
- data/example/{rails → rails2}/config/environments/production.rb +0 -0
- data/example/{rails → rails2}/config/environments/test.rb +0 -0
- data/example/{rails → rails2}/config/initializers/cookie_verification_secret.rb +0 -0
- data/example/{rails → rails2}/config/initializers/new_rails_defaults.rb +0 -0
- data/example/{rails → rails2}/config/initializers/session_store.rb +1 -1
- data/example/{rails → rails2}/config/rest-graph.yaml +0 -0
- data/example/{rails → rails2}/config/routes.rb +0 -0
- data/example/{rails → rails2}/log +0 -0
- data/example/{rails → rails2}/script/console +0 -0
- data/example/{rails → rails2}/script/server +0 -0
- data/example/{rails → rails2}/test/functional/application_controller_test.rb +10 -1
- data/example/{rails → rails2}/test/test_helper.rb +0 -0
- data/example/{rails → rails2}/test/unit/rails_util_test.rb +0 -0
- data/lib/rest-graph.rb +150 -49
- data/lib/rest-graph/rails_util.rb +31 -27
- data/lib/rest-graph/version.rb +1 -1
- data/rest-graph.gemspec +16 -13
- data/test/test_api.rb +1 -1
- data/test/test_error.rb +51 -0
- data/test/test_handler.rb +4 -2
- data/test/test_misc.rb +29 -0
- data/test/test_old.rb +33 -0
- data/test/test_page.rb +54 -0
- data/test/test_parse.rb +7 -0
- data/test/test_rest-graph.rb +10 -0
- metadata +69 -50
- data/test/test_access_token.rb +0 -26
data/CHANGES
CHANGED
@@ -1,5 +1,72 @@
|
|
1
1
|
= rest-graph changes history
|
2
2
|
|
3
|
+
== rest-graph 1.5.0 -- 2010-10-11
|
4
|
+
|
5
|
+
* [RestGraph] Make sure RestGraph::Error#message is string, that way,
|
6
|
+
irb could print out error message correctly. Introduced
|
7
|
+
RestGraph::Error#error for original error hash. Thanks Bluce.
|
8
|
+
|
9
|
+
* [RestGraph] Make RestGraph#inspect honor default attributes, see:
|
10
|
+
http://groups.google.com/group/rest-graph/browse_thread/thread/7ad5c81fbb0334e8
|
11
|
+
|
12
|
+
* [RestGraph] Introduced RestGraph::Error::AccessToken,
|
13
|
+
RestGraph::Error::InvalidAccessToken,
|
14
|
+
RestGraph::Error::MissingAccessToken.
|
15
|
+
RestGraph::Error::AccessToken is the parent of the others,
|
16
|
+
and RestGraph::Error is the parent of all above.
|
17
|
+
|
18
|
+
* [RestGraph] Add RestGraph#next_page and RestGraph#prev_page.
|
19
|
+
To get next page for a result from Facebook, example:
|
20
|
+
|
21
|
+
rg.next_page(rg.get('me/feed'))
|
22
|
+
|
23
|
+
* [RestGraph] Add RestGraph#for_pages that would crawl from page 1 to
|
24
|
+
a number of pages you specified. For example, this might
|
25
|
+
crawl down all the feeds:
|
26
|
+
|
27
|
+
rg.for_pages(rg.get('me/feed'), 1000)
|
28
|
+
|
29
|
+
* [RestGraph] Added RestGraph#secret_old_rest, see:
|
30
|
+
http://www.nivas.hr/blog/2010/09/03/facebook-php-sdk-access-token-signing-bug/
|
31
|
+
|
32
|
+
If you're getting this error from calling old_rest:
|
33
|
+
|
34
|
+
The method you are calling or the FQL table you are querying cannot be
|
35
|
+
called using a session secret or by a desktop application.
|
36
|
+
|
37
|
+
Then try secret_old_rest instead. The problem is that the access_token
|
38
|
+
should be formatted by "#{app_id}|#{secret}" instead of the usual one.
|
39
|
+
|
40
|
+
* [RestGraph] Added RestGraph#strict.
|
41
|
+
In some API, e.g. admin.getAppProperties, Facebook returns
|
42
|
+
broken JSON, which is double encoded, and is not a well-formed
|
43
|
+
JSON. That case, we'll need to double parse the JSON to get
|
44
|
+
the correct result. For example, Facebook might return this:
|
45
|
+
|
46
|
+
"{\"app_id\":\"123\"}"
|
47
|
+
|
48
|
+
instead of the correct one:
|
49
|
+
|
50
|
+
{"app_id":"123"}
|
51
|
+
|
52
|
+
P.S. This doesn't matter for people who don't use :auto_decode.
|
53
|
+
So under non-strict mode, which is the default, rest-graph
|
54
|
+
would handle this for you.
|
55
|
+
|
56
|
+
* [RestGraph] Fallback to ruby-hmac gem if we have an old openssl lib when
|
57
|
+
parsing signed_request which requires HMAC SHA256.
|
58
|
+
(e.g. Mac OS 10.5) Thanks Barnabas Debreczeni!
|
59
|
+
|
60
|
+
* [RailsUtil] Only rescue RestGraph::Error::AccessToken in controller,
|
61
|
+
make other errors raise through.
|
62
|
+
|
63
|
+
* [RailsUtil] Remove bad fbs in cookies when doing new authorization.
|
64
|
+
* [RailsUtil] Make signed_request and session higher priority than
|
65
|
+
fbs in cookies. Since Facebook is not deleting bad fbs,
|
66
|
+
I guess? Thanks Barnabas Debreczeni!
|
67
|
+
|
68
|
+
* [RailsUtil] URI.encode before URI.parse for broken URL Facebook passed.
|
69
|
+
|
3
70
|
== rest-graph 1.4.6 -- 2010-09-01
|
4
71
|
|
5
72
|
* [RestGraph] Now it will try to pick yajl-ruby or json gem from memory first,
|
data/CONTRIBUTORS
CHANGED
data/Gemfile
CHANGED
@@ -4,11 +4,19 @@ source 'http://rubygems.org'
|
|
4
4
|
gem 'rest-client'
|
5
5
|
|
6
6
|
group :test do
|
7
|
+
# optional
|
8
|
+
gem 'rack'
|
9
|
+
|
10
|
+
# optional, pick one json backend
|
7
11
|
gem 'yajl-ruby'
|
8
12
|
gem 'json'
|
9
13
|
gem 'json_pure'
|
10
14
|
|
11
|
-
|
15
|
+
# falls back from openssl
|
16
|
+
gem 'ruby-hmac'
|
17
|
+
|
18
|
+
# for testing
|
19
|
+
gem 'bones'
|
12
20
|
gem 'rr'
|
13
21
|
gem 'webmock'
|
14
22
|
gem 'bacon'
|
data/Gemfile.lock
CHANGED
@@ -1,30 +1,40 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.2.
|
4
|
+
addressable (2.2.1)
|
5
5
|
bacon (1.1.0)
|
6
|
+
bones (3.4.7)
|
7
|
+
little-plugger (>= 1.1.2)
|
8
|
+
loquacious (>= 1.6.4)
|
9
|
+
rake (>= 0.8.7)
|
6
10
|
crack (0.1.8)
|
7
11
|
json (1.4.6)
|
8
12
|
json_pure (1.4.6)
|
13
|
+
little-plugger (1.1.2)
|
14
|
+
loquacious (1.7.0)
|
9
15
|
mime-types (1.16)
|
10
16
|
rack (1.2.1)
|
17
|
+
rake (0.8.7)
|
11
18
|
rest-client (1.6.1)
|
12
19
|
mime-types (>= 1.16)
|
13
20
|
rr (1.0.0)
|
14
|
-
|
21
|
+
ruby-hmac (0.4.0)
|
22
|
+
webmock (1.3.5)
|
15
23
|
addressable (>= 2.1.1)
|
16
24
|
crack (>= 0.1.7)
|
17
|
-
yajl-ruby (0.7.
|
25
|
+
yajl-ruby (0.7.8)
|
18
26
|
|
19
27
|
PLATFORMS
|
20
28
|
ruby
|
21
29
|
|
22
30
|
DEPENDENCIES
|
23
31
|
bacon
|
32
|
+
bones
|
24
33
|
json
|
25
34
|
json_pure
|
26
35
|
rack
|
27
36
|
rest-client
|
28
37
|
rr
|
38
|
+
ruby-hmac
|
29
39
|
webmock
|
30
40
|
yajl-ruby
|
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= rest-graph 1.
|
1
|
+
= rest-graph 1.5.0
|
2
2
|
by Cardinal Blue ( http://cardinalblue.com )
|
3
3
|
|
4
4
|
== LINKS:
|
@@ -20,8 +20,9 @@ A super simple Facebook Open Graph API client
|
|
20
20
|
|
21
21
|
== REQUIREMENTS:
|
22
22
|
|
23
|
-
* Tested with MRI 1.8.7 and 1.9.2 and Rubinius
|
23
|
+
* Tested with MRI 1.8.7 and 1.9.2 and Rubinius 1.1.0
|
24
24
|
* gem install rest-client
|
25
|
+
* gem install yajl-ruby (optional)
|
25
26
|
* gem install json (optional)
|
26
27
|
* gem install json_pure (optional)
|
27
28
|
* gem install rack (optional, to parse access_token in HTTP_COOKIE)
|
@@ -97,6 +98,7 @@ Here are ALL the available options for new instance of RestGraph.
|
|
97
98
|
|
98
99
|
rg = RestGraph.new(
|
99
100
|
:access_token => TOKEN , # default nil
|
101
|
+
:strict => false , # this is the default
|
100
102
|
:graph_server => 'https://graph.facebook.com/', # this is the default
|
101
103
|
:old_server => 'https://api.facebook.com/' , # this is the default
|
102
104
|
:accept => 'text/javascript' , # this is the default
|
@@ -159,7 +161,7 @@ options for RestGraph instance are also valid options for rest_graph_setup.
|
|
159
161
|
# site or iframe canvas application, you might want
|
160
162
|
# to just use the Rails (or other framework) session.
|
161
163
|
|
162
|
-
===
|
164
|
+
=== Alternate ways to setup RestGraph:
|
163
165
|
|
164
166
|
1. Set upon RestGraph object creation:
|
165
167
|
|
@@ -239,6 +241,11 @@ Call functionality from Facebook's old REST API:
|
|
239
241
|
# if Facebook is not returning a proper JSON
|
240
242
|
# response. Otherwise, this could be omitted.
|
241
243
|
|
244
|
+
# Some Old Rest API requires a special access token with app secret
|
245
|
+
# inside of it. For those methods, use secret_old_rest instead of the
|
246
|
+
# usual old_rest with common access token.
|
247
|
+
rg.secret_old_rest('admin.getAppProperties', :properties => 'app_id')
|
248
|
+
|
242
249
|
=== Utility Methods:
|
243
250
|
|
244
251
|
==== parse_xxxx
|
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= rest-graph 1.
|
1
|
+
= rest-graph 1.5.0
|
2
2
|
by Cardinal Blue ( http://cardinalblue.com )
|
3
3
|
|
4
4
|
== LINKS:
|
@@ -20,8 +20,9 @@ A super simple Facebook Open Graph API client
|
|
20
20
|
|
21
21
|
== REQUIREMENTS:
|
22
22
|
|
23
|
-
* Tested with MRI 1.8.7 and 1.9.2 and Rubinius
|
23
|
+
* Tested with MRI 1.8.7 and 1.9.2 and Rubinius 1.1.0
|
24
24
|
* gem install rest-client
|
25
|
+
* gem install yajl-ruby (optional)
|
25
26
|
* gem install json (optional)
|
26
27
|
* gem install json_pure (optional)
|
27
28
|
* gem install rack (optional, to parse access_token in HTTP_COOKIE)
|
@@ -97,6 +98,7 @@ Here are ALL the available options for new instance of RestGraph.
|
|
97
98
|
|
98
99
|
rg = RestGraph.new(
|
99
100
|
:access_token => TOKEN , # default nil
|
101
|
+
:strict => false , # this is the default
|
100
102
|
:graph_server => 'https://graph.facebook.com/', # this is the default
|
101
103
|
:old_server => 'https://api.facebook.com/' , # this is the default
|
102
104
|
:accept => 'text/javascript' , # this is the default
|
@@ -159,7 +161,7 @@ options for RestGraph instance are also valid options for rest_graph_setup.
|
|
159
161
|
# site or iframe canvas application, you might want
|
160
162
|
# to just use the Rails (or other framework) session.
|
161
163
|
|
162
|
-
===
|
164
|
+
=== Alternate ways to setup RestGraph:
|
163
165
|
|
164
166
|
1. Set upon RestGraph object creation:
|
165
167
|
|
@@ -239,6 +241,11 @@ Call functionality from Facebook's old REST API:
|
|
239
241
|
# if Facebook is not returning a proper JSON
|
240
242
|
# response. Otherwise, this could be omitted.
|
241
243
|
|
244
|
+
# Some Old Rest API requires a special access token with app secret
|
245
|
+
# inside of it. For those methods, use secret_old_rest instead of the
|
246
|
+
# usual old_rest with common access token.
|
247
|
+
rg.secret_old_rest('admin.getAppProperties', :properties => 'app_id')
|
248
|
+
|
242
249
|
=== Utility Methods:
|
243
250
|
|
244
251
|
==== parse_xxxx
|
data/Rakefile
CHANGED
@@ -17,11 +17,14 @@ Bones{
|
|
17
17
|
|
18
18
|
depend_on 'rest-client'
|
19
19
|
|
20
|
+
depend_on 'rack' , :development => true
|
21
|
+
|
20
22
|
depend_on 'yajl-ruby', :development => true
|
21
23
|
depend_on 'json' , :development => true
|
22
24
|
depend_on 'json_pure', :development => true
|
23
25
|
|
24
|
-
depend_on '
|
26
|
+
depend_on 'ruby-hmac', :development => true
|
27
|
+
|
25
28
|
depend_on 'rr' , :development => true
|
26
29
|
depend_on 'webmock' , :development => true
|
27
30
|
depend_on 'bacon' , :development => true
|
@@ -47,7 +50,7 @@ end
|
|
47
50
|
|
48
51
|
desc 'Run example tests'
|
49
52
|
task 'test:example' => ['gem:install'] do
|
50
|
-
sh "cd example/
|
53
|
+
sh "cd example/rails2; #{Gem.ruby} -S rake test"
|
51
54
|
end
|
52
55
|
|
53
56
|
desc 'Run all tests'
|
File without changes
|
File without changes
|
@@ -50,6 +50,10 @@ class ApplicationController < ActionController::Base
|
|
50
50
|
render :text => Rails.cache.read(Digest::MD5.hexdigest(url))
|
51
51
|
end
|
52
52
|
|
53
|
+
def error
|
54
|
+
raise RestGraph::Error.new("don't rescue me")
|
55
|
+
end
|
56
|
+
|
53
57
|
private
|
54
58
|
def filter_common
|
55
59
|
rest_graph_setup(:auto_authorize => true, :canvas => '')
|
@@ -62,8 +62,12 @@ module Rails
|
|
62
62
|
gem 'rails'
|
63
63
|
end
|
64
64
|
rescue Gem::LoadError => load_error
|
65
|
-
|
66
|
-
|
65
|
+
if load_error.message =~ /Could not find RubyGem rails/
|
66
|
+
STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
67
|
+
exit 1
|
68
|
+
else
|
69
|
+
raise
|
70
|
+
end
|
67
71
|
end
|
68
72
|
|
69
73
|
class << self
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Be sure to restart your server when you modify this file
|
2
2
|
|
3
3
|
# Specifies gem version of Rails to use when vendor/rails is not present
|
4
|
-
RAILS_GEM_VERSION = '2.3.
|
4
|
+
RAILS_GEM_VERSION = '2.3.9' unless defined? RAILS_GEM_VERSION
|
5
5
|
|
6
6
|
# Bootstrap the Rails environment, frameworks, and default configuration
|
7
7
|
require File.join(File.dirname(__FILE__), 'boot')
|
@@ -12,7 +12,7 @@ Rails::Initializer.run do |config|
|
|
12
12
|
# -- all .rb files in that directory are automatically loaded.
|
13
13
|
|
14
14
|
# Add additional load paths for your own custom dirs
|
15
|
-
# config.
|
15
|
+
# config.autoload_paths += %W( #{RAILS_ROOT}/extras )
|
16
16
|
|
17
17
|
# Specify gems that this application depends on and have them installed with rake gems:install
|
18
18
|
# config.gem "bj"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# Make sure the secret is at least 30 characters and all random,
|
6
6
|
# no regular words or you'll be exposed to dictionary attacks.
|
7
7
|
ActionController::Base.session = {
|
8
|
-
:key => '
|
8
|
+
:key => '_rails2_session',
|
9
9
|
:secret => 'c99a19ce0dc4ed1809e32b6b43bd9229c3a504c456230119dd445fdcb63c0ce06b436f1bf1eace27ebbe0da6041ff2b65cbb4ae4beadc3077e3e6ae07ea75118'
|
10
10
|
}
|
11
11
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -8,8 +8,11 @@ class ApplicationControllerTest < ActionController::TestCase
|
|
8
8
|
include WebMock
|
9
9
|
|
10
10
|
def setup
|
11
|
+
body = rand(2) == 0 ? '{"error":{"type":"OAuthException"}}' :
|
12
|
+
'{"error_code":104}'
|
13
|
+
|
11
14
|
stub_request(:get, 'https://graph.facebook.com/me').
|
12
|
-
to_return(:body =>
|
15
|
+
to_return(:body => body)
|
13
16
|
end
|
14
17
|
|
15
18
|
def teardown
|
@@ -131,4 +134,10 @@ class ApplicationControllerTest < ActionController::TestCase
|
|
131
134
|
assert_response :success
|
132
135
|
assert_equal '["yeti"]', @response.body
|
133
136
|
end
|
137
|
+
|
138
|
+
def test_error
|
139
|
+
get(:error)
|
140
|
+
rescue => e
|
141
|
+
assert_equal RestGraph::Error, e.class
|
142
|
+
end
|
134
143
|
end
|
File without changes
|
File without changes
|
data/lib/rest-graph.rb
CHANGED
@@ -14,15 +14,105 @@ begin
|
|
14
14
|
rescue LoadError; end
|
15
15
|
|
16
16
|
# the data structure used in RestGraph
|
17
|
-
RestGraphStruct = Struct.new(:auto_decode,
|
17
|
+
RestGraphStruct = Struct.new(:auto_decode, :strict,
|
18
18
|
:graph_server, :old_server,
|
19
19
|
:accept, :lang,
|
20
20
|
:app_id, :secret,
|
21
21
|
:data, :cache,
|
22
22
|
:error_handler,
|
23
|
-
:log_handler) unless defined?(RestGraphStruct)
|
23
|
+
:log_handler) unless defined?(::RestGraphStruct)
|
24
24
|
|
25
25
|
class RestGraph < RestGraphStruct
|
26
|
+
EventStruct = Struct.new(:duration, :url) unless
|
27
|
+
defined?(::RestGraph::EventStruct)
|
28
|
+
|
29
|
+
Attributes = RestGraphStruct.members.map(&:to_sym) unless
|
30
|
+
defined?(::RestGraph::Attributes)
|
31
|
+
|
32
|
+
class Event < EventStruct; end
|
33
|
+
class Event::Requested < Event; end
|
34
|
+
class Event::CacheHit < Event; end
|
35
|
+
|
36
|
+
class Error < RuntimeError
|
37
|
+
class AccessToken < Error; end
|
38
|
+
class InvalidAccessToken < AccessToken; end
|
39
|
+
class MissingAccessToken < AccessToken; end
|
40
|
+
|
41
|
+
attr_reader :error
|
42
|
+
def initialize error
|
43
|
+
@error = error
|
44
|
+
super(error.inspect)
|
45
|
+
end
|
46
|
+
|
47
|
+
module Util
|
48
|
+
extend self
|
49
|
+
def parse error
|
50
|
+
return Error.new(error) unless error.kind_of?(Hash)
|
51
|
+
if invalid_token?(error)
|
52
|
+
InvalidAccessToken.new(error)
|
53
|
+
elsif missing_token?(error)
|
54
|
+
MissingAccessToken.new(error)
|
55
|
+
else
|
56
|
+
Error.new(error)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def invalid_token? error
|
61
|
+
(%w[OAuthInvalidTokenException
|
62
|
+
OAuthException].include?((error['error'] || {})['type'])) ||
|
63
|
+
(error['error_code'] == 190) # Invalid OAuth 2.0 Access Token
|
64
|
+
end
|
65
|
+
|
66
|
+
def missing_token? error
|
67
|
+
(error['error'] || {})['message'] =~ /^An active access token/ ||
|
68
|
+
(error['error_code'] == 104) # Requires valid signature
|
69
|
+
end
|
70
|
+
end
|
71
|
+
extend Util
|
72
|
+
end
|
73
|
+
|
74
|
+
# honor default attributes
|
75
|
+
Attributes.each{ |name|
|
76
|
+
module_eval <<-RUBY
|
77
|
+
def #{name}
|
78
|
+
(r = super).nil? ? (self.#{name} = self.class.default_#{name}) : r
|
79
|
+
end
|
80
|
+
RUBY
|
81
|
+
}
|
82
|
+
|
83
|
+
# setup defaults
|
84
|
+
module DefaultAttributes
|
85
|
+
extend self
|
86
|
+
def default_auto_decode ; true ; end
|
87
|
+
def default_strict ; false ; end
|
88
|
+
def default_graph_server; 'https://graph.facebook.com/'; end
|
89
|
+
def default_old_server ; 'https://api.facebook.com/' ; end
|
90
|
+
def default_accept ; 'text/javascript' ; end
|
91
|
+
def default_lang ; 'en-us' ; end
|
92
|
+
def default_app_id ; nil ; end
|
93
|
+
def default_secret ; nil ; end
|
94
|
+
def default_data ; {} ; end
|
95
|
+
def default_cache ; nil ; end
|
96
|
+
def default_error_handler
|
97
|
+
lambda{ |error| raise ::RestGraph::Error.parse(error) }
|
98
|
+
end
|
99
|
+
def default_log_handler
|
100
|
+
lambda{ |event| }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
extend DefaultAttributes
|
104
|
+
|
105
|
+
# Fallback to ruby-hmac gem in case system openssl
|
106
|
+
# lib doesn't support SHA256 (OSX 10.5)
|
107
|
+
def self.hmac_sha256 key, data
|
108
|
+
# for ruby version >= 1.8.7, we can simply pass sha256,
|
109
|
+
# instead of OpenSSL::Digest::Digest.new('sha256')
|
110
|
+
# i'll go back to original implementation once all old systems died
|
111
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, data)
|
112
|
+
rescue RuntimeError
|
113
|
+
require 'hmac-sha2'
|
114
|
+
HMAC::SHA256.digest(key, data)
|
115
|
+
end
|
26
116
|
|
27
117
|
# begin json backend adapter
|
28
118
|
module YajlRuby
|
@@ -54,7 +144,6 @@ class RestGraph < RestGraphStruct
|
|
54
144
|
def self.extended mod
|
55
145
|
mod.const_set(:ParseError, Gsub::ParseError)
|
56
146
|
end
|
57
|
-
|
58
147
|
# only works for flat hash
|
59
148
|
def json_encode hash
|
60
149
|
middle = hash.inject([]){ |r, (k, v)|
|
@@ -62,7 +151,7 @@ class RestGraph < RestGraphStruct
|
|
62
151
|
}.join(',')
|
63
152
|
"{#{middle}}"
|
64
153
|
end
|
65
|
-
def json_decode
|
154
|
+
def json_decode json
|
66
155
|
raise NotImplementedError.new(
|
67
156
|
'You need to install either yajl-ruby, json, or json_pure gem')
|
68
157
|
end
|
@@ -87,46 +176,9 @@ class RestGraph < RestGraphStruct
|
|
87
176
|
select_json!(true)
|
88
177
|
end
|
89
178
|
end
|
90
|
-
select_json!
|
179
|
+
select_json! unless respond_to?(:json_decode)
|
91
180
|
# end json backend adapter
|
92
181
|
|
93
|
-
class Error < RuntimeError; end
|
94
|
-
class Event < Struct.new(:duration, :url); end
|
95
|
-
class Event::Requested < Event; end
|
96
|
-
class Event::CacheHit < Event; end
|
97
|
-
|
98
|
-
Attributes = RestGraphStruct.members.map(&:to_sym)
|
99
|
-
|
100
|
-
# honor default attributes
|
101
|
-
Attributes.each{ |name|
|
102
|
-
module_eval <<-RUBY
|
103
|
-
def #{name}
|
104
|
-
(r = super).nil? ? (self.#{name} = self.class.default_#{name}) : r
|
105
|
-
end
|
106
|
-
RUBY
|
107
|
-
}
|
108
|
-
|
109
|
-
# setup defaults
|
110
|
-
module DefaultAttributes
|
111
|
-
extend self
|
112
|
-
def default_auto_decode ; true ; end
|
113
|
-
def default_graph_server; 'https://graph.facebook.com/'; end
|
114
|
-
def default_old_server ; 'https://api.facebook.com/' ; end
|
115
|
-
def default_accept ; 'text/javascript' ; end
|
116
|
-
def default_lang ; 'en-us' ; end
|
117
|
-
def default_app_id ; nil ; end
|
118
|
-
def default_secret ; nil ; end
|
119
|
-
def default_data ; {} ; end
|
120
|
-
def default_cache ; nil ; end
|
121
|
-
def default_error_handler
|
122
|
-
lambda{ |error| raise ::RestGraph::Error.new(error) }
|
123
|
-
end
|
124
|
-
def default_log_handler
|
125
|
-
lambda{ |event| }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
extend DefaultAttributes
|
129
|
-
|
130
182
|
|
131
183
|
|
132
184
|
|
@@ -151,6 +203,10 @@ class RestGraph < RestGraphStruct
|
|
151
203
|
!!access_token
|
152
204
|
end
|
153
205
|
|
206
|
+
def secret_access_token
|
207
|
+
"#{app_id}|#{secret}"
|
208
|
+
end
|
209
|
+
|
154
210
|
def lighten!
|
155
211
|
[:cache, :error_handler, :log_handler].each{ |obj| send("#{obj}=", nil) }
|
156
212
|
self
|
@@ -160,6 +216,13 @@ class RestGraph < RestGraphStruct
|
|
160
216
|
dup.lighten!
|
161
217
|
end
|
162
218
|
|
219
|
+
def inspect
|
220
|
+
super.gsub(/(\w+)=([^,]+)/){ |match|
|
221
|
+
value = $2 == 'nil' ? self.class.send("default_#{$1}").inspect : $2
|
222
|
+
"#{$1}=#{value}"
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
163
226
|
|
164
227
|
|
165
228
|
|
@@ -186,6 +249,26 @@ class RestGraph < RestGraphStruct
|
|
186
249
|
request(:put , url(path, query, graph_server), opts, payload)
|
187
250
|
end
|
188
251
|
|
252
|
+
def next_page hash, opts={}
|
253
|
+
return unless hash['paging'].kind_of?(Hash) && hash['paging']['next']
|
254
|
+
request(:get , hash['paging']['next'] , opts)
|
255
|
+
end
|
256
|
+
|
257
|
+
def prev_page hash, opts={}
|
258
|
+
return unless hash['paging'].kind_of?(Hash) && hash['paging']['previous']
|
259
|
+
request(:get , hash['paging']['previous'] , opts)
|
260
|
+
end
|
261
|
+
alias_method :previous_page, :prev_page
|
262
|
+
|
263
|
+
def for_pages hash, pages=1, kind=:next_page, opts={}
|
264
|
+
return hash if pages <= 1
|
265
|
+
if result = send(kind, hash, opts)
|
266
|
+
for_pages(merge_data(result, hash), pages - 1, kind, opts)
|
267
|
+
else
|
268
|
+
hash
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
189
272
|
|
190
273
|
|
191
274
|
|
@@ -225,7 +308,7 @@ class RestGraph < RestGraphStruct
|
|
225
308
|
"#{str.tr('-_', '+/')}==".unpack('m').first
|
226
309
|
}
|
227
310
|
self.data = self.class.json_decode(json) if
|
228
|
-
secret &&
|
311
|
+
secret && self.class.hmac_sha256(secret, json_encoded) == sig
|
229
312
|
rescue ParseError
|
230
313
|
end
|
231
314
|
|
@@ -260,6 +343,11 @@ class RestGraph < RestGraphStruct
|
|
260
343
|
opts)
|
261
344
|
end
|
262
345
|
|
346
|
+
def secret_old_rest path, query={}, opts={}
|
347
|
+
old_rest(path, {:access_token => secret_access_token}.merge(query), opts)
|
348
|
+
end
|
349
|
+
alias_method :broken_old_rest, :secret_old_rest
|
350
|
+
|
263
351
|
def exchange_sessions opts={}
|
264
352
|
query = {:client_id => app_id, :client_secret => secret,
|
265
353
|
:type => 'client_cred'}.merge(opts)
|
@@ -282,10 +370,9 @@ class RestGraph < RestGraphStruct
|
|
282
370
|
private
|
283
371
|
def request meth, uri, opts={}, payload=nil
|
284
372
|
start_time = Time.now
|
285
|
-
post_request(cache_get(uri) || fetch(meth, uri, payload),
|
286
|
-
opts[:suppress_decode])
|
373
|
+
post_request(cache_get(uri) || fetch(meth, uri, payload), opts)
|
287
374
|
rescue RestClient::Exception => e
|
288
|
-
post_request(e.http_body, opts
|
375
|
+
post_request(e.http_body, opts)
|
289
376
|
ensure
|
290
377
|
log_handler.call(Event::Requested.new(Time.now - start_time, uri))
|
291
378
|
end
|
@@ -304,9 +391,14 @@ class RestGraph < RestGraphStruct
|
|
304
391
|
headers
|
305
392
|
end
|
306
393
|
|
307
|
-
def post_request result,
|
308
|
-
if auto_decode && !suppress_decode
|
309
|
-
|
394
|
+
def post_request result, opts={}
|
395
|
+
if auto_decode && !opts[:suppress_decode]
|
396
|
+
decoded = self.class.json_decode("[#{result}]").first
|
397
|
+
check_error(if strict || !decoded.kind_of?(String)
|
398
|
+
decoded
|
399
|
+
else
|
400
|
+
self.class.json_decode(decoded)
|
401
|
+
end)
|
310
402
|
else
|
311
403
|
result
|
312
404
|
end
|
@@ -355,4 +447,13 @@ class RestGraph < RestGraphStruct
|
|
355
447
|
cache[cache_key(uri)] = result if cache && meth == :get
|
356
448
|
}
|
357
449
|
end
|
450
|
+
|
451
|
+
def merge_data lhs, rhs
|
452
|
+
[lhs, rhs].each{ |hash|
|
453
|
+
return rhs.reject{ |k, v| k == 'paging' } if
|
454
|
+
!hash.kind_of?(Hash) || !hash['data'].kind_of?(Array)
|
455
|
+
}
|
456
|
+
lhs['data'].unshift(*rhs['data'])
|
457
|
+
lhs
|
458
|
+
end
|
358
459
|
end
|