fgraph 0.1.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 +2 -0
- data/License +20 -0
- data/README.rdoc +136 -0
- data/Rakefile +37 -0
- data/VERSION.yml +5 -0
- data/examples/get_access_token.rb +19 -0
- data/examples/publish_feed.rb +16 -0
- data/lib/fgraph.rb +321 -0
- data/lib/fgraph/client.rb +111 -0
- data/test/fgraph/client_test.rb +155 -0
- data/test/fgraph_test.rb +236 -0
- data/test/fixtures/access_token.txt +1 -0
- data/test/fixtures/object_cocacola.json +10 -0
- data/test/test_helper.rb +34 -0
- metadata +148 -0
data/History
ADDED
data/License
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2010 Herryanto Siatono
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
= fgraph
|
|
2
|
+
|
|
3
|
+
* http://github.com/jugend/fgraph
|
|
4
|
+
|
|
5
|
+
== Description
|
|
6
|
+
|
|
7
|
+
Facebook Graph Ruby API implementation with Ruby magic (http://graph.facebook.com).
|
|
8
|
+
|
|
9
|
+
== Installation
|
|
10
|
+
|
|
11
|
+
sudo gem install fgraph
|
|
12
|
+
|
|
13
|
+
=== Facebook Graph Cheat Sheet
|
|
14
|
+
|
|
15
|
+
sudo gem install cheat
|
|
16
|
+
cheat fbgraph
|
|
17
|
+
|
|
18
|
+
=== Single object query
|
|
19
|
+
|
|
20
|
+
# Users: https://graph.facebook.com/btaylor (Bret Taylor)
|
|
21
|
+
FGraph.object('btaylor')
|
|
22
|
+
|
|
23
|
+
# Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
|
|
24
|
+
FGraph.object('cocacola')
|
|
25
|
+
|
|
26
|
+
# Fields selection with metadata
|
|
27
|
+
FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
|
|
28
|
+
|
|
29
|
+
# Page photos
|
|
30
|
+
FGraph.object('/cocacola/photos')
|
|
31
|
+
FGraph.object_photos('cocacola')
|
|
32
|
+
|
|
33
|
+
# Current user: https://graph.facebook.com/me?access_token=...
|
|
34
|
+
FGraph.me(:access_token => '...')
|
|
35
|
+
|
|
36
|
+
# Current user's friends: https://graph.facebook.com/me/friends?access_token=...
|
|
37
|
+
FGraph.me('friends', :access_token => '...')
|
|
38
|
+
FGraph.me_friends(:access_token => '...')
|
|
39
|
+
|
|
40
|
+
=== Multiple objects query
|
|
41
|
+
|
|
42
|
+
# Multiple users select: https://graph.facebook.com?ids=arjun,vernal
|
|
43
|
+
FGraph.objects('arjun', 'vernel')
|
|
44
|
+
|
|
45
|
+
# Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
|
|
46
|
+
FGraph.objects('arjun', 'vernel', :fields => 'id,name,picture')
|
|
47
|
+
|
|
48
|
+
=== OAuth
|
|
49
|
+
|
|
50
|
+
OAuth authorization URL:
|
|
51
|
+
# https://graph.facebook.com/oauth/authorize?
|
|
52
|
+
# client_id=...&
|
|
53
|
+
# redirect_uri=http://www.example.com/oauth_redirect&
|
|
54
|
+
# scope=publish_stream
|
|
55
|
+
FGraph.oauth_authorize_url('[client id]', 'http://www.example.com/oauth_redirect', :scope =>
|
|
56
|
+
'publish_stream')
|
|
57
|
+
|
|
58
|
+
OAuth Access Token:
|
|
59
|
+
# https://graph.facebook.com/oauth/access_token?
|
|
60
|
+
# client_id=...&
|
|
61
|
+
# client_secret=...&
|
|
62
|
+
# redirect_uri=http://www.example.com/oauth_redirect&
|
|
63
|
+
# code=...
|
|
64
|
+
FGraph.oauth_access_token('[client id]', '[client secret]',
|
|
65
|
+
:redirect_uri => ''http://www.example.com/oauth_redirect',
|
|
66
|
+
:code => '[authorization code]')
|
|
67
|
+
|
|
68
|
+
OAuth Application Access Token, required to access application anlytics data:
|
|
69
|
+
# https://graph.facebook.com/oauth/access_token?
|
|
70
|
+
# client_id=...&
|
|
71
|
+
# client_secret=...&
|
|
72
|
+
# type=client_cred
|
|
73
|
+
FGraph.oauth_access_token('[client id]', '[client secret]', :type => 'client_cred')
|
|
74
|
+
|
|
75
|
+
=== Publish to Facebook Graph
|
|
76
|
+
|
|
77
|
+
# Post to user's feed.
|
|
78
|
+
# curl -F 'access_token=...' \
|
|
79
|
+
# -F 'message=Hello, Arjun. I like this new API.' \
|
|
80
|
+
# https://graph.facebook.com/arjun/feed
|
|
81
|
+
FGraph.publish('arjun/feed', :message => 'Hello, Arjun. I like this n ew API.',
|
|
82
|
+
:access_token => '...')
|
|
83
|
+
FGraph.publish_feed('arjun', :message => '...', :access_token => '... ')
|
|
84
|
+
FGraph.publish_feed('me', ':message => '...', :access_token => '...')
|
|
85
|
+
|
|
86
|
+
=== Remove from Facebook Graph
|
|
87
|
+
|
|
88
|
+
# DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
|
|
89
|
+
FGraph.remove('[ID]')
|
|
90
|
+
FGraph.remove('[ID]/likes')
|
|
91
|
+
FGraph.remove_likes('[ID]')
|
|
92
|
+
|
|
93
|
+
=== Search
|
|
94
|
+
|
|
95
|
+
# https://graph.facebook.com/search?q=watermelon&type=post
|
|
96
|
+
FGraph.search('watermelon', :type => 'post')
|
|
97
|
+
FGraph.search_post('watermelon')
|
|
98
|
+
|
|
99
|
+
=== Insights
|
|
100
|
+
|
|
101
|
+
# https://graph.facebook.com/app_id/insights?access_token=...
|
|
102
|
+
FGraph.insights('[app_id]', '[app_access_token]')
|
|
103
|
+
|
|
104
|
+
# https://graph.facebook.com/app_id/insights/application_api_call/day?access_token=...
|
|
105
|
+
FGraph.insights('[app_id]', '[app_access_token]', :metric_path => 'application_api_call/day')
|
|
106
|
+
|
|
107
|
+
=== Pagination Options
|
|
108
|
+
|
|
109
|
+
* <tt>limit</tt> - max no of records
|
|
110
|
+
* <tt>offset</tt> - offset
|
|
111
|
+
* <tt>until</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
|
112
|
+
|
|
113
|
+
== License
|
|
114
|
+
|
|
115
|
+
(The MIT License)
|
|
116
|
+
|
|
117
|
+
Copyright (c) 2010 Herryanto Siatono http://www.pluitsolutions.com
|
|
118
|
+
|
|
119
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
120
|
+
a copy of this software and associated documentation files (the
|
|
121
|
+
'Software'), to deal in the Software without restriction, including
|
|
122
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
123
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
124
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
125
|
+
the following conditions:
|
|
126
|
+
|
|
127
|
+
The above copyright notice and this permission notice shall be
|
|
128
|
+
included in all copies or substantial portions of the Software.
|
|
129
|
+
|
|
130
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
131
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
132
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
133
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
134
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
135
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
136
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'jeweler'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
Jeweler::Tasks.new do |gem|
|
|
6
|
+
gem.name = "fgraph"
|
|
7
|
+
gem.summary = "Ruby Facebook Graph API"
|
|
8
|
+
gem.description = "Ruby Facebook Graph API"
|
|
9
|
+
gem.email = "herryanto@gmail.com"
|
|
10
|
+
gem.homepage = "http://github.com/jugend/fgraph"
|
|
11
|
+
gem.authors = ["Herryanto Siatono"]
|
|
12
|
+
gem.files = FileList["[A-Z]*", "{examples,lib,test}/**/*"]
|
|
13
|
+
|
|
14
|
+
gem.add_dependency("httparty", "~> 0.5.0")
|
|
15
|
+
# gem.add_dependency("hashie", "~> 0.2.0")
|
|
16
|
+
|
|
17
|
+
gem.add_development_dependency("shoulda", "~> 2.10.0")
|
|
18
|
+
gem.add_development_dependency("jnunemaker-matchy", "~> 0.4.0")
|
|
19
|
+
gem.add_development_dependency("mocha", "~> 0.9.0")
|
|
20
|
+
gem.add_development_dependency("fakeweb", "~> 1.2.0")
|
|
21
|
+
end
|
|
22
|
+
rescue LoadError
|
|
23
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Jeweler::GemcutterTasks.new
|
|
27
|
+
|
|
28
|
+
require "rake/testtask"
|
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
|
30
|
+
test.libs << "test"
|
|
31
|
+
test.ruby_opts << "-rubygems"
|
|
32
|
+
test.pattern = "test/**/*_test.rb"
|
|
33
|
+
test.verbose = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
task :default => :test
|
|
37
|
+
task :test => :check_dependencies
|
data/VERSION.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Retrieve your code by accessing Authorization URL From your browser,
|
|
2
|
+
# it should redirect to 'redirect_uri' with 'code' param.
|
|
3
|
+
# Replace the FACEBOOK_XXX constants accordingly.
|
|
4
|
+
#
|
|
5
|
+
# Authorization URL, access from your browser to get Authorization Code
|
|
6
|
+
# https://graph.facebook.com/oauth/authorize?client_id=878116c4a4a76f25e4beb97ab096cc92&redirect_uri=http%3A%2F%2Fbookjetty.pluitsolutions.com%2F&scope=publish_stream
|
|
7
|
+
|
|
8
|
+
FACEBOOK_APP_ID = '878116c4a4a76f25e4beb97ab096cc92'
|
|
9
|
+
FACEBOOK_APP_SECRET = '41f0e7ee8b6409dce1610de9926477c4'
|
|
10
|
+
FACEBOOK_OAUTH_REDIRECT_URI = 'http://bookjetty.pluitsolutions.com/'
|
|
11
|
+
FACEBOOK_OAUTH_CODE = '2.Dq5RDPHxhsgaeScc_fiigg__.3600.1273831200-756314021|4Eew6iuIg0x69N1d3Cr99gdVGwU.'
|
|
12
|
+
FACEBOOK_OAUTH_SCOPE = ''
|
|
13
|
+
|
|
14
|
+
require 'pp'
|
|
15
|
+
require 'rubygems'
|
|
16
|
+
require File.dirname(__FILE__) + '/../lib/fgraph'
|
|
17
|
+
|
|
18
|
+
pp FGraph.oauth_access_token(FACEBOOK_APP_ID, FACEBOOK_OAUTH_REDIRECT_URI,
|
|
19
|
+
FACEBOOK_APP_SECRET, FACEBOOK_OAUTH_CODE)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Replace FACEBOOK_ACCESS_TOKEN with valid token with publish_stream extended permission
|
|
2
|
+
#
|
|
3
|
+
# Post to user's feed.
|
|
4
|
+
# curl -F 'access_token=...' -F 'message=Hello, Arjun. I like this new API.' https://graph.facebook.com/me/feed
|
|
5
|
+
# curl -F 'access_token=112157085478818|2.AlP5TBjZ9F6wOXPX_V0GTg__.3600.1273777200-756314021|NV7Dnuol59KbQr6W1axv6ZmytaI.' -F 'message=Hello, Arjun. I like this new API.' https://graph.facebook.com/me/feed
|
|
6
|
+
FACEBOOK_ACCESS_TOKEN = '112157085478818|2.Dq5RDPHxhsgaeScc_fiigg__.3600.1273831200-756314021|Tvm41skDwOdGR2O3Lz4owMVz1lM.'
|
|
7
|
+
|
|
8
|
+
require 'rubygems'
|
|
9
|
+
require 'pp'
|
|
10
|
+
require File.dirname(__FILE__) + '/../lib/fgraph'
|
|
11
|
+
|
|
12
|
+
# Post to current user's f eed
|
|
13
|
+
pp FGraph.publish_feed('me', :message => 'Hello. I like this new API.',
|
|
14
|
+
:access_token => FACEBOOK_ACCESS_TOKEN)
|
|
15
|
+
|
|
16
|
+
puts "Message successfully posted."
|
data/lib/fgraph.rb
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
require 'cgi'
|
|
3
|
+
require 'oauth2'
|
|
4
|
+
require 'fgraph/client'
|
|
5
|
+
|
|
6
|
+
module FGraph
|
|
7
|
+
include HTTParty
|
|
8
|
+
base_uri 'https://graph.facebook.com'
|
|
9
|
+
format :json
|
|
10
|
+
|
|
11
|
+
# Facebook Error
|
|
12
|
+
class FacebookError < StandardError
|
|
13
|
+
attr_reader :data
|
|
14
|
+
|
|
15
|
+
def initialize(data)
|
|
16
|
+
@data = data
|
|
17
|
+
super("(#{data['type']}) #{data['message']}")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class QueryParseError < FacebookError; end
|
|
22
|
+
class GraphMethodError < FacebookError; end
|
|
23
|
+
class OAuthError < FacebookError; end
|
|
24
|
+
class OAuthAccessTokenError < OAuthError; end
|
|
25
|
+
|
|
26
|
+
# Single object query.
|
|
27
|
+
#
|
|
28
|
+
# # Users: https://graph.facebook.com/btaylor (Bret Taylor)
|
|
29
|
+
# FGraph.object('btaylor')
|
|
30
|
+
#
|
|
31
|
+
# # Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
|
|
32
|
+
# FGraph.object('cocacola')
|
|
33
|
+
#
|
|
34
|
+
# # Fields selection with metadata
|
|
35
|
+
# FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
|
|
36
|
+
#
|
|
37
|
+
# # Page photos
|
|
38
|
+
# FGraph.object('/cocacola/photos')
|
|
39
|
+
# FGraph.object_photos('cocacola')
|
|
40
|
+
#
|
|
41
|
+
def self.object(id, options={})
|
|
42
|
+
perform_get("/#{id}", options)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# call-seq:
|
|
46
|
+
# FGraph.objects(id, id)
|
|
47
|
+
# FGraph.objects(id, id, options_hash)
|
|
48
|
+
#
|
|
49
|
+
# Multiple objects query.
|
|
50
|
+
#
|
|
51
|
+
# # Multiple users select: https://graph.facebook.com?ids=arjun,vernal
|
|
52
|
+
# FGraph.objects('arjun', 'vernel')
|
|
53
|
+
#
|
|
54
|
+
# # Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
|
|
55
|
+
# FGraph.objects('arjun', 'vernel', :fields => 'id,name,picture')
|
|
56
|
+
#
|
|
57
|
+
def self.objects(*args)
|
|
58
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
59
|
+
options = options.merge(:ids => args.join(','))
|
|
60
|
+
perform_get("/", options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# call-seq:
|
|
64
|
+
# FGraph.me(category)
|
|
65
|
+
# FGraph.me(category, options_hash)
|
|
66
|
+
#
|
|
67
|
+
# Returns current user object details.
|
|
68
|
+
#
|
|
69
|
+
# <tt>category</tt> - <tt>friends|home|feed|likes|movies|books|notes|photos|videos|events|groups</tt>
|
|
70
|
+
#
|
|
71
|
+
# # Current user: https://graph.facebook.com/me?access_token=...
|
|
72
|
+
# FGraph.me(:access_token => '...')
|
|
73
|
+
#
|
|
74
|
+
# # Current user's friends: https://graph.facebook.com/me/friends?access_token=...
|
|
75
|
+
# FGraph.me('friends', :access_token => '...')
|
|
76
|
+
# FGraph.me_friends(:access_token => '...')
|
|
77
|
+
#
|
|
78
|
+
def self.me(*args)
|
|
79
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
80
|
+
category = args.shift
|
|
81
|
+
|
|
82
|
+
path = "me"
|
|
83
|
+
path += "/#{category}" unless category.blank?
|
|
84
|
+
self.object(path, options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Request authorization from Facebok to fetch private data in the profile or permission to publish on a
|
|
88
|
+
# user's behalf. Returns Oauth Authorization URL, redirect to this URL to allow user to authorize your
|
|
89
|
+
# application from Facebook.
|
|
90
|
+
#
|
|
91
|
+
# <tt>client_id</tt> - Facebook API Key
|
|
92
|
+
# <tt>redirect_uri</tt> - Needs to begin with your app's Connect URL. For instance, if your Connect URL
|
|
93
|
+
# is http://www.example.com then your redirect URI could be http://www.example.com/oauth_redirect.
|
|
94
|
+
# <tt>scope (optional)</tt> -
|
|
95
|
+
#
|
|
96
|
+
# ==== Options
|
|
97
|
+
# * <tt>scope</tt> - Extended permission required to fetch private data or request permision to
|
|
98
|
+
# publish to Facebook on a user's behalf.
|
|
99
|
+
# * <tt>display</tt> - Other display type for authentication/authorization form, i.e. popup, touch.
|
|
100
|
+
#
|
|
101
|
+
# # https://graph.facebook.com/oauth/authorize?
|
|
102
|
+
# # client_id=...&
|
|
103
|
+
# # redirect_uri=http://www.example.com/oauth_redirect&
|
|
104
|
+
# # scope=publish_stream
|
|
105
|
+
#
|
|
106
|
+
# FGraph.oauth_authorize_url('[client id]', 'http://www.example.com/oauth_redirect', :scope =>
|
|
107
|
+
# 'publish_stream')
|
|
108
|
+
#
|
|
109
|
+
def self.oauth_authorize_url(client_id, redirect_uri, options={})
|
|
110
|
+
self.format_url('/oauth/authorize', {
|
|
111
|
+
:client_id => client_id,
|
|
112
|
+
:redirect_uri => redirect_uri
|
|
113
|
+
}.merge(options))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Return OAuth access_token. There are two types of access token, user access token and application
|
|
117
|
+
# access token.
|
|
118
|
+
#
|
|
119
|
+
# User access_token requires <tt>code</tt> and and <tt>redirect_uri</tt> options. <tt>code</tt> is
|
|
120
|
+
# the autorization code appended as query string to redirect URI when accessing oauth authorization URL.
|
|
121
|
+
#
|
|
122
|
+
# # https://graph.facebook.com/oauth/access_token?
|
|
123
|
+
# # client_id=...&
|
|
124
|
+
# # client_secret=...&
|
|
125
|
+
# # redirect_uri=http://www.example.com/oauth_redirect&
|
|
126
|
+
# # code=...
|
|
127
|
+
# FGraph.oauth_access_token('[client id]', '[client secret]',
|
|
128
|
+
# :redirect_uri => ''http://www.example.com/oauth_redirect',
|
|
129
|
+
# :code => '[authorization code]')
|
|
130
|
+
#
|
|
131
|
+
# Application access token requires <tt>:type => 'client_cred'</td> option. Used to access application
|
|
132
|
+
# insights data.
|
|
133
|
+
#
|
|
134
|
+
# # https://graph.facebook.com/oauth/access_token?
|
|
135
|
+
# # client_id=...&
|
|
136
|
+
# # client_secret=...&
|
|
137
|
+
# # type=client_cred
|
|
138
|
+
# FGraph.oauth_access_token('[client id]', '[client secret]', :type => 'client_cred')
|
|
139
|
+
#
|
|
140
|
+
def self.oauth_access_token(client_id, client_secret, options={})
|
|
141
|
+
url = self.format_url('/oauth/access_token', {
|
|
142
|
+
:client_id => client_id,
|
|
143
|
+
:client_secret => client_secret
|
|
144
|
+
}.merge(options || {}))
|
|
145
|
+
|
|
146
|
+
response = self.perform_get(url)
|
|
147
|
+
response_hash = {}
|
|
148
|
+
response.split('&').each do |value|
|
|
149
|
+
value_pair = value.split('=')
|
|
150
|
+
response_hash[value_pair[0]] = value_pair[1]
|
|
151
|
+
end
|
|
152
|
+
response_hash
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Shortcut to retrieve application access token.
|
|
156
|
+
def self.oauth_app_access_token(client_id, client_secret)
|
|
157
|
+
self.oauth_access_token(client_id, client_secret, :type => 'client_cred')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Publish to Facebook, you would need to be authorized and provide access token.
|
|
161
|
+
#
|
|
162
|
+
# # Post to user's feed.
|
|
163
|
+
# # curl -F 'access_token=...' \
|
|
164
|
+
# # -F 'message=Hello, Arjun. I like this new API.' \
|
|
165
|
+
# # https://graph.facebook.com/arjun/feed
|
|
166
|
+
# FGraph.publish('arjun/feed', :message => 'Hello, Arjun. I like this new API.',
|
|
167
|
+
# :access_token => '...')
|
|
168
|
+
# FGraph.publish_feed('arjun', :message => '...', :access_token => '...')
|
|
169
|
+
# FGraph.publish_feed('me', ':message => '...', :access_token => '...')
|
|
170
|
+
#
|
|
171
|
+
# ==== Options
|
|
172
|
+
#
|
|
173
|
+
# Method Description Options
|
|
174
|
+
# -------------------------------------------------------------------------------------
|
|
175
|
+
# /PROFILE_ID/feed write to the given profile's feed/wall :message, :picture,
|
|
176
|
+
# :link, :name, description
|
|
177
|
+
# /POST_ID/comments comment on the given post :message
|
|
178
|
+
# /POST_ID/likes like the given post none
|
|
179
|
+
# /PROFILE_ID/notes write a note on the given profile :message, :subject
|
|
180
|
+
# /PROFILE_ID/links write a link on the given profile :link, :message
|
|
181
|
+
# /EVENT_ID/attending attend the given event none
|
|
182
|
+
# /EVENT_ID/maybe maybe attend the given event none
|
|
183
|
+
# /EVENT_ID/declined decline the given event none
|
|
184
|
+
#
|
|
185
|
+
def self.publish(id, options={})
|
|
186
|
+
self.perform_post("/#{id}", options)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Delete objects in the graph.
|
|
190
|
+
#
|
|
191
|
+
# # DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
|
|
192
|
+
#
|
|
193
|
+
# FGraph.remove('[ID]')
|
|
194
|
+
# FGraph.remove('[ID]/likes')
|
|
195
|
+
# FGraph.remove_likes('[ID]')
|
|
196
|
+
#
|
|
197
|
+
def self.remove(id, options={})
|
|
198
|
+
self.perform_delete("/#{id}", options)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Search over all public objects in the social graph.
|
|
202
|
+
#
|
|
203
|
+
# # https://graph.facebook.com/search?q=watermelon&type=post
|
|
204
|
+
# FGraph.search('watermelon', :type => 'post')
|
|
205
|
+
# FGraph.search_post('watermelon')
|
|
206
|
+
#
|
|
207
|
+
# ==== Options
|
|
208
|
+
# * <tt>type</tt> - <tt>album|event|group|link|note|page|photo|post|status|user|video</tt>
|
|
209
|
+
# * <tt>limit</tt> - max no of records
|
|
210
|
+
# * <tt>offset</tt> - offset
|
|
211
|
+
# * <tt>until</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
|
212
|
+
def self.search(query, options={})
|
|
213
|
+
self.perform_get("/search", {
|
|
214
|
+
:q => query
|
|
215
|
+
}.merge(options|| {}))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Download insights data for your application.
|
|
219
|
+
#
|
|
220
|
+
# # https://graph.facebook.com/app_id/insights?access_token=...
|
|
221
|
+
# FGraph.insights('[app_id]', '[app_access_token]')
|
|
222
|
+
#
|
|
223
|
+
# # https://graph.facebook.com/app_id/insights/application_api_call/day?access_token=...
|
|
224
|
+
# FGraph.insights('[app_id]', '[app_access_token]', :metric_path => 'application_api_call/day')
|
|
225
|
+
#
|
|
226
|
+
# ==== Options
|
|
227
|
+
# * <tt>metric_path</tt> - e.g. application_api_calls/day
|
|
228
|
+
# * <tt>since</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
|
229
|
+
# * <tt>until</tt> - until (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
|
|
230
|
+
def self.insights(app_id, app_access_token, options={})
|
|
231
|
+
metric_path = options.delete(:metric_path)
|
|
232
|
+
|
|
233
|
+
path = "/#{app_id}/insights"
|
|
234
|
+
path += "/#{metric_path}" if metric_path
|
|
235
|
+
|
|
236
|
+
self.perform_get(path, {
|
|
237
|
+
:access_token => app_access_token
|
|
238
|
+
}.merge(options || {}))
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def self.perform_get(uri, options = {})
|
|
242
|
+
handle_response(get(uri, {:query => options}))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def self.perform_post(uri, options = {})
|
|
246
|
+
handle_response(post(uri, {:body => options}))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def self.perform_delete(uri, options = {})
|
|
250
|
+
handle_response(delete(uri, {:body => options}))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def self.handle_response(response)
|
|
254
|
+
# Check for error
|
|
255
|
+
return response unless response['error']
|
|
256
|
+
|
|
257
|
+
case response['error']['type']
|
|
258
|
+
when 'QueryParseException'
|
|
259
|
+
raise QueryParseError, response['error']
|
|
260
|
+
when 'GraphMethodException'
|
|
261
|
+
raise GraphMethodError, response['error']
|
|
262
|
+
when 'OAuthException'
|
|
263
|
+
raise OAuthError, response['error']
|
|
264
|
+
when 'OAuthAccessTokenException'
|
|
265
|
+
raise OAuthAccessTokenError, response['error']
|
|
266
|
+
else
|
|
267
|
+
raise FacebookError, response['error']
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def self.format_url(path, options={})
|
|
272
|
+
url = self.base_uri.dup
|
|
273
|
+
url << path
|
|
274
|
+
unless options.blank?
|
|
275
|
+
url << "?"
|
|
276
|
+
|
|
277
|
+
option_count = 0
|
|
278
|
+
|
|
279
|
+
stringified_options = {}
|
|
280
|
+
options.each do |key, value|
|
|
281
|
+
stringified_options[key.to_s] = value
|
|
282
|
+
end
|
|
283
|
+
options = stringified_options
|
|
284
|
+
|
|
285
|
+
options.each do |option|
|
|
286
|
+
next if option[1].blank?
|
|
287
|
+
url << "&" if option_count > 0
|
|
288
|
+
url << "#{option[0]}=#{CGI.escape(option[1].to_s)}"
|
|
289
|
+
option_count += 1
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
url
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def self.method_missing(name, *args, &block)
|
|
296
|
+
names = name.to_s.split('_')
|
|
297
|
+
super unless names.length > 1
|
|
298
|
+
|
|
299
|
+
case names.shift
|
|
300
|
+
when 'object'
|
|
301
|
+
# object_photos
|
|
302
|
+
self.object("#{args[0]}/#{names[0]}", args[1])
|
|
303
|
+
when 'me'
|
|
304
|
+
# me_photos
|
|
305
|
+
self.me(names[0], args[0])
|
|
306
|
+
when 'publish'
|
|
307
|
+
# publish_feed(id)
|
|
308
|
+
self.publish("#{args[0]}/#{names[0]}", args[1])
|
|
309
|
+
when 'remove'
|
|
310
|
+
# remove_feed(id)
|
|
311
|
+
self.remove("#{args[0]}/#{names[0]}", args[1])
|
|
312
|
+
when 'search'
|
|
313
|
+
# search_user(query)
|
|
314
|
+
options = args[1] || {}
|
|
315
|
+
options[:type] = names[0]
|
|
316
|
+
self.search(args[0], options)
|
|
317
|
+
else
|
|
318
|
+
super
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'oauth2'
|
|
2
|
+
|
|
3
|
+
module FGraph
|
|
4
|
+
|
|
5
|
+
# Facebook proxy class to call Facebook Graph API methods with default options.
|
|
6
|
+
# Please refer to FGraph method documentation for more information.
|
|
7
|
+
class Client
|
|
8
|
+
attr_reader :oauth_client, :client_id, :client_secret, :options
|
|
9
|
+
|
|
10
|
+
# Initialize Client with default options, so options are not required to be passed
|
|
11
|
+
# when calling respective Facebook Graph API methods.
|
|
12
|
+
#
|
|
13
|
+
# ==== Options
|
|
14
|
+
# * <tt>client_id</tt> - Application API key
|
|
15
|
+
# * <tt>client_secret</tt> - Application Secret
|
|
16
|
+
# * <tt>app_id</tt> - Application ID
|
|
17
|
+
# * <tt>access_token</tt> - Access token, required to publish to Facebook Graph or access
|
|
18
|
+
# current user profile.
|
|
19
|
+
# * <tt>app_access_token</tt> - Application access token, required to access Facebook insights.
|
|
20
|
+
# Auto generated if client_id and client_secret option are provided.
|
|
21
|
+
def initialize(options={})
|
|
22
|
+
@options = options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def oauth_authorize_url(redirect_uri)
|
|
26
|
+
FGraph.oauth_authorize_url(self.options[:client_id], redirect_uri)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def oauth_access_token(redirect_uri, code)
|
|
30
|
+
FGraph.oauth_access_token(self.options[:client_id], self.options[:client_secret],
|
|
31
|
+
:redirect_uri => redirect_uri, :code => code)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def oauth_app_access_token
|
|
35
|
+
FGraph.oauth_app_access_token(self.options[:client_id], self.options[:client_secret])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def object(id, options={})
|
|
39
|
+
FGraph.object(id, {:access_token => self.options[:access_token]}.merge(options || {}))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def objects(*args)
|
|
43
|
+
return if args.blank?
|
|
44
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
45
|
+
options[:access_token] = self.options[:access_token]
|
|
46
|
+
args << options
|
|
47
|
+
|
|
48
|
+
FGraph.objects(args)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def me(*args)
|
|
52
|
+
return if args.blank?
|
|
53
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
54
|
+
options[:access_token] = self.options[:access_token]
|
|
55
|
+
args << options
|
|
56
|
+
|
|
57
|
+
FGraph.me(args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def publish(id, options={})
|
|
61
|
+
FGraph.publish(id, {
|
|
62
|
+
:access_token => self.options[:access_token]
|
|
63
|
+
}.merge(options || {}))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remove(id, options={})
|
|
67
|
+
FGraph.remove(id, {
|
|
68
|
+
:access_token => self.options[:access_token]
|
|
69
|
+
}.merge(options || {}))
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def search(query, options={})
|
|
74
|
+
FGraph.search(query, options)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def insights(options={})
|
|
78
|
+
unless self.options[:app_access_token]
|
|
79
|
+
self.options[:app_access_token] = self.oauth_app_access_token
|
|
80
|
+
end
|
|
81
|
+
FGraph.insights(self.options[:app_id], self.options[:app_access_token], options)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def method_missing(name, *args, &block)
|
|
85
|
+
names = name.to_s.split('_')
|
|
86
|
+
super unless names.length > 1
|
|
87
|
+
|
|
88
|
+
case names.shift
|
|
89
|
+
when 'object'
|
|
90
|
+
# object_photos
|
|
91
|
+
self.object("#{args[0]}/#{names[0]}", args[1])
|
|
92
|
+
when 'me'
|
|
93
|
+
# me_photos
|
|
94
|
+
self.me(names[0], args[0])
|
|
95
|
+
when 'publish'
|
|
96
|
+
# publish_feed(id)
|
|
97
|
+
self.publish("#{args[0]}/#{names[0]}", args[1])
|
|
98
|
+
when 'remove'
|
|
99
|
+
# remove_feed(id)
|
|
100
|
+
self.remove("#{args[0]}/#{names[0]}", args[1])
|
|
101
|
+
when 'search'
|
|
102
|
+
# search_user(query)
|
|
103
|
+
options = args[1] || {}
|
|
104
|
+
options[:type] = names[0]
|
|
105
|
+
self.search(args[0], options)
|
|
106
|
+
else
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class ClientTest < Test::Unit::TestCase
|
|
4
|
+
FACEBOOK_API_KEY = '878116c4a4b79f25e4beb97ab096cc92'
|
|
5
|
+
FACEBOOK_APP_SECRET = '41f0e7ee8b6501dca1610de9926477c4'
|
|
6
|
+
FACEBOOK_APP_ID = '112157085578818'
|
|
7
|
+
FACEBOOK_OAUTH_REDIRECT_URI = 'http://www.example.com/oauth_redirect'
|
|
8
|
+
FACEBOOK_OAUTH_CODE = '2.0eXhebBSDTpoe08qIaocNQ__.3600.1273748400-503153225|caqygNb5Gobz6lpj3HXjlthDxds.'
|
|
9
|
+
FACEBOOK_OAUTH_ACCESS_TOKEN = "115187085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
|
10
|
+
FACEBOOK_OAUTH_APP_ACCESS_TOKEN = "112167085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
|
11
|
+
|
|
12
|
+
def fb_client
|
|
13
|
+
FGraph::Client.new(
|
|
14
|
+
:client_id => FACEBOOK_API_KEY,
|
|
15
|
+
:client_secret => FACEBOOK_APP_SECRET,
|
|
16
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context "FGraph::Client#oauth_authorize_url" do
|
|
21
|
+
should "call FGraph.oauth_authorize_url with :client_id option" do
|
|
22
|
+
FGraph.expects(:oauth_authorize_url).with(FACEBOOK_API_KEY, FACEBOOK_OAUTH_REDIRECT_URI)
|
|
23
|
+
fb_client.oauth_authorize_url(FACEBOOK_OAUTH_REDIRECT_URI)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "FGraph::Client#oauth_access_token" do
|
|
28
|
+
should "call FGraph.oauth_access_token with :client_id and :client_secret options" do
|
|
29
|
+
FGraph.expects(:oauth_access_token).with(FACEBOOK_API_KEY, FACEBOOK_APP_SECRET,
|
|
30
|
+
:redirect_uri => FACEBOOK_OAUTH_REDIRECT_URI, :code => FACEBOOK_OAUTH_CODE)
|
|
31
|
+
|
|
32
|
+
fb_client.oauth_access_token(FACEBOOK_OAUTH_REDIRECT_URI, FACEBOOK_OAUTH_CODE)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "FGraph::Client#object" do
|
|
37
|
+
should "call FGraph.object with :access_token option" do
|
|
38
|
+
object_id = '12345'
|
|
39
|
+
FGraph.expects(:object).with(object_id,
|
|
40
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN,
|
|
41
|
+
:fields => 'user_photos'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
fb_client.object(object_id, :fields => 'user_photos')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should "support #object_[category] method" do
|
|
48
|
+
client = fb_client
|
|
49
|
+
client.expects(:object).with('arun/photos', {:limit => 5})
|
|
50
|
+
client.object_photos('arun', {:limit => 5})
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "FGraph::Client#objects" do
|
|
55
|
+
should "call FGraph.objects with :access_token option" do
|
|
56
|
+
FGraph.expects(:objects).with(['1', '2', {
|
|
57
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN,
|
|
58
|
+
:fields => 'user_photos'
|
|
59
|
+
}])
|
|
60
|
+
|
|
61
|
+
fb_client.objects('1', '2', :fields => 'user_photos')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "FGraph::Client#me" do
|
|
66
|
+
should "call FGraph.me with :access_token option" do
|
|
67
|
+
FGraph.expects(:me).with([{
|
|
68
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN,
|
|
69
|
+
:fields => 'user_photos'
|
|
70
|
+
}])
|
|
71
|
+
|
|
72
|
+
fb_client.me(:fields => 'user_photos')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
should "support #me_[category] method" do
|
|
76
|
+
client = fb_client
|
|
77
|
+
client.expects(:me).with('photos', {:limit => 5})
|
|
78
|
+
client.me_photos(:limit => 5)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context "FGraph::Client#publish" do
|
|
83
|
+
should "call FGraph.publish with :access_token option" do
|
|
84
|
+
id = '1'
|
|
85
|
+
FGraph.expects(:publish).with(id, {
|
|
86
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN,
|
|
87
|
+
:message => 'hello'
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
fb_client.publish(id, :message => 'hello')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
should "support publish_[category] method" do
|
|
94
|
+
client = fb_client
|
|
95
|
+
client.expects(:publish).with('me/feed', {:limit => 5})
|
|
96
|
+
client.publish_feed('me', {:limit => 5})
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context "FGraph::Client#remove" do
|
|
101
|
+
should "call FGraph.remove with :access_token option" do
|
|
102
|
+
id = '1'
|
|
103
|
+
FGraph.expects(:remove).with(id, {
|
|
104
|
+
:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
fb_client.remove(id)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
should "support remove_[category] method" do
|
|
111
|
+
client = fb_client
|
|
112
|
+
client.expects(:remove).with('12345/likes', {:limit => 5})
|
|
113
|
+
client.remove_likes('12345', :limit => 5)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context "FGraph::Client#search" do
|
|
118
|
+
should "call FGraph.search with options" do
|
|
119
|
+
query = 'watermelon'
|
|
120
|
+
options = {:limit => 5}
|
|
121
|
+
FGraph.expects(:search).with(query, options)
|
|
122
|
+
|
|
123
|
+
fb_client.search(query, options)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
should "support dynamic method search_[type] method" do
|
|
127
|
+
client = fb_client
|
|
128
|
+
client.expects(:search).with('watermelon', {
|
|
129
|
+
:type => 'post'
|
|
130
|
+
})
|
|
131
|
+
client.search_post('watermelon')
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context "FGraph::Client#insights" do
|
|
136
|
+
should "auto populate :app_id and :oauth_app_access_token" do
|
|
137
|
+
client = fb_client
|
|
138
|
+
client.options[:app_id] = FACEBOOK_APP_ID
|
|
139
|
+
client.options[:app_access_token] = FACEBOOK_OAUTH_APP_ACCESS_TOKEN
|
|
140
|
+
|
|
141
|
+
FGraph.expects(:insights).with(FACEBOOK_APP_ID, FACEBOOK_OAUTH_APP_ACCESS_TOKEN, {})
|
|
142
|
+
client.insights
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
should "auto retrieve :oauth_app_access_token option" do
|
|
146
|
+
client = fb_client
|
|
147
|
+
|
|
148
|
+
client.expects(:oauth_app_access_token).returns(FACEBOOK_OAUTH_APP_ACCESS_TOKEN)
|
|
149
|
+
FGraph.expects(:insights).with(nil, FACEBOOK_OAUTH_APP_ACCESS_TOKEN, {
|
|
150
|
+
:metric_path => 'application_api_calls/day'
|
|
151
|
+
})
|
|
152
|
+
client.insights(:metric_path => 'application_api_calls/day')
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/test/fgraph_test.rb
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class FGraphTest < Test::Unit::TestCase
|
|
4
|
+
FACEBOOK_API_KEY = '878116c4a4b79f25e4beb97ab096cc92'
|
|
5
|
+
FACEBOOK_APP_SECRET = '41f0e7ee8b6501dca1610de9926477c4'
|
|
6
|
+
FACEBOOK_APP_ID = '112157085578818'
|
|
7
|
+
FACEBOOK_OAUTH_REDIRECT_URI = 'http://www.example.com/oauth_redirect'
|
|
8
|
+
FACEBOOK_OAUTH_CODE = '2.0eXhebBSDTpoe08qIaocNQ__.3600.1273748400-503153225|caqygNb5Gobz6lpj3HXjlthDxds.'
|
|
9
|
+
FACEBOOK_OAUTH_ACCESS_TOKEN = "115187085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
|
10
|
+
FACEBOOK_OAUTH_APP_ACCESS_TOKEN = "112167085478818|rDIv_5zgjCSM_fWBv5Z-lQr5gFk."
|
|
11
|
+
|
|
12
|
+
context "FGraph.object" do
|
|
13
|
+
should "return object hash" do
|
|
14
|
+
stub_get('/cocacola', 'object_cocacola.json')
|
|
15
|
+
object = FGraph.object('cocacola')
|
|
16
|
+
|
|
17
|
+
object.should_not be_nil
|
|
18
|
+
object['name'].should == 'Coca-Cola'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
should "call handle_response" do
|
|
22
|
+
stub_get('/cocacola', 'object_cocacola.json')
|
|
23
|
+
FGraph.expects(:handle_response).once
|
|
24
|
+
object = FGraph.object('cocacola')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should "parse options into get options" do
|
|
28
|
+
options = {:fields => 'id,name,picture'}
|
|
29
|
+
FGraph.expects(:perform_get).with('/cocacola', options)
|
|
30
|
+
FGraph.object('cocacola', options)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "FGraph.objects" do
|
|
35
|
+
should "call perform_get with ids and query options" do
|
|
36
|
+
options = {:fields => 'id,name'}
|
|
37
|
+
FGraph.expects(:perform_get).with('/', options.merge(:ids => 'herry,john'))
|
|
38
|
+
FGraph.objects('herry', 'john', options)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "FGraph.me" do
|
|
43
|
+
access_token = {:access_token => FACEBOOK_OAUTH_ACCESS_TOKEN}
|
|
44
|
+
|
|
45
|
+
should "get object with /me path" do
|
|
46
|
+
FGraph.expects(:object).with('me', access_token)
|
|
47
|
+
FGraph.me(access_token)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
should "get object with /me/likes path" do
|
|
51
|
+
FGraph.expects(:object).with('me/likes', access_token)
|
|
52
|
+
FGraph.me('likes', access_token)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "FGraph.oauth_authorize_url" do
|
|
57
|
+
should "should call format_url with appropriate hash" do
|
|
58
|
+
FGraph.expects(:format_url).with('/oauth/authorize', {
|
|
59
|
+
:client_id => FACEBOOK_API_KEY,
|
|
60
|
+
:redirect_uri => FACEBOOK_OAUTH_REDIRECT_URI
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
FGraph.oauth_authorize_url(FACEBOOK_API_KEY, FACEBOOK_OAUTH_REDIRECT_URI)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
should "should call format_url with options" do
|
|
67
|
+
FGraph.expects(:format_url).with('/oauth/authorize', {
|
|
68
|
+
:client_id => FACEBOOK_API_KEY,
|
|
69
|
+
:redirect_uri => FACEBOOK_OAUTH_REDIRECT_URI,
|
|
70
|
+
:scope => 'user_photos'
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
FGraph.oauth_authorize_url(FACEBOOK_API_KEY, FACEBOOK_OAUTH_REDIRECT_URI,
|
|
74
|
+
:scope => 'user_photos')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "FGraph.oauth_access_token" do
|
|
79
|
+
should "return user access token and expires" do
|
|
80
|
+
stub_get(FGraph.format_url('/oauth/access_token', {
|
|
81
|
+
:client_id => FACEBOOK_API_KEY,
|
|
82
|
+
:client_secret => FACEBOOK_APP_SECRET,
|
|
83
|
+
:redirect_uri => FACEBOOK_OAUTH_REDIRECT_URI,
|
|
84
|
+
:code => FACEBOOK_OAUTH_CODE
|
|
85
|
+
}), 'access_token.txt')
|
|
86
|
+
|
|
87
|
+
token = FGraph.oauth_access_token(FACEBOOK_API_KEY, FACEBOOK_APP_SECRET,
|
|
88
|
+
:redirect_uri => FACEBOOK_OAUTH_REDIRECT_URI,
|
|
89
|
+
:code => FACEBOOK_OAUTH_CODE)
|
|
90
|
+
|
|
91
|
+
token['access_token'].should == 'thisisanaccesstoken'
|
|
92
|
+
token['expires'].should == '4000'
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "FGraph.publish" do
|
|
97
|
+
options = { :message => 'test message'}
|
|
98
|
+
|
|
99
|
+
should "call perform_post" do
|
|
100
|
+
FGraph.expects(:perform_post).with("/me/feed", options)
|
|
101
|
+
FGraph.publish('me/feed', options)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
should "have publish_[category] method" do
|
|
105
|
+
FGraph.expects(:publish).with('me/feed', options)
|
|
106
|
+
FGraph.publish_feed('me', options)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context "FGraph.delete" do
|
|
111
|
+
options = {}
|
|
112
|
+
|
|
113
|
+
should "call perform_delete" do
|
|
114
|
+
FGraph.expects(:perform_delete).with('/12345', options)
|
|
115
|
+
FGraph.remove('12345', options)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
should "support remove_[category] method" do
|
|
119
|
+
FGraph.expects(:remove).with('12345/likes', options)
|
|
120
|
+
FGraph.remove_likes('12345', options)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "FGraph.search" do
|
|
125
|
+
should "call perform_get('/search')" do
|
|
126
|
+
FGraph.expects(:perform_get).with('/search', {
|
|
127
|
+
:q => 'watermelon',
|
|
128
|
+
:type => 'post'
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
FGraph.search('watermelon', :type => 'post')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
should "support dynamic method search_[type] method" do
|
|
135
|
+
FGraph.expects(:search).with('watermelon', {
|
|
136
|
+
:type => 'post'
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
FGraph.search_post('watermelon')
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "Facebook.insights" do
|
|
144
|
+
should "call perform_get('/[app_id]/insights')" do
|
|
145
|
+
FGraph.expects(:perform_get).with("/#{FACEBOOK_APP_ID}/insights", {
|
|
146
|
+
:access_token => FACEBOOK_OAUTH_APP_ACCESS_TOKEN
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
FGraph.insights(FACEBOOK_APP_ID, FACEBOOK_OAUTH_APP_ACCESS_TOKEN)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
should "process :metric_path option" do
|
|
153
|
+
FGraph.expects(:perform_get).with("/#{FACEBOOK_APP_ID}/insights/application_api_call/day", {
|
|
154
|
+
:access_token => FACEBOOK_OAUTH_APP_ACCESS_TOKEN
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
FGraph.insights(FACEBOOK_APP_ID, FACEBOOK_OAUTH_APP_ACCESS_TOKEN, {
|
|
158
|
+
:metric_path => 'application_api_call/day'
|
|
159
|
+
})
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context "FGraph.method_missing" do
|
|
164
|
+
options = options = {:filter => 'id,name,picture'}
|
|
165
|
+
|
|
166
|
+
should "auto map object_[category] method" do
|
|
167
|
+
FGraph.expects(:object).with('arun/photos', options)
|
|
168
|
+
FGraph.object_photos('arun', options)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
should "auto map me_[category] method" do
|
|
172
|
+
FGraph.expects(:me).with('photos', options)
|
|
173
|
+
FGraph.me_photos(options)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
should "raise no method error if missing method name does not start with object_ or me_" do
|
|
177
|
+
lambda do
|
|
178
|
+
FGraph.xyz_photos
|
|
179
|
+
end.should raise_error(NoMethodError)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context "FGraph.format_url" do
|
|
184
|
+
should "return URL without query string" do
|
|
185
|
+
formatted_url = FGraph.format_url('/test')
|
|
186
|
+
formatted_url.should == "https://graph.facebook.com/test"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
should "return URL with query string with escaped value" do
|
|
190
|
+
formatted_url = FGraph.format_url('/test', {:username => 'john lim'})
|
|
191
|
+
formatted_url.should == "https://graph.facebook.com/test?username=john+lim"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
should "return URL with multiple options" do
|
|
195
|
+
formatted_url = FGraph.format_url('/test', {:username => 'john', :age => 20})
|
|
196
|
+
formatted_url.should =~ /username=john/
|
|
197
|
+
formatted_url.should =~ /age=20/
|
|
198
|
+
formatted_url.should =~ /&/
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
should "return URL without empty options" do
|
|
202
|
+
formatted_url = FGraph.format_url('/test', {:username => 'john', :age => nil})
|
|
203
|
+
formatted_url.should == "https://graph.facebook.com/test?username=john"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
context "FGraph.handle_response" do
|
|
208
|
+
should "raise QueryParseError" do
|
|
209
|
+
lambda do
|
|
210
|
+
object = FGraph.handle_response(response_error('QueryParseException'))
|
|
211
|
+
end.should raise_error(FGraph::QueryParseError)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
should "raise GraphMethodError" do
|
|
215
|
+
lambda do
|
|
216
|
+
object = FGraph.handle_response(response_error('GraphMethodException'))
|
|
217
|
+
end.should raise_error(FGraph::GraphMethodError)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
should "raise OAuthError" do
|
|
221
|
+
lambda do
|
|
222
|
+
object = FGraph.handle_response(response_error('OAuthException'))
|
|
223
|
+
end.should raise_error(FGraph::OAuthError)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
should "raise OAuthAccessTokenError" do
|
|
227
|
+
lambda do
|
|
228
|
+
object = FGraph.handle_response(response_error('OAuthAccessTokenException'))
|
|
229
|
+
end.should raise_error(FGraph::OAuthAccessTokenError)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def response_error(type, msg=nil)
|
|
234
|
+
{'error' => { 'type' => type, 'message' => msg}}
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
access_token=thisisanaccesstoken&expires=4000
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "40796308305",
|
|
3
|
+
"name": "Coca-Cola",
|
|
4
|
+
"picture": "http://profile.ak.fbcdn.net/object3/1853/100/s40796308305_2334.jpg",
|
|
5
|
+
"link": "http://www.facebook.com/coca-cola",
|
|
6
|
+
"category": "Consumer_products",
|
|
7
|
+
"username": "coca-cola",
|
|
8
|
+
"products": "Coca-Cola is the most popular and biggest-selling soft drink in history, as well as the best-known product in the world.\n\nCreated in Atlanta, Georgia, by Dr. John S. Pemberton, Coca-Cola was first offered as a fountain beverage by mixing Coca-Cola syrup with carbonated water. Coca-Cola was introduced in 1886, patented in 1887, registered as a trademark in 1893 and by 1895 it was being sold in every state and territory in the United States. In 1899, The Coca-Cola Company began franchised bottling operations in the United States.\n\nCoca-Cola might owe its origins to the United States, but its popularity has made it truly universal. Today, you can find Coca-Cola in virtually every part of the world.",
|
|
9
|
+
"fan_count": 5445797
|
|
10
|
+
}
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'shoulda'
|
|
3
|
+
require 'matchy'
|
|
4
|
+
require 'mocha'
|
|
5
|
+
require 'fakeweb'
|
|
6
|
+
require 'pp'
|
|
7
|
+
|
|
8
|
+
# FakeWeb.allow_net_connect = true
|
|
9
|
+
# FakeWeb.allow_net_connect = false
|
|
10
|
+
|
|
11
|
+
require File.dirname(__FILE__) + '/../lib/fgraph'
|
|
12
|
+
|
|
13
|
+
def stub_get(url, filename, status=nil)
|
|
14
|
+
options = {:body => read_fixture(filename)}
|
|
15
|
+
options.merge!({:status => status}) unless status.nil?
|
|
16
|
+
FakeWeb.register_uri(:get, graph_url(url), options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def stub_post(url, filename)
|
|
20
|
+
FakeWeb.register_uri(:post, graph_url(url), :body => read_fixture(filename))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stub_put(url, filename)
|
|
24
|
+
FakeWeb.register_uri(:put, graph_url(url), :body => read_fixture(filename))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read_fixture(filename)
|
|
28
|
+
return "" if filename == ""
|
|
29
|
+
File.read(File.dirname(__FILE__) + "/fixtures/" + filename)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def graph_url(url)
|
|
33
|
+
url =~ /^http/ ? url : "http://graph.facebook.com#{url}"
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fgraph
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Herryanto Siatono
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-05-20 00:00:00 +08:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: httparty
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ~>
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
segments:
|
|
28
|
+
- 0
|
|
29
|
+
- 5
|
|
30
|
+
- 0
|
|
31
|
+
version: 0.5.0
|
|
32
|
+
type: :runtime
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: shoulda
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ~>
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
segments:
|
|
42
|
+
- 2
|
|
43
|
+
- 10
|
|
44
|
+
- 0
|
|
45
|
+
version: 2.10.0
|
|
46
|
+
type: :development
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
- !ruby/object:Gem::Dependency
|
|
49
|
+
name: jnunemaker-matchy
|
|
50
|
+
prerelease: false
|
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ~>
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
segments:
|
|
56
|
+
- 0
|
|
57
|
+
- 4
|
|
58
|
+
- 0
|
|
59
|
+
version: 0.4.0
|
|
60
|
+
type: :development
|
|
61
|
+
version_requirements: *id003
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: mocha
|
|
64
|
+
prerelease: false
|
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ~>
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
segments:
|
|
70
|
+
- 0
|
|
71
|
+
- 9
|
|
72
|
+
- 0
|
|
73
|
+
version: 0.9.0
|
|
74
|
+
type: :development
|
|
75
|
+
version_requirements: *id004
|
|
76
|
+
- !ruby/object:Gem::Dependency
|
|
77
|
+
name: fakeweb
|
|
78
|
+
prerelease: false
|
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ~>
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
segments:
|
|
84
|
+
- 1
|
|
85
|
+
- 2
|
|
86
|
+
- 0
|
|
87
|
+
version: 1.2.0
|
|
88
|
+
type: :development
|
|
89
|
+
version_requirements: *id005
|
|
90
|
+
description: Ruby Facebook Graph API
|
|
91
|
+
email: herryanto@gmail.com
|
|
92
|
+
executables: []
|
|
93
|
+
|
|
94
|
+
extensions: []
|
|
95
|
+
|
|
96
|
+
extra_rdoc_files:
|
|
97
|
+
- README.rdoc
|
|
98
|
+
files:
|
|
99
|
+
- History
|
|
100
|
+
- License
|
|
101
|
+
- README.rdoc
|
|
102
|
+
- Rakefile
|
|
103
|
+
- VERSION.yml
|
|
104
|
+
- examples/get_access_token.rb
|
|
105
|
+
- examples/publish_feed.rb
|
|
106
|
+
- lib/fgraph.rb
|
|
107
|
+
- lib/fgraph/client.rb
|
|
108
|
+
- test/fgraph/client_test.rb
|
|
109
|
+
- test/fgraph_test.rb
|
|
110
|
+
- test/fixtures/access_token.txt
|
|
111
|
+
- test/fixtures/object_cocacola.json
|
|
112
|
+
- test/test_helper.rb
|
|
113
|
+
has_rdoc: true
|
|
114
|
+
homepage: http://github.com/jugend/fgraph
|
|
115
|
+
licenses: []
|
|
116
|
+
|
|
117
|
+
post_install_message:
|
|
118
|
+
rdoc_options:
|
|
119
|
+
- --charset=UTF-8
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
segments:
|
|
127
|
+
- 0
|
|
128
|
+
version: "0"
|
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
segments:
|
|
134
|
+
- 0
|
|
135
|
+
version: "0"
|
|
136
|
+
requirements: []
|
|
137
|
+
|
|
138
|
+
rubyforge_project:
|
|
139
|
+
rubygems_version: 1.3.6
|
|
140
|
+
signing_key:
|
|
141
|
+
specification_version: 3
|
|
142
|
+
summary: Ruby Facebook Graph API
|
|
143
|
+
test_files:
|
|
144
|
+
- test/fgraph/client_test.rb
|
|
145
|
+
- test/fgraph_test.rb
|
|
146
|
+
- test/test_helper.rb
|
|
147
|
+
- examples/get_access_token.rb
|
|
148
|
+
- examples/publish_feed.rb
|