fgraph 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History +5 -0
- data/README.rdoc +80 -3
- data/Rakefile +1 -1
- data/VERSION.yml +2 -2
- data/lib/fgraph/rails/fgraph_helper.rb +8 -3
- data/lib/fgraph/rails/fgraph_tag_helper.rb +90 -0
- data/lib/fgraph.rb +341 -271
- data/lib/tasks/fgraph.rake +15 -0
- data/lib/tasks/fgraph.rb +1 -0
- data/rails/init.rb +15 -0
- data/rails/install.rb +9 -0
- data/templates/fgraph.yml +16 -0
- data/test/fgraph_test.rb +77 -2
- metadata +9 -4
data/History
CHANGED
data/README.rdoc
CHANGED
@@ -9,7 +9,26 @@ Facebook Graph Ruby API implementation with Ruby magic (http://graph.facebook.co
|
|
9
9
|
== Installation
|
10
10
|
|
11
11
|
sudo gem install fgraph
|
12
|
+
|
13
|
+
== Rails Plugin Installation
|
14
|
+
|
15
|
+
Gem Plugin installation:
|
16
|
+
|
17
|
+
sudo gem install fgraph
|
12
18
|
|
19
|
+
# Edit [RAILS_ROOT]/config/environment.rb
|
20
|
+
config.gem 'fgraph', :version => ">=0.2.0"
|
21
|
+
|
22
|
+
# Edit [RAILS_ROOT]/Rakefile
|
23
|
+
require 'tasks/fgraph'
|
24
|
+
|
25
|
+
# Create fgraph.yml config in [RAILS_ROOT]/config
|
26
|
+
rake fgraph:setup
|
27
|
+
|
28
|
+
Normal Plugin Installation:
|
29
|
+
|
30
|
+
script/plugin install http://github.com/jugend/fgraph.git
|
31
|
+
|
13
32
|
=== Facebook Graph Cheat Sheet
|
14
33
|
|
15
34
|
sudo gem install cheat
|
@@ -29,6 +48,10 @@ Facebook Graph Ruby API implementation with Ruby magic (http://graph.facebook.co
|
|
29
48
|
# Page photos
|
30
49
|
FGraph.object('/cocacola/photos')
|
31
50
|
FGraph.object_photos('cocacola')
|
51
|
+
|
52
|
+
# Passing object hash as id
|
53
|
+
friend = { 'name' => 'Mark Zuckerberg', 'id' => '4'}
|
54
|
+
friend_details = FGraph.object(friend)
|
32
55
|
|
33
56
|
# Current user: https://graph.facebook.com/me?access_token=...
|
34
57
|
FGraph.me(:access_token => '...')
|
@@ -40,11 +63,27 @@ Facebook Graph Ruby API implementation with Ruby magic (http://graph.facebook.co
|
|
40
63
|
=== Multiple objects query
|
41
64
|
|
42
65
|
# Multiple users select: https://graph.facebook.com?ids=arjun,vernal
|
43
|
-
FGraph.objects('arjun', '
|
66
|
+
FGraph.objects('arjun', 'herryanto')
|
44
67
|
|
45
68
|
# Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
|
46
|
-
FGraph.objects('arjun', '
|
69
|
+
FGraph.objects('arjun', 'herryanto', :fields => 'id,name,picture')
|
70
|
+
|
71
|
+
# Passing hash objects
|
72
|
+
FGraph.objects([{:name => 'Arjun Banker', :id => 'arjun'}, {:name => 'Herryanto Siatono', :id => 'herryanto'}])
|
47
73
|
|
74
|
+
=== Collection Response
|
75
|
+
|
76
|
+
friends = FGraph.me_friends(:limit => 5, :access_token => '...')
|
77
|
+
friends.each do |friend|
|
78
|
+
puts friend['name']
|
79
|
+
puts friend['id']
|
80
|
+
end
|
81
|
+
|
82
|
+
# Other convenient methods
|
83
|
+
friends.next?
|
84
|
+
friends.next_url
|
85
|
+
friends.next_options
|
86
|
+
|
48
87
|
=== OAuth
|
49
88
|
|
50
89
|
OAuth authorization URL:
|
@@ -115,13 +154,51 @@ OAuth Application Access Token, required to access application anlytics data:
|
|
115
154
|
fg_client = FGraph::Client.new(:access_token => '...')
|
116
155
|
fg_client.me
|
117
156
|
fg.client.publish_feed('herryanto', :message => 'Cool!')
|
118
|
-
|
157
|
+
|
119
158
|
=== Pagination Options
|
120
159
|
|
121
160
|
* <tt>limit</tt> - max no of records
|
122
161
|
* <tt>offset</tt> - offset
|
123
162
|
* <tt>until</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
124
163
|
|
164
|
+
=== Rails Helper
|
165
|
+
|
166
|
+
Sample codes:
|
167
|
+
|
168
|
+
<%= fgraph_javascript_init_tag %>
|
169
|
+
<script type="text/javascript">
|
170
|
+
FB.Event.subscribe('auth.sessionChange', function(response) {
|
171
|
+
if (response.session) {
|
172
|
+
// A user has logged in, and a new cookie has been saved
|
173
|
+
} else {
|
174
|
+
// The user has logged out, and the cookie has been cleared
|
175
|
+
}
|
176
|
+
});
|
177
|
+
</script>
|
178
|
+
|
179
|
+
<!-- Facebook Login Button -->
|
180
|
+
<fb:login-button autologoutlink="true" perms="email,publish_stream"></fb:login-button>
|
181
|
+
|
182
|
+
<% if fgraph_logged_in? %>
|
183
|
+
<br>Hello <%= fgraph_current_user['name'] %>,
|
184
|
+
<br><%= fgraph_image_tag(fgraph_current_user, 'large') %>
|
185
|
+
<% end %>
|
186
|
+
|
187
|
+
For Asynchronous load, use <tt>window.afterFbAsyncInit</tt>:
|
188
|
+
|
189
|
+
<%= fgraph_javascript_init_tag :async => true %>
|
190
|
+
<script type="text/javascript">
|
191
|
+
window.afterFbAsyncInit = function() {
|
192
|
+
FB.Event.subscribe('auth.sessionChange', function(response) {
|
193
|
+
if (response.session) {
|
194
|
+
// A user has logged in, and a new cookie has been saved
|
195
|
+
} else {
|
196
|
+
// The user has logged out, and the cookie has been cleared
|
197
|
+
}
|
198
|
+
});
|
199
|
+
}
|
200
|
+
</script>
|
201
|
+
|
125
202
|
== License
|
126
203
|
|
127
204
|
(The MIT License)
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ begin
|
|
9
9
|
gem.email = "herryanto@gmail.com"
|
10
10
|
gem.homepage = "http://github.com/jugend/fgraph"
|
11
11
|
gem.authors = ["Herryanto Siatono"]
|
12
|
-
gem.files = FileList["[A-Z]*", "{examples,lib,test}/**/*"]
|
12
|
+
gem.files = FileList["[A-Z]*", "{examples,lib,test,rails,tasks,templates}/**/*"]
|
13
13
|
|
14
14
|
gem.add_dependency("httparty", "~> 0.5.0")
|
15
15
|
# gem.add_dependency("hashie", "~> 0.2.0")
|
data/VERSION.yml
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
module FGraph
|
2
2
|
module Rails
|
3
3
|
module FGraphHelper
|
4
|
+
|
5
|
+
# Access FGraph.config initialized with values set in <tt>[RAILS_ROOT]/config/fgraph.yml</tt>.
|
4
6
|
def fgraph_config
|
5
7
|
FGraph.config || {}
|
6
8
|
end
|
7
|
-
|
9
|
+
|
10
|
+
# Return Facebook session, default to retrieve session from cookies
|
8
11
|
def fgraph_session(app_id = fgraph_config['app_id'],
|
9
12
|
app_secret = fgraph_config['app_secret'])
|
10
13
|
|
@@ -12,6 +15,7 @@ module FGraph
|
|
12
15
|
@fgraph_session = fgraph_session_cookies(app_id, app_secret)
|
13
16
|
end
|
14
17
|
|
18
|
+
# Return Facebook_session cookies
|
15
19
|
def fgraph_session_cookies(app_id = fgraph_config['app_id'],
|
16
20
|
app_secret = fgraph_config['app_secret'])
|
17
21
|
|
@@ -60,10 +64,11 @@ module FGraph
|
|
60
64
|
@fgraph_current_user = fgraph_client.me
|
61
65
|
end
|
62
66
|
|
67
|
+
# Return FGraph::Client.instance initialized with settings set in <tt>fgraph.yml</tt>.
|
68
|
+
# Initialized with <tt>:access_token</tt> as well if Facebook session exists.
|
63
69
|
def fgraph_client
|
64
70
|
return @fgraph_client if @fgraph_client
|
65
|
-
|
66
|
-
|
71
|
+
|
67
72
|
@fgraph_client = FGraph::Client.new(
|
68
73
|
:client_id => fgraph_config['app_id'],
|
69
74
|
:client_secret => fgraph_config['app_secret'],
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module FGraph
|
2
|
+
module Rails
|
3
|
+
module FGraphTagHelper
|
4
|
+
def fgraph_javascript_include_tag
|
5
|
+
%{<script src="http://connect.facebook.net/en_US/all.js"></script>}
|
6
|
+
end
|
7
|
+
|
8
|
+
# Inititalize XFBML Javascript include and initialization script.
|
9
|
+
#
|
10
|
+
# ==== Options
|
11
|
+
# * <tt>app_id</tt> - overrride Fgraph.config['app_id'] value.
|
12
|
+
# * <tt>async</tt> - asynchronous javascript include & initialization.
|
13
|
+
# for other Facebook JS initialization codes please wrap under:
|
14
|
+
#
|
15
|
+
# window.afterFbAsyncInit = function() {
|
16
|
+
# ....
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
def fgraph_javascript_init_tag(options={})
|
20
|
+
options = { :app_id => FGraph.config['app_id'] }.merge(options || {})
|
21
|
+
|
22
|
+
if options[:async]
|
23
|
+
%{
|
24
|
+
<div id="fb-root"></div>
|
25
|
+
<script>
|
26
|
+
window.fbAsyncInit = function() {
|
27
|
+
FB.init({appId: '#{options[:app_id]}', status: true, cookie: true,
|
28
|
+
xfbml: true});
|
29
|
+
|
30
|
+
if (window.afterFbAsyncInit) {
|
31
|
+
window.afterFbAsyncInit();
|
32
|
+
}
|
33
|
+
};
|
34
|
+
(function() {
|
35
|
+
var e = document.createElement('script'); e.async = true;
|
36
|
+
e.src = document.location.protocol +
|
37
|
+
'//connect.facebook.net/en_US/all.js';
|
38
|
+
document.getElementById('fb-root').appendChild(e);
|
39
|
+
}());
|
40
|
+
</script>
|
41
|
+
}
|
42
|
+
else
|
43
|
+
tag = fgraph_javascript_include_tag
|
44
|
+
tag << %{
|
45
|
+
<div id="fb-root"></div>
|
46
|
+
<script>
|
47
|
+
FB.init({appId: '#{options[:app_id]}', status: true, cookie: true, xfbml: true});
|
48
|
+
</script>
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return Facebook object picture url: http://graph.facebook.com/[id]/picture
|
54
|
+
#
|
55
|
+
# ==== Type Options
|
56
|
+
# * <tt>square</tt> - 50x50 (default)
|
57
|
+
# * <tt>small</tt> - 50 pixels wide, variable height
|
58
|
+
# * <tt>normal</tt> - 100 pixels wide, variable height
|
59
|
+
# * <tt>large</tt> - 200 pixels wide, variable height
|
60
|
+
#
|
61
|
+
def fgraph_picture_url(id, type=nil)
|
62
|
+
id = FGraph.get_id(id)
|
63
|
+
url = "http://graph.facebook.com/#{id}/picture"
|
64
|
+
url += "?type=#{type}" if type
|
65
|
+
url
|
66
|
+
end
|
67
|
+
|
68
|
+
def fgraph_image_tag(id, type=nil, options={})
|
69
|
+
default_options = fgraph_image_options(type)
|
70
|
+
default_options[:alt] = id['name'] if id.is_a?(Hash)
|
71
|
+
image_tag(fgraph_picture_url(id, type), default_options.merge(options || {}))
|
72
|
+
end
|
73
|
+
|
74
|
+
def fgraph_image_options(type)
|
75
|
+
case type
|
76
|
+
when 'square'
|
77
|
+
{:width => 50, :height => 50}
|
78
|
+
when 'small'
|
79
|
+
{:width => 50}
|
80
|
+
when 'normal'
|
81
|
+
{:width => 100}
|
82
|
+
when 'large'
|
83
|
+
{:width => 200}
|
84
|
+
else
|
85
|
+
{:width => 50, :height => 50}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/fgraph.rb
CHANGED
@@ -8,14 +8,6 @@ module FGraph
|
|
8
8
|
base_uri 'https://graph.facebook.com'
|
9
9
|
format :json
|
10
10
|
|
11
|
-
def self.config
|
12
|
-
@@config
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.config=(config)
|
16
|
-
@@config = config
|
17
|
-
end
|
18
|
-
|
19
11
|
# Facebook Error
|
20
12
|
class FacebookError < StandardError
|
21
13
|
attr_reader :data
|
@@ -31,299 +23,377 @@ module FGraph
|
|
31
23
|
class OAuthError < FacebookError; end
|
32
24
|
class OAuthAccessTokenError < OAuthError; end
|
33
25
|
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# # Users: https://graph.facebook.com/btaylor (Bret Taylor)
|
37
|
-
# FGraph.object('btaylor')
|
38
|
-
#
|
39
|
-
# # Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
|
40
|
-
# FGraph.object('cocacola')
|
41
|
-
#
|
42
|
-
# # Fields selection with metadata
|
43
|
-
# FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
|
26
|
+
# Collection objects for Graph response with array data.
|
44
27
|
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
28
|
+
class Collection < Array
|
29
|
+
attr_reader :next_url, :previous_url, :next_options, :previous_options
|
30
|
+
|
31
|
+
# Initialize Facebook response object with 'data' array value.
|
32
|
+
def initialize(response)
|
33
|
+
return super unless response
|
34
|
+
|
35
|
+
super(response['data'])
|
36
|
+
paging = response['paging'] || {}
|
37
|
+
self.next_url = paging['next']
|
38
|
+
self.previous_url = paging['previous']
|
39
|
+
end
|
40
|
+
|
41
|
+
def next_url=(url)
|
42
|
+
@next_url = url
|
43
|
+
@next_options = self.url_options(url)
|
44
|
+
end
|
45
|
+
|
46
|
+
def previous_url=(url)
|
47
|
+
@previous_url = url
|
48
|
+
@previous_options = self.url_options(url)
|
49
|
+
end
|
50
|
+
|
51
|
+
def first?
|
52
|
+
@previous_url.blank? and not @next_url.blank?
|
53
|
+
end
|
54
|
+
|
55
|
+
def next?
|
56
|
+
not @next_url.blank?
|
57
|
+
end
|
58
|
+
|
59
|
+
def previous?
|
60
|
+
not @previous_url.blank?
|
61
|
+
end
|
62
|
+
|
63
|
+
def url_options(url)
|
64
|
+
return unless url
|
65
|
+
|
66
|
+
uri = URI.parse(url)
|
67
|
+
options = {}
|
68
|
+
uri.query.split('&').each do |param_set|
|
69
|
+
param_set = param_set.split('=')
|
70
|
+
options[param_set[0]] = CGI.unescape(param_set[1])
|
71
|
+
end
|
72
|
+
options
|
73
|
+
end
|
51
74
|
end
|
52
75
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
76
|
+
class << self
|
77
|
+
attr_accessor :config
|
78
|
+
|
79
|
+
# Single object query.
|
80
|
+
#
|
81
|
+
# # Users: https://graph.facebook.com/btaylor (Bret Taylor)
|
82
|
+
# FGraph.object('btaylor')
|
83
|
+
#
|
84
|
+
# # Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
|
85
|
+
# FGraph.object('cocacola')
|
86
|
+
#
|
87
|
+
# # Fields selection with metadata
|
88
|
+
# FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
|
89
|
+
#
|
90
|
+
# # Page photos
|
91
|
+
# FGraph.object('/cocacola/photos')
|
92
|
+
# photos = FGraph.object_photos('cocacola')
|
93
|
+
#
|
94
|
+
# # Support id from object hash
|
95
|
+
# friend = { 'name' => 'Mark Zuckerberg', 'id' => '4'}
|
96
|
+
# friend_details = FGraph.object(friend)
|
97
|
+
def object(id, options={})
|
98
|
+
id = self.get_id(id)
|
99
|
+
perform_get("/#{id}", options)
|
100
|
+
end
|
70
101
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
#
|
86
|
-
def self.me(*args)
|
87
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
88
|
-
category = args.shift
|
102
|
+
# call-seq:
|
103
|
+
# FGraph.objects(id, id)
|
104
|
+
# FGraph.objects(id, id, options_hash)
|
105
|
+
#
|
106
|
+
# Multiple objects query.
|
107
|
+
#
|
108
|
+
# # Multiple users select: https://graph.facebook.com?ids=arjun,vernal
|
109
|
+
# FGraph.objects('arjun', 'vernel')
|
110
|
+
#
|
111
|
+
# # Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
|
112
|
+
# FGraph.objects('arjun', 'vernel', :fields => 'id,name,picture')
|
113
|
+
#
|
114
|
+
def objects(*args)
|
115
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
89
116
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
117
|
+
# If first input before option is an array
|
118
|
+
if args.count == 1 and args.first.is_a?(Array)
|
119
|
+
args = args.first.map do |arg|
|
120
|
+
self.get_id(arg)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
options = options.merge(:ids => args.join(','))
|
125
|
+
perform_get("/", options)
|
126
|
+
end
|
94
127
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
self.format_url('/oauth/authorize', {
|
119
|
-
:client_id => client_id,
|
120
|
-
:redirect_uri => redirect_uri
|
121
|
-
}.merge(options))
|
122
|
-
end
|
128
|
+
# call-seq:
|
129
|
+
# FGraph.me(category)
|
130
|
+
# FGraph.me(category, options_hash)
|
131
|
+
#
|
132
|
+
# Returns current user object details.
|
133
|
+
#
|
134
|
+
# <tt>category</tt> - <tt>friends|home|feed|likes|movies|books|notes|photos|videos|events|groups</tt>
|
135
|
+
#
|
136
|
+
# # Current user: https://graph.facebook.com/me?access_token=...
|
137
|
+
# FGraph.me(:access_token => '...')
|
138
|
+
#
|
139
|
+
# # Current user's friends: https://graph.facebook.com/me/friends?access_token=...
|
140
|
+
# FGraph.me('friends', :access_token => '...')
|
141
|
+
# FGraph.me_friends(:access_token => '...')
|
142
|
+
#
|
143
|
+
def me(*args)
|
144
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
145
|
+
category = args.shift
|
146
|
+
|
147
|
+
path = "me"
|
148
|
+
path += "/#{category}" unless category.blank?
|
149
|
+
self.object(path, options)
|
150
|
+
end
|
123
151
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
152
|
+
# Request authorization from Facebok to fetch private data in the profile or permission to publish on a
|
153
|
+
# user's behalf. Returns Oauth Authorization URL, redirect to this URL to allow user to authorize your
|
154
|
+
# application from Facebook.
|
155
|
+
#
|
156
|
+
# <tt>client_id</tt> - Application ID
|
157
|
+
# <tt>redirect_uri</tt> - Needs to begin with your app's Connect URL. For instance, if your Connect URL
|
158
|
+
# is http://www.example.com then your redirect URI could be http://www.example.com/oauth_redirect.
|
159
|
+
# <tt>scope (optional)</tt> -
|
160
|
+
#
|
161
|
+
# ==== Options
|
162
|
+
# * <tt>scope</tt> - Extended permission required to fetch private data or request permision to
|
163
|
+
# publish to Facebook on a user's behalf.
|
164
|
+
# * <tt>display</tt> - Other display type for authentication/authorization form, i.e. popup, touch.
|
165
|
+
#
|
166
|
+
# # https://graph.facebook.com/oauth/authorize?
|
167
|
+
# # client_id=...&
|
168
|
+
# # redirect_uri=http://www.example.com/oauth_redirect&
|
169
|
+
# # scope=publish_stream
|
170
|
+
#
|
171
|
+
# FGraph.oauth_authorize_url('[client id]', 'http://www.example.com/oauth_redirect', :scope =>
|
172
|
+
# 'publish_stream')
|
173
|
+
#
|
174
|
+
def oauth_authorize_url(client_id, redirect_uri, options={})
|
175
|
+
self.format_url('/oauth/authorize', {
|
176
|
+
:client_id => client_id,
|
177
|
+
:redirect_uri => redirect_uri
|
178
|
+
}.merge(options))
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return OAuth access_token. There are two types of access token, user access token and application
|
182
|
+
# access token.
|
183
|
+
#
|
184
|
+
# User access_token requires <tt>code</tt> and and <tt>redirect_uri</tt> options. <tt>code</tt> is
|
185
|
+
# the autorization code appended as query string to redirect URI when accessing oauth authorization URL.
|
186
|
+
#
|
187
|
+
# # https://graph.facebook.com/oauth/access_token?
|
188
|
+
# # client_id=...&
|
189
|
+
# # client_secret=...&
|
190
|
+
# # redirect_uri=http://www.example.com/oauth_redirect&
|
191
|
+
# # code=...
|
192
|
+
# FGraph.oauth_access_token('[client id]', '[client secret]',
|
193
|
+
# :redirect_uri => ''http://www.example.com/oauth_redirect',
|
194
|
+
# :code => '[authorization code]')
|
195
|
+
#
|
196
|
+
# Application access token requires <tt>:type => 'client_cred'</td> option. Used to access application
|
197
|
+
# insights data.
|
198
|
+
#
|
199
|
+
# # https://graph.facebook.com/oauth/access_token?
|
200
|
+
# # client_id=...&
|
201
|
+
# # client_secret=...&
|
202
|
+
# # type=client_cred
|
203
|
+
# FGraph.oauth_access_token('[client id]', '[client secret]', :type => 'client_cred')
|
204
|
+
#
|
205
|
+
def oauth_access_token(client_id, client_secret, options={})
|
206
|
+
url = self.format_url('/oauth/access_token', {
|
207
|
+
:client_id => client_id,
|
208
|
+
:client_secret => client_secret
|
209
|
+
}.merge(options || {}))
|
153
210
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
211
|
+
response = self.perform_get(url)
|
212
|
+
response_hash = {}
|
213
|
+
response.split('&').each do |value|
|
214
|
+
value_pair = value.split('=')
|
215
|
+
response_hash[value_pair[0]] = value_pair[1]
|
216
|
+
end
|
217
|
+
response_hash
|
159
218
|
end
|
160
|
-
response_hash
|
161
|
-
end
|
162
219
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
220
|
+
# Shortcut to retrieve application access token.
|
221
|
+
def oauth_app_access_token(client_id, client_secret)
|
222
|
+
self.oauth_access_token(client_id, client_secret, :type => 'client_cred')
|
223
|
+
end
|
167
224
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
225
|
+
# Publish to Facebook, you would need to be authorized and provide access token.
|
226
|
+
#
|
227
|
+
# # Post to user's feed.
|
228
|
+
# # curl -F 'access_token=...' \
|
229
|
+
# # -F 'message=Hello, Arjun. I like this new API.' \
|
230
|
+
# # https://graph.facebook.com/arjun/feed
|
231
|
+
# FGraph.publish('arjun/feed', :message => 'Hello, Arjun. I like this new API.',
|
232
|
+
# :access_token => '...')
|
233
|
+
# FGraph.publish_feed('arjun', :message => '...', :access_token => '...')
|
234
|
+
# FGraph.publish_feed('me', ':message => '...', :access_token => '...')
|
235
|
+
#
|
236
|
+
# ==== Options
|
237
|
+
#
|
238
|
+
# Method Description Options
|
239
|
+
# -------------------------------------------------------------------------------------
|
240
|
+
# /PROFILE_ID/feed write to the given profile's feed/wall :message, :picture,
|
241
|
+
# :link, :name, description
|
242
|
+
# /POST_ID/comments comment on the given post :message
|
243
|
+
# /POST_ID/likes like the given post none
|
244
|
+
# /PROFILE_ID/notes write a note on the given profile :message, :subject
|
245
|
+
# /PROFILE_ID/links write a link on the given profile :link, :message
|
246
|
+
# /EVENT_ID/attending attend the given event none
|
247
|
+
# /EVENT_ID/maybe maybe attend the given event none
|
248
|
+
# /EVENT_ID/declined decline the given event none
|
249
|
+
#
|
250
|
+
def publish(id, options={})
|
251
|
+
id = self.get_id(id)
|
252
|
+
self.perform_post("/#{id}", options)
|
253
|
+
end
|
196
254
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
255
|
+
# Delete objects in the graph.
|
256
|
+
#
|
257
|
+
# # DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
|
258
|
+
#
|
259
|
+
# FGraph.remove('[ID]')
|
260
|
+
# FGraph.remove('[ID]/likes')
|
261
|
+
# FGraph.remove_likes('[ID]')
|
262
|
+
#
|
263
|
+
def remove(id, options={})
|
264
|
+
id = self.get_id(id)
|
265
|
+
self.perform_delete("/#{id}", options)
|
266
|
+
end
|
208
267
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
268
|
+
# Search over all public objects in the social graph.
|
269
|
+
#
|
270
|
+
# # https://graph.facebook.com/search?q=watermelon&type=post
|
271
|
+
# FGraph.search('watermelon', :type => 'post')
|
272
|
+
# FGraph.search_post('watermelon')
|
273
|
+
#
|
274
|
+
# ==== Options
|
275
|
+
# * <tt>type</tt> - <tt>album|event|group|link|note|page|photo|post|status|user|video</tt>
|
276
|
+
# * <tt>limit</tt> - max no of records
|
277
|
+
# * <tt>offset</tt> - offset
|
278
|
+
# * <tt>until</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
279
|
+
def search(query, options={})
|
280
|
+
self.perform_get("/search", {
|
281
|
+
:q => query
|
282
|
+
}.merge(options|| {}))
|
283
|
+
end
|
225
284
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
285
|
+
# Download insights data for your application.
|
286
|
+
#
|
287
|
+
# # https://graph.facebook.com/[client_id]/insights?access_token=...
|
288
|
+
# FGraph.insights('[client_id]', '[app_access_token]')
|
289
|
+
#
|
290
|
+
# # https://graph.facebook.com/[client_id]/insights/application_api_call/day?access_token=...
|
291
|
+
# FGraph.insights('[client_id]', '[app_access_token]', :metric_path => 'application_api_call/day')
|
292
|
+
#
|
293
|
+
# ==== Options
|
294
|
+
# * <tt>metric_path</tt> - e.g. application_api_calls/day
|
295
|
+
# * <tt>since</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
296
|
+
# * <tt>until</tt> - until (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
297
|
+
def insights(client_id, app_access_token, options={})
|
298
|
+
metric_path = options.delete(:metric_path)
|
240
299
|
|
241
|
-
|
242
|
-
|
300
|
+
path = "/#{client_id}/insights"
|
301
|
+
path += "/#{metric_path}" if metric_path
|
243
302
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
303
|
+
self.perform_get(path, {
|
304
|
+
:access_token => app_access_token
|
305
|
+
}.merge(options || {}))
|
306
|
+
end
|
248
307
|
|
249
|
-
|
250
|
-
|
251
|
-
|
308
|
+
def perform_get(uri, options = {})
|
309
|
+
handle_response(get(uri, {:query => options}))
|
310
|
+
end
|
252
311
|
|
253
|
-
|
254
|
-
|
255
|
-
|
312
|
+
def perform_post(uri, options = {})
|
313
|
+
handle_response(post(uri, {:body => options}))
|
314
|
+
end
|
256
315
|
|
257
|
-
|
258
|
-
|
259
|
-
|
316
|
+
def perform_delete(uri, options = {})
|
317
|
+
handle_response(delete(uri, {:body => options}))
|
318
|
+
end
|
260
319
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
case response['error']['type']
|
266
|
-
when 'QueryParseException'
|
267
|
-
raise QueryParseError, response['error']
|
268
|
-
when 'GraphMethodException'
|
269
|
-
raise GraphMethodError, response['error']
|
270
|
-
when 'OAuthException'
|
271
|
-
raise OAuthError, response['error']
|
272
|
-
when 'OAuthAccessTokenException'
|
273
|
-
raise OAuthAccessTokenError, response['error']
|
320
|
+
def handle_response(response)
|
321
|
+
unless response['error']
|
322
|
+
return FGraph::Collection.new(response) if response['data']
|
323
|
+
response
|
274
324
|
else
|
275
|
-
|
325
|
+
case response['error']['type']
|
326
|
+
when 'QueryParseException'
|
327
|
+
raise QueryParseError, response['error']
|
328
|
+
when 'GraphMethodException'
|
329
|
+
raise GraphMethodError, response['error']
|
330
|
+
when 'OAuthException'
|
331
|
+
raise OAuthError, response['error']
|
332
|
+
when 'OAuthAccessTokenException'
|
333
|
+
raise OAuthAccessTokenError, response['error']
|
334
|
+
else
|
335
|
+
raise FacebookError, response['error']
|
336
|
+
end
|
337
|
+
end
|
276
338
|
end
|
277
|
-
end
|
278
339
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
340
|
+
def format_url(path, options={})
|
341
|
+
url = self.base_uri.dup
|
342
|
+
url << path
|
343
|
+
unless options.blank?
|
344
|
+
url << "?"
|
284
345
|
|
285
|
-
|
346
|
+
option_count = 0
|
286
347
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
348
|
+
stringified_options = {}
|
349
|
+
options.each do |key, value|
|
350
|
+
stringified_options[key.to_s] = value
|
351
|
+
end
|
352
|
+
options = stringified_options
|
292
353
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
354
|
+
options.each do |option|
|
355
|
+
next if option[1].blank?
|
356
|
+
url << "&" if option_count > 0
|
357
|
+
url << "#{option[0]}=#{CGI.escape(option[1].to_s)}"
|
358
|
+
option_count += 1
|
359
|
+
end
|
298
360
|
end
|
361
|
+
url
|
299
362
|
end
|
300
|
-
url
|
301
|
-
end
|
302
363
|
|
303
|
-
|
304
|
-
|
305
|
-
|
364
|
+
def method_missing(name, *args, &block)
|
365
|
+
names = name.to_s.split('_')
|
366
|
+
super unless names.length > 1
|
306
367
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
368
|
+
case names.shift
|
369
|
+
when 'object'
|
370
|
+
# object_photos
|
371
|
+
self.object("#{args[0]}/#{names[0]}", args[1])
|
372
|
+
when 'me'
|
373
|
+
# me_photos
|
374
|
+
self.me(names[0], args[0])
|
375
|
+
when 'publish'
|
376
|
+
# publish_feed(id)
|
377
|
+
self.publish("#{args[0]}/#{names[0]}", args[1])
|
378
|
+
when 'remove'
|
379
|
+
# remove_feed(id)
|
380
|
+
self.remove("#{args[0]}/#{names[0]}", args[1])
|
381
|
+
when 'search'
|
382
|
+
# search_user(query)
|
383
|
+
options = args[1] || {}
|
384
|
+
options[:type] = names[0]
|
385
|
+
self.search(args[0], options)
|
386
|
+
else
|
387
|
+
super
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Return ID['id'] if ID is a hash object
|
392
|
+
#
|
393
|
+
def get_id(id)
|
394
|
+
return unless id
|
395
|
+
id = id['id'] if id.is_a?(Hash)
|
396
|
+
id
|
327
397
|
end
|
328
398
|
end
|
329
399
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
namespace :fgraph do
|
4
|
+
desc "Create fgraph.yml configuration file in Rails config folder"
|
5
|
+
task :setup => :environment do
|
6
|
+
fgraph_config = File.join(RAILS_ROOT, "config", "fgraph.yml")
|
7
|
+
unless File.exist?(fgraph_config)
|
8
|
+
fgraph_config_template = File.join(RAILS_ROOT, "vendor", "plugins", "fgraph", "templates", "fgraph.yml")
|
9
|
+
FileUtils.cp fgraph_config_teirmplate, fgraph_config
|
10
|
+
puts "#{RAILS_ROOT}/config/fgraph.yml created, please update your app_id and app_secret."
|
11
|
+
else
|
12
|
+
puts "#{RAILS_ROOT}/config/fgraph.yml already exists."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tasks/fgraph.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
load 'tasks/fgraph.rake'
|
data/rails/init.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fgraph'
|
2
|
+
require 'fgraph/rails/fgraph_helper'
|
3
|
+
require 'fgraph/rails/fgraph_tag_helper'
|
4
|
+
|
5
|
+
FGRAPH_CONFIG = "#{RAILS_ROOT}/config/fgraph.yml"
|
6
|
+
|
7
|
+
# Load configuration file
|
8
|
+
if File.exists?(FGRAPH_CONFIG)
|
9
|
+
fgraph_config = YAML.load(ERB.new(File.read(FGRAPH_CONFIG)).result)
|
10
|
+
FGraph.config = fgraph_config[RAILS_ENV]
|
11
|
+
end
|
12
|
+
|
13
|
+
ActionController::Base.send :include, FGraph::Rails::FGraphHelper
|
14
|
+
ActionView::Base.send :include, FGraph::Rails::FGraphHelper
|
15
|
+
ActionView::Base.send :include, FGraph::Rails::FGraphTagHelper
|
data/rails/install.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutil'
|
3
|
+
|
4
|
+
fgraph_config = File.join(RAILS_ROOT, "config", "fgraph.yml")
|
5
|
+
unless File.exist?(fgraph_config)
|
6
|
+
fgraph_config_template = File.join(RAILS_ROOT, "vendor", "plugins", "fgraph", "templates", "fgraph.yml")
|
7
|
+
FileUtils.cp fgraph_config_template, fgraph_config
|
8
|
+
puts "#{RAILS_ROOT}/config/fgraph.yml created, please update your app_id and app_secret."
|
9
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# FGraph Facebook Configuration File
|
2
|
+
#
|
3
|
+
# Register your application to get your app_id and app_secret:
|
4
|
+
# http://developers.facebook.com/setup/
|
5
|
+
#
|
6
|
+
development:
|
7
|
+
app_id:
|
8
|
+
app_secret:
|
9
|
+
|
10
|
+
test:
|
11
|
+
app_id:
|
12
|
+
app_secret:
|
13
|
+
|
14
|
+
production:
|
15
|
+
app_id:
|
16
|
+
app_secret:
|
data/test/fgraph_test.rb
CHANGED
@@ -8,6 +8,20 @@ class FGraphTest < Test::Unit::TestCase
|
|
8
8
|
FACEBOOK_OAUTH_ACCESS_TOKEN = "115187085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
9
9
|
FACEBOOK_OAUTH_APP_ACCESS_TOKEN = "112167085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
10
10
|
|
11
|
+
context "FGraph.get_id" do
|
12
|
+
should "return 'id' if input 'id' is not a Hash" do
|
13
|
+
test_id = '123'
|
14
|
+
id = FGraph.get_id(test_id)
|
15
|
+
id.should == test_id
|
16
|
+
end
|
17
|
+
|
18
|
+
should "return 'id' value from hash object if input 'id' is a Hash" do
|
19
|
+
test_id = { 'name' => 'Anthony', 'id' => '123' }
|
20
|
+
id = FGraph.get_id(test_id)
|
21
|
+
id.should == test_id['id']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
context "FGraph.object" do
|
12
26
|
should "return object hash" do
|
13
27
|
stub_get('/cocacola', 'object_cocacola.json')
|
@@ -28,13 +42,29 @@ class FGraphTest < Test::Unit::TestCase
|
|
28
42
|
FGraph.expects(:perform_get).with('/cocacola', options)
|
29
43
|
FGraph.object('cocacola', options)
|
30
44
|
end
|
45
|
+
|
46
|
+
should "call FGraph.get_id" do
|
47
|
+
stub_get('/cocacola', 'object_cocacola.json')
|
48
|
+
FGraph.expects(:get_id).with('cocacola')
|
49
|
+
FGraph.expects(:perform_get)
|
50
|
+
object = FGraph.object('cocacola')
|
51
|
+
end
|
31
52
|
end
|
32
53
|
|
33
54
|
context "FGraph.objects" do
|
34
55
|
should "call perform_get with ids and query options" do
|
35
56
|
options = {:fields => 'id,name'}
|
36
|
-
FGraph.expects(:perform_get).with('/', options.merge(:ids => '
|
37
|
-
FGraph.objects('
|
57
|
+
FGraph.expects(:perform_get).with('/', options.merge(:ids => '1,2'))
|
58
|
+
FGraph.objects('1', '2', options)
|
59
|
+
end
|
60
|
+
|
61
|
+
should "collect id values if input is an array of hash values" do
|
62
|
+
test_ids = [
|
63
|
+
{ 'name' => 'Herry', 'id' => '1'},
|
64
|
+
{ 'name' => 'John', 'id' => '2'}
|
65
|
+
]
|
66
|
+
FGraph.expects(:perform_get).with('/', :ids => '1,2')
|
67
|
+
FGraph.objects(test_ids)
|
38
68
|
end
|
39
69
|
end
|
40
70
|
|
@@ -204,6 +234,28 @@ class FGraphTest < Test::Unit::TestCase
|
|
204
234
|
end
|
205
235
|
|
206
236
|
context "FGraph.handle_response" do
|
237
|
+
should "return response object if there's no error" do
|
238
|
+
fb_response = {'name' => 'test'}
|
239
|
+
response = FGraph.handle_response(fb_response)
|
240
|
+
response.should == fb_response
|
241
|
+
end
|
242
|
+
|
243
|
+
should "convert to FGraph::Collection object if response contain 'data' value" do
|
244
|
+
fb_response = {
|
245
|
+
"data" => [
|
246
|
+
{ "name" =>"Belle Clara", "id" => "100000133774483" },
|
247
|
+
{ "name" =>"Rosemary Schapira", "id" => "100000237306697" }
|
248
|
+
],
|
249
|
+
"paging" => {
|
250
|
+
"next" => "https://graph.facebook.com/756314021/friends?offset=4&limit=2&access_token=101507589896698"
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
collection = FGraph.handle_response(fb_response)
|
255
|
+
collection.class.should == FGraph::Collection
|
256
|
+
collection.count.should == fb_response['data'].count
|
257
|
+
end
|
258
|
+
|
207
259
|
should "raise QueryParseError" do
|
208
260
|
lambda do
|
209
261
|
object = FGraph.handle_response(response_error('QueryParseException'))
|
@@ -229,6 +281,29 @@ class FGraphTest < Test::Unit::TestCase
|
|
229
281
|
end
|
230
282
|
end
|
231
283
|
|
284
|
+
context "FGraph::Collection" do
|
285
|
+
should "should convert response object to Collection" do
|
286
|
+
response = {
|
287
|
+
"data" => [
|
288
|
+
{"name"=>"Belle Clara", "id"=>"100000133774483"},
|
289
|
+
{"name"=>"Rosemary Schapira", "id"=>"100000237306697"}
|
290
|
+
],
|
291
|
+
"paging"=> {
|
292
|
+
"previous"=> "https://graph.facebook.com/756314021/friends?offset=0&limit=2&access_token=101507589896698",
|
293
|
+
"next"=> "https://graph.facebook.com/756314021/friends?offset=4&limit=2&access_token=101507589896698"
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
collection = FGraph::Collection.new(response)
|
298
|
+
collection.count.should == response['data'].count
|
299
|
+
collection.first.should == response['data'].first
|
300
|
+
collection.next_url.should == response['paging']['next']
|
301
|
+
collection.previous_url.should == response['paging']['previous']
|
302
|
+
collection.previous_options.should == {'offset' => '0', 'limit' => '2', 'access_token' => '101507589896698'}
|
303
|
+
collection.next_options.should == {'offset' => '4', 'limit' => '2', 'access_token' => '101507589896698'}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
232
307
|
def response_error(type, msg=nil)
|
233
308
|
{'error' => { 'type' => type, 'message' => msg}}
|
234
309
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Herryanto Siatono
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-26 00:00:00 +08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -107,6 +107,11 @@ files:
|
|
107
107
|
- lib/fgraph/client.rb
|
108
108
|
- lib/fgraph/rails/fgraph_helper.rb
|
109
109
|
- lib/fgraph/rails/fgraph_tag_helper.rb
|
110
|
+
- lib/tasks/fgraph.rake
|
111
|
+
- lib/tasks/fgraph.rb
|
112
|
+
- rails/init.rb
|
113
|
+
- rails/install.rb
|
114
|
+
- templates/fgraph.yml
|
110
115
|
- test/fgraph/client_test.rb
|
111
116
|
- test/fgraph_test.rb
|
112
117
|
- test/fixtures/access_token.txt
|