line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Facebook::OpenGraph; |
2
|
33
|
|
|
33
|
|
2701565
|
use strict; |
|
33
|
|
|
|
|
234
|
|
|
33
|
|
|
|
|
949
|
|
3
|
33
|
|
|
33
|
|
184
|
use warnings; |
|
33
|
|
|
|
|
63
|
|
|
33
|
|
|
|
|
789
|
|
4
|
33
|
|
|
33
|
|
737
|
use 5.008001; |
|
33
|
|
|
|
|
115
|
|
5
|
|
|
|
|
|
|
|
6
|
33
|
|
|
33
|
|
13631
|
use Facebook::OpenGraph::Response; |
|
33
|
|
|
|
|
89
|
|
|
33
|
|
|
|
|
1073
|
|
7
|
33
|
|
|
33
|
|
15920
|
use HTTP::Request::Common; |
|
33
|
|
|
|
|
756530
|
|
|
33
|
|
|
|
|
2428
|
|
8
|
33
|
|
|
33
|
|
265
|
use URI; |
|
33
|
|
|
|
|
75
|
|
|
33
|
|
|
|
|
775
|
|
9
|
33
|
|
|
33
|
|
6474
|
use Furl::HTTP; |
|
33
|
|
|
|
|
194080
|
|
|
33
|
|
|
|
|
1154
|
|
10
|
33
|
|
|
33
|
|
15317
|
use Data::Recursive::Encode; |
|
33
|
|
|
|
|
392766
|
|
|
33
|
|
|
|
|
1304
|
|
11
|
33
|
|
|
33
|
|
255
|
use JSON 2 (); |
|
33
|
|
|
|
|
769
|
|
|
33
|
|
|
|
|
773
|
|
12
|
33
|
|
|
33
|
|
177
|
use Carp qw(croak); |
|
33
|
|
|
|
|
73
|
|
|
33
|
|
|
|
|
1667
|
|
13
|
33
|
|
|
33
|
|
16765
|
use Digest::SHA qw(hmac_sha256 hmac_sha256_hex); |
|
33
|
|
|
|
|
97100
|
|
|
33
|
|
|
|
|
2849
|
|
14
|
33
|
|
|
33
|
|
14787
|
use MIME::Base64::URLSafe qw(urlsafe_b64decode); |
|
33
|
|
|
|
|
44215
|
|
|
33
|
|
|
|
|
2004
|
|
15
|
33
|
|
|
33
|
|
238
|
use Scalar::Util qw(blessed); |
|
33
|
|
|
|
|
113
|
|
|
33
|
|
|
|
|
111595
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '1.30'; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub new { |
20
|
66
|
|
|
66
|
1
|
151669
|
my $class = shift; |
21
|
66
|
|
100
|
|
|
310
|
my $args = shift || +{}; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
return bless +{ |
24
|
|
|
|
|
|
|
app_id => $args->{app_id}, |
25
|
|
|
|
|
|
|
secret => $args->{secret}, |
26
|
|
|
|
|
|
|
namespace => $args->{namespace}, |
27
|
|
|
|
|
|
|
access_token => $args->{access_token}, |
28
|
|
|
|
|
|
|
redirect_uri => $args->{redirect_uri}, |
29
|
|
|
|
|
|
|
batch_limit => $args->{batch_limit} || 50, |
30
|
|
|
|
|
|
|
is_beta => $args->{is_beta} || 0, |
31
|
|
|
|
|
|
|
json => $args->{json} || JSON->new->utf8, |
32
|
|
|
|
|
|
|
use_appsecret_proof => $args->{use_appsecret_proof} || 0, |
33
|
|
|
|
|
|
|
use_post_method => $args->{use_post_method} || 0, |
34
|
|
|
|
|
|
|
version => $args->{version} || undef, |
35
|
66
|
|
100
|
|
|
2362
|
ua => $args->{ua} || Furl::HTTP->new( |
|
|
|
100
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
36
|
|
|
|
|
|
|
capture_request => 1, |
37
|
|
|
|
|
|
|
agent => __PACKAGE__ . '/' . $VERSION, |
38
|
|
|
|
|
|
|
), |
39
|
|
|
|
|
|
|
}, $class; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# accessors |
43
|
33
|
|
|
33
|
1
|
242
|
sub app_id { shift->{app_id} } |
44
|
29
|
|
|
29
|
1
|
253
|
sub secret { shift->{secret} } |
45
|
42
|
|
|
42
|
1
|
801
|
sub ua { shift->{ua} } |
46
|
4
|
|
|
4
|
1
|
23
|
sub namespace { shift->{namespace} } |
47
|
86
|
|
|
86
|
1
|
2861
|
sub access_token { shift->{access_token} } |
48
|
16
|
|
|
16
|
1
|
89
|
sub redirect_uri { shift->{redirect_uri} } |
49
|
17
|
|
|
17
|
1
|
337
|
sub batch_limit { shift->{batch_limit} } |
50
|
61
|
|
|
61
|
1
|
240
|
sub is_beta { shift->{is_beta} } |
51
|
68
|
|
|
68
|
1
|
1312
|
sub json { shift->{json} } |
52
|
42
|
|
|
42
|
1
|
147
|
sub use_appsecret_proof { shift->{use_appsecret_proof} } |
53
|
2
|
|
|
2
|
1
|
10
|
sub use_post_method { shift->{use_post_method} } |
54
|
63
|
|
|
63
|
1
|
274
|
sub version { shift->{version} } |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub uri { |
57
|
46
|
|
|
46
|
1
|
15264
|
my $self = shift; |
58
|
|
|
|
|
|
|
|
59
|
46
|
100
|
|
|
|
133
|
my $base = $self->is_beta ? 'https://graph.beta.facebook.com/' |
60
|
|
|
|
|
|
|
: 'https://graph.facebook.com/' |
61
|
|
|
|
|
|
|
; |
62
|
|
|
|
|
|
|
|
63
|
46
|
|
|
|
|
168
|
return $self->_uri($base, @_); |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub video_uri { |
67
|
9
|
|
|
9
|
1
|
15532
|
my $self = shift; |
68
|
|
|
|
|
|
|
|
69
|
9
|
100
|
|
|
|
23
|
my $base = $self->is_beta ? 'https://graph-video.beta.facebook.com/' |
70
|
|
|
|
|
|
|
: 'https://graph-video.facebook.com/' |
71
|
|
|
|
|
|
|
; |
72
|
|
|
|
|
|
|
|
73
|
9
|
|
|
|
|
43
|
return $self->_uri($base, @_); |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub site_uri { |
77
|
4
|
|
|
4
|
1
|
7
|
my $self = shift; |
78
|
|
|
|
|
|
|
|
79
|
4
|
100
|
|
|
|
17
|
my $base = $self->is_beta ? 'https://www.beta.facebook.com/' |
80
|
|
|
|
|
|
|
: 'https://www.facebook.com/' |
81
|
|
|
|
|
|
|
; |
82
|
|
|
|
|
|
|
|
83
|
4
|
|
|
|
|
15
|
return $self->_uri($base, @_); |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
sub _uri { |
87
|
59
|
|
|
59
|
|
170
|
my ($self, $base, $path, $param_ref) = @_; |
88
|
59
|
|
100
|
|
|
381
|
my $uri = URI->new_abs($path || '/', $base); |
89
|
|
|
|
|
|
|
$uri->query_form(+{ |
90
|
|
|
|
|
|
|
$uri->query_form, # when given $path is like /foo?bar=bazz |
91
|
59
|
100
|
|
|
|
209418
|
%{ $param_ref || +{} }, # additional query parameter |
|
59
|
|
|
|
|
1403
|
|
92
|
|
|
|
|
|
|
}); |
93
|
|
|
|
|
|
|
|
94
|
59
|
|
|
|
|
3551
|
return $uri; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# Login for Games on Facebook > Checking Login Status > Parsing the Signed Request |
98
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/using-login-with-games |
99
|
|
|
|
|
|
|
sub parse_signed_request { |
100
|
5
|
|
|
5
|
1
|
186
|
my ($self, $signed_request) = @_; |
101
|
5
|
50
|
|
|
|
14
|
croak 'signed_request is not given' unless $signed_request; |
102
|
5
|
100
|
|
|
|
13
|
croak 'secret key must be set' unless $self->secret; |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# "1. Split the signed request into two parts delineated by a '.' character |
105
|
|
|
|
|
|
|
# (eg. 238fsdfsd.oijdoifjsidf899)" |
106
|
4
|
|
|
|
|
19
|
my ($enc_sig, $payload) = split(m{ \. }xms, $signed_request); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# "2. Decode the first part - the encoded signature - from base64url" |
109
|
4
|
|
|
|
|
18
|
my $sig = urlsafe_b64decode($enc_sig); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# "3. Decode the second part - the 'payload' - from base64url and then |
112
|
|
|
|
|
|
|
# decode the resultant JSON object" |
113
|
4
|
|
|
|
|
73
|
my $val = $self->json->decode(urlsafe_b64decode($payload)); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# "It specifically uses HMAC-SHA256 encoding, which you can again use with |
116
|
|
|
|
|
|
|
# most programming languages." |
117
|
|
|
|
|
|
|
croak 'algorithm must be HMAC-SHA256' |
118
|
4
|
50
|
|
|
|
86
|
unless uc( $val->{algorithm} ) eq 'HMAC-SHA256'; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# "You can compare this encoded signature with an expected signature using |
121
|
|
|
|
|
|
|
# the payload you received as well as the app secret which is known only to |
122
|
|
|
|
|
|
|
# your and ensure that they match." |
123
|
4
|
|
|
|
|
11
|
my $expected_sig = hmac_sha256($payload, $self->secret); |
124
|
4
|
50
|
|
|
|
24
|
croak 'Signature does not match' unless $sig eq $expected_sig; |
125
|
|
|
|
|
|
|
|
126
|
4
|
|
|
|
|
14
|
return $val; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# Detailed flow is described here. |
130
|
|
|
|
|
|
|
# Manually Build a Login Flow > Logging people in > Invoking the login dialog |
131
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/ |
132
|
|
|
|
|
|
|
# |
133
|
|
|
|
|
|
|
# Parameters for login dialog are shown here. |
134
|
|
|
|
|
|
|
# Login Dialog > Parameters |
135
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/reference/dialogs/oauth/ |
136
|
|
|
|
|
|
|
sub auth_uri { |
137
|
6
|
|
|
6
|
1
|
347
|
my ($self, $param_ref) = @_; |
138
|
6
|
|
100
|
|
|
18
|
$param_ref ||= +{}; |
139
|
6
|
100
|
66
|
|
|
14
|
croak 'redirect_uri and app_id must be set' |
140
|
|
|
|
|
|
|
unless $self->redirect_uri && $self->app_id; |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# "A comma separated list of permission names which you would like people |
143
|
|
|
|
|
|
|
# to grant your app." |
144
|
5
|
100
|
|
|
|
19
|
if (my $scope_ref = ref $param_ref->{scope}) { |
145
|
4
|
100
|
|
|
|
20
|
croak 'scope must be string or array ref' unless $scope_ref eq 'ARRAY'; |
146
|
3
|
|
|
|
|
6
|
$param_ref->{scope} = join q{,}, @{ $param_ref->{scope} }; |
|
3
|
|
|
|
|
10
|
|
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# "The URL to redirect to after a button is clicked or tapped in the |
150
|
|
|
|
|
|
|
# dialog." |
151
|
4
|
|
|
|
|
9
|
$param_ref->{redirect_uri} = $self->redirect_uri; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# "Your App ID. This is called client_id instead of app_id for this |
154
|
|
|
|
|
|
|
# particular method in order to be compliant with the OAuth 2.0 |
155
|
|
|
|
|
|
|
# specification." |
156
|
4
|
|
|
|
|
9
|
$param_ref->{client_id} = $self->app_id; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# "If you are using the URL redirect dialog implementation, then this will |
159
|
|
|
|
|
|
|
# be a full page display, shown within Facebook.com. This display type is |
160
|
|
|
|
|
|
|
# called page." |
161
|
4
|
|
50
|
|
|
18
|
$param_ref->{display} ||= 'page'; |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# "Response data is included as URL parameters and contains code parameter |
164
|
|
|
|
|
|
|
# (an encrypted string unique to each login request). This is the default |
165
|
|
|
|
|
|
|
# behaviour if this parameter is not specified." |
166
|
4
|
|
50
|
|
|
16
|
$param_ref->{response_type} ||= 'code'; |
167
|
|
|
|
|
|
|
|
168
|
4
|
|
|
|
|
10
|
my $uri = $self->site_uri('/dialog/oauth', $param_ref); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Platform Versioning > Making Versioned Requests > Dialogs. |
171
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/apps/versions#dialogs |
172
|
4
|
|
|
|
|
13
|
$uri->path( $self->gen_versioned_path($uri->path) ); |
173
|
|
|
|
|
|
|
|
174
|
4
|
|
|
|
|
143
|
return $uri->as_string; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub set_access_token { |
178
|
4
|
|
|
4
|
1
|
15
|
my ($self, $token) = @_; |
179
|
4
|
|
|
|
|
44
|
$self->{access_token} = $token; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# Access Tokens > App Tokens |
183
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens |
184
|
|
|
|
|
|
|
sub get_app_token { |
185
|
3
|
|
|
3
|
1
|
149
|
my $self = shift; |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# Document does not mention what grant_type is all about or what values can |
188
|
|
|
|
|
|
|
# be set, but RFC 6749 covers the basic idea of grant types and its Section |
189
|
|
|
|
|
|
|
# 4.4 describes Client Credentials Grant. |
190
|
|
|
|
|
|
|
# http://tools.ietf.org/html/rfc6749#section-4.4 |
191
|
3
|
|
|
|
|
14
|
return $self->_get_token(+{grant_type => 'client_credentials'}); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# Manually Build a Login Flow > Confirming identity > Exchanging code for an access token |
195
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow |
196
|
|
|
|
|
|
|
sub get_user_token_by_code { |
197
|
2
|
|
|
2
|
1
|
45
|
my ($self, $code) = @_; |
198
|
|
|
|
|
|
|
|
199
|
2
|
50
|
|
|
|
6
|
croak 'code is not given' unless $code; |
200
|
2
|
50
|
|
|
|
7
|
croak 'redirect_uri must be set' unless $self->redirect_uri; |
201
|
|
|
|
|
|
|
|
202
|
2
|
|
|
|
|
6
|
my $query_ref = +{ |
203
|
|
|
|
|
|
|
redirect_uri => $self->redirect_uri, |
204
|
|
|
|
|
|
|
code => $code, |
205
|
|
|
|
|
|
|
}; |
206
|
2
|
|
|
|
|
6
|
return $self->_get_token($query_ref); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub get_user_token_by_cookie { |
210
|
4
|
|
|
4
|
1
|
223
|
my ($self, $cookie_value) = @_; |
211
|
|
|
|
|
|
|
|
212
|
4
|
100
|
|
|
|
28
|
croak 'cookie value is not given' unless $cookie_value; |
213
|
|
|
|
|
|
|
|
214
|
3
|
|
|
|
|
11
|
my $parsed_signed_request = $self->parse_signed_request($cookie_value); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# https://github.com/oklahomer/p5-Facebook-OpenGraph/issues/1#issuecomment-41065480 |
217
|
|
|
|
|
|
|
# parsed content should be something like below. |
218
|
|
|
|
|
|
|
# { |
219
|
|
|
|
|
|
|
# algorithm => "HMAC-SHA256", |
220
|
|
|
|
|
|
|
# issued_at => 1398180151, |
221
|
|
|
|
|
|
|
# code => "SOME_OPAQUE_STRING", |
222
|
|
|
|
|
|
|
# user_id => 44007581, |
223
|
|
|
|
|
|
|
# }; |
224
|
|
|
|
|
|
|
croak q{"code" is not contained in cookie value: } . $cookie_value |
225
|
3
|
100
|
|
|
|
20
|
unless $parsed_signed_request->{code}; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# Redirect_uri MUST be empty string in this case. |
228
|
|
|
|
|
|
|
# That's why I didn't use get_user_token_by_code(). |
229
|
|
|
|
|
|
|
my $query_ref = +{ |
230
|
|
|
|
|
|
|
code => $parsed_signed_request->{code}, |
231
|
2
|
|
|
|
|
7
|
redirect_uri => '', |
232
|
|
|
|
|
|
|
}; |
233
|
2
|
|
|
|
|
7
|
return $self->_get_token($query_ref); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Access Tokens > Expiration and Extending Tokens |
237
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/access-tokens/ |
238
|
|
|
|
|
|
|
sub exchange_token { |
239
|
3
|
|
|
3
|
1
|
147
|
my ($self, $short_term_token) = @_; |
240
|
|
|
|
|
|
|
|
241
|
3
|
50
|
|
|
|
8
|
croak 'short term token is not given' unless $short_term_token; |
242
|
|
|
|
|
|
|
|
243
|
3
|
|
|
|
|
11
|
my $query_ref = +{ |
244
|
|
|
|
|
|
|
grant_type => 'fb_exchange_token', |
245
|
|
|
|
|
|
|
fb_exchange_token => $short_term_token, |
246
|
|
|
|
|
|
|
}; |
247
|
3
|
|
|
|
|
9
|
return $self->_get_token($query_ref); |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
sub _get_token { |
251
|
10
|
|
|
10
|
|
24
|
my ($self, $param_ref) = @_; |
252
|
|
|
|
|
|
|
|
253
|
10
|
100
|
100
|
|
|
24
|
croak 'app_id and secret must be set' unless $self->app_id && $self->secret; |
254
|
|
|
|
|
|
|
|
255
|
6
|
|
|
|
|
24
|
$param_ref = +{ |
256
|
|
|
|
|
|
|
%$param_ref, |
257
|
|
|
|
|
|
|
client_id => $self->app_id, |
258
|
|
|
|
|
|
|
client_secret => $self->secret, |
259
|
|
|
|
|
|
|
}; |
260
|
|
|
|
|
|
|
|
261
|
6
|
|
|
|
|
48
|
my $response = $self->request('GET', '/oauth/access_token', $param_ref); |
262
|
|
|
|
|
|
|
# Document describes as follows: |
263
|
|
|
|
|
|
|
# "The response you will receive from this endpoint, if successful, is |
264
|
|
|
|
|
|
|
# access_token={access-token}&expires={seconds-til-expiration} |
265
|
|
|
|
|
|
|
# If it is not successful, you'll receive an explanatory error message." |
266
|
|
|
|
|
|
|
# |
267
|
|
|
|
|
|
|
# It, however, returnes no "expires" parameter on some edge cases. |
268
|
|
|
|
|
|
|
# e.g. Your app requests manage_pages permission. |
269
|
|
|
|
|
|
|
# https://developers.facebook.com/bugs/597779113651383/ |
270
|
6
|
100
|
|
|
|
30
|
if ($response->is_api_version_eq_or_later_than('v2.3')) { |
271
|
|
|
|
|
|
|
# As of v2.3, to be compliant with RFC 6749, response is JSON formatted |
272
|
|
|
|
|
|
|
# as described below. |
273
|
|
|
|
|
|
|
# {"access_token": , "token_type":, "expires_in": |
274
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.3#confirm |
275
|
1
|
|
|
|
|
5
|
return $response->as_hashref; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
5
|
|
|
|
|
25
|
my $res_content = $response->content; |
279
|
5
|
|
|
|
|
31
|
my $token_ref = +{ URI->new("?$res_content")->query_form }; |
280
|
|
|
|
|
|
|
croak "can't get access_token properly: $res_content" |
281
|
5
|
50
|
|
|
|
835
|
unless $token_ref->{access_token}; |
282
|
|
|
|
|
|
|
|
283
|
5
|
|
|
|
|
55
|
return $token_ref; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub get { |
287
|
10
|
|
|
10
|
1
|
292
|
return shift->request('GET', @_)->as_hashref; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub post { |
291
|
21
|
|
|
21
|
1
|
201
|
return shift->request('POST', @_)->as_hashref; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# Deleting > Objects |
295
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/reference/api/deleting/ |
296
|
|
|
|
|
|
|
sub delete { |
297
|
1
|
|
|
1
|
1
|
18
|
return shift->request('DELETE', @_)->as_hashref; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# For those who got used to Facebook::Graph |
301
|
|
|
|
|
|
|
*fetch = \&get; |
302
|
|
|
|
|
|
|
*publish = \&post; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# Using ETags |
305
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/reference/ads-api/etags-reference/ |
306
|
|
|
|
|
|
|
sub fetch_with_etag { |
307
|
2
|
|
|
2
|
1
|
42
|
my ($self, $uri, $param_ref, $etag) = @_; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Attach ETag value to header |
310
|
|
|
|
|
|
|
# Returns status 304 without contnet, or status 200 with modified content |
311
|
2
|
|
|
|
|
6
|
my $header = ['IF-None-Match' => $etag]; |
312
|
2
|
|
|
|
|
7
|
my $response = $self->request('GET', $uri, $param_ref, $header); |
313
|
|
|
|
|
|
|
|
314
|
2
|
100
|
|
|
|
7
|
return $response->is_modified ? $response->as_hashref : undef; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
sub bulk_fetch { |
318
|
1
|
|
|
1
|
1
|
27
|
my ($self, $paths_ref) = @_; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
my @queries = map { |
321
|
1
|
|
|
|
|
3
|
+{ |
322
|
2
|
|
|
|
|
8
|
method => 'GET', |
323
|
|
|
|
|
|
|
relative_url => $_, |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
} @$paths_ref; |
326
|
|
|
|
|
|
|
|
327
|
1
|
|
|
|
|
5
|
return $self->batch(\@queries); |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Making Multiple API Requests > Making a simple batched request |
331
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/graph-api/making-multiple-requests |
332
|
|
|
|
|
|
|
sub batch { |
333
|
6
|
|
|
6
|
1
|
187
|
my $self = shift; |
334
|
|
|
|
|
|
|
|
335
|
6
|
|
|
|
|
18
|
my $responses_ref = $self->batch_fast(@_); |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# Devide response content and create response objects that correspond to |
338
|
|
|
|
|
|
|
# each request |
339
|
5
|
|
|
|
|
13
|
my @data = (); |
340
|
5
|
|
|
|
|
13
|
for my $r (@$responses_ref) { |
341
|
7
|
|
|
|
|
16
|
for my $res_ref (@$r) { |
342
|
|
|
|
|
|
|
my $response = $self->create_response( |
343
|
|
|
|
|
|
|
$res_ref->{code}, |
344
|
|
|
|
|
|
|
$res_ref->{message}, |
345
|
58
|
|
|
|
|
126
|
[ map { $_->{name} => $_->{value} } @{ $res_ref->{headers} } ], |
|
13
|
|
|
|
|
33
|
|
346
|
|
|
|
|
|
|
$res_ref->{body}, |
347
|
13
|
|
|
|
|
38
|
); |
348
|
13
|
50
|
|
|
|
100
|
croak $response->error_string unless $response->is_success; |
349
|
|
|
|
|
|
|
|
350
|
13
|
|
|
|
|
34
|
push @data, $response->as_hashref; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
5
|
|
|
|
|
61
|
return \@data; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# doesn't create F::OG::Response object for each response |
358
|
|
|
|
|
|
|
sub batch_fast { |
359
|
6
|
|
|
6
|
1
|
12
|
my $self = shift; |
360
|
6
|
|
|
|
|
9
|
my $batch = shift; |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# Other than HTTP header, you need to set access_token as top level |
363
|
|
|
|
|
|
|
# parameter. You can specify individual token for each request so you can |
364
|
|
|
|
|
|
|
# act as several other users and pages. |
365
|
6
|
100
|
|
|
|
20
|
croak 'Top level access_token must be set' unless $self->access_token; |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# "We currently limit the number of requests which can be in a batch to 50" |
368
|
5
|
|
|
|
|
21
|
my @responses = (); |
369
|
5
|
|
|
|
|
20
|
while(my @queries = splice @$batch, 0, $self->batch_limit) { |
370
|
|
|
|
|
|
|
|
371
|
7
|
|
|
|
|
17
|
for my $q (@queries) { |
372
|
13
|
50
|
66
|
|
|
57
|
if ($q->{method} eq 'POST' && $q->{body}) { |
373
|
3
|
|
|
|
|
9
|
my $body_ref = $self->prep_param($q->{body}); |
374
|
3
|
|
|
|
|
14
|
my $uri = URI->new; |
375
|
3
|
|
|
|
|
4589
|
$uri->query_form(%$body_ref); |
376
|
3
|
|
|
|
|
399
|
$q->{body} = $uri->query; |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
7
|
|
|
|
|
48
|
my @req = ( |
381
|
|
|
|
|
|
|
'/', |
382
|
|
|
|
|
|
|
+{ |
383
|
|
|
|
|
|
|
access_token => $self->access_token, |
384
|
|
|
|
|
|
|
batch => $self->json->encode(\@queries), |
385
|
|
|
|
|
|
|
}, |
386
|
|
|
|
|
|
|
@_, |
387
|
|
|
|
|
|
|
); |
388
|
|
|
|
|
|
|
|
389
|
7
|
|
|
|
|
28
|
push @responses, $self->post(@req); |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
5
|
|
|
|
|
15
|
return \@responses; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
# Facebook Query Language (FQL) Overview |
396
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/technical-guides/fql/ |
397
|
|
|
|
|
|
|
sub fql { |
398
|
2
|
|
|
2
|
1
|
25
|
my $self = shift; |
399
|
2
|
|
|
|
|
5
|
my $query = shift; |
400
|
2
|
|
|
|
|
11
|
return $self->get('/fql', +{q => $query}, @_); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# Facebook Query Language (FQL) Overview: Multi-query |
404
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/technical-guides/fql/#multi |
405
|
|
|
|
|
|
|
sub bulk_fql { |
406
|
1
|
|
|
1
|
1
|
23
|
my $self = shift; |
407
|
1
|
|
|
|
|
2
|
my $batch = shift; |
408
|
1
|
|
|
|
|
3
|
return $self->fql($self->json->encode($batch), @_); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub request { |
412
|
40
|
|
|
40
|
1
|
132
|
my ($self, $method, $uri, $param_ref, $headers) = @_; |
413
|
|
|
|
|
|
|
|
414
|
40
|
|
|
|
|
136
|
$method = uc $method; |
415
|
40
|
50
|
33
|
|
|
280
|
$uri = $self->uri($uri) unless blessed($uri) && $uri->isa('URI'); |
416
|
40
|
|
|
|
|
151
|
$uri->path( $self->gen_versioned_path($uri->path) ); |
417
|
|
|
|
|
|
|
$param_ref = $self->prep_param(+{ |
418
|
|
|
|
|
|
|
$uri->query_form(+{}), |
419
|
40
|
100
|
|
|
|
1547
|
%{$param_ref || +{}}, |
|
40
|
|
|
|
|
1960
|
|
420
|
|
|
|
|
|
|
}); |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
# Securing Graph API Requests > Verifying Graph API Calls with appsecret_proof |
423
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/graph-api/securing-requests/ |
424
|
40
|
100
|
|
|
|
193
|
if ($self->use_appsecret_proof) { |
425
|
1
|
|
|
|
|
3
|
$param_ref->{appsecret_proof} = $self->gen_appsecret_proof; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Use POST as default HTTP method and add method=(POST|GET|DELETE) to query |
429
|
|
|
|
|
|
|
# parameter. Document only says we can send HTTP DELETE method or, instead, |
430
|
|
|
|
|
|
|
# HTTP POST method with ?method=delete to delete object. It does not say |
431
|
|
|
|
|
|
|
# POST with method=(get|post) parameter works, but PHP SDK always sends POST |
432
|
|
|
|
|
|
|
# with method parameter so... I just give you this option. |
433
|
|
|
|
|
|
|
# Check PHP SDK's base_facebook.php for detail. |
434
|
40
|
100
|
|
|
|
124
|
if ($self->{use_post_method}) { |
435
|
2
|
|
|
|
|
5
|
$param_ref->{method} = $method; |
436
|
2
|
|
|
|
|
4
|
$method = 'POST'; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
40
|
|
100
|
|
|
220
|
$headers ||= []; |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
# Document says we can pass access_token as a part of query parameter, |
442
|
|
|
|
|
|
|
# but it actually supports Authorization header to be compliant with the |
443
|
|
|
|
|
|
|
# OAuth 2.0 spec. |
444
|
|
|
|
|
|
|
# http://tools.ietf.org/html/rfc6749#section-7 |
445
|
40
|
100
|
|
|
|
127
|
if ($self->access_token) { |
446
|
19
|
|
|
|
|
55
|
push @$headers, ( |
447
|
|
|
|
|
|
|
'Authorization', |
448
|
|
|
|
|
|
|
sprintf('OAuth %s', $self->access_token), |
449
|
|
|
|
|
|
|
); |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
40
|
|
|
|
|
95
|
my $content = q{}; |
453
|
40
|
100
|
|
|
|
122
|
if ($method eq 'POST') { |
454
|
23
|
100
|
100
|
|
|
152
|
if ($param_ref->{source} || $param_ref->{file} || $param_ref->{upload_phase} ) { |
|
|
|
100
|
|
|
|
|
455
|
|
|
|
|
|
|
# post image or video file |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/reference/api/video/ |
458
|
|
|
|
|
|
|
# When posting a video, use graph-video.facebook.com . |
459
|
|
|
|
|
|
|
# base_facebook.php has an equivalent part in isVideoPost(). |
460
|
|
|
|
|
|
|
# ($method == 'POST' && preg_match("/^(\/)(.+)(\/)(videos)$/", $path)) |
461
|
|
|
|
|
|
|
# For other actions, use graph.facebook.com/VIDEO_ID/CONNECTION_TYPE |
462
|
5
|
100
|
|
|
|
15
|
if ($uri->path =~ m{\A /.+/videos \z}xms) { |
463
|
3
|
|
|
|
|
47
|
$uri->host($self->video_uri->host); |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# Content-Type should be multipart/form-data |
467
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/reference/api/publishing/ |
468
|
5
|
|
|
|
|
328
|
push @$headers, (Content_Type => 'form-data'); |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# Furl::HTTP document says we can use multipart/form-data with |
471
|
|
|
|
|
|
|
# HTTP::Request::Common. |
472
|
5
|
|
|
|
|
35
|
my $req = POST $uri, @$headers, Content => [%$param_ref]; |
473
|
5
|
|
|
|
|
63395
|
$content = $req->content; |
474
|
5
|
|
|
|
|
133
|
my $req_header = $req->headers; |
475
|
|
|
|
|
|
|
$headers = +[ |
476
|
|
|
|
|
|
|
map { |
477
|
5
|
|
|
|
|
43
|
my $k = $_; |
|
15
|
|
|
|
|
153
|
|
478
|
15
|
|
|
|
|
36
|
map { ( $k => $_ ) } $req_header->header($k); |
|
15
|
|
|
|
|
499
|
|
479
|
|
|
|
|
|
|
} $req_header->header_field_names |
480
|
|
|
|
|
|
|
]; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
else { |
483
|
|
|
|
|
|
|
# Post simple parameters such as message, link, description, etc... |
484
|
|
|
|
|
|
|
# Content-Type: application/x-www-form-urlencoded will be set in |
485
|
|
|
|
|
|
|
# Furl::HTTP, and $content will be treated properly. |
486
|
18
|
|
|
|
|
36
|
$content = $param_ref; |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
else { |
490
|
17
|
|
|
|
|
58
|
$uri->query_form($param_ref); |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
40
|
|
|
|
|
1969
|
my ($res_minor_version, @res_elms) = $self->ua->request( |
494
|
|
|
|
|
|
|
method => $method, |
495
|
|
|
|
|
|
|
url => $uri, |
496
|
|
|
|
|
|
|
headers => $headers, |
497
|
|
|
|
|
|
|
content => $content, |
498
|
|
|
|
|
|
|
); |
499
|
|
|
|
|
|
|
|
500
|
40
|
|
|
|
|
82992
|
my $res = $self->create_response(@res_elms); |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
# return F::OG::Response object on success |
503
|
40
|
100
|
|
|
|
209
|
return $res if $res->is_success; |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# Use later version of Furl::HTTP to utilize req_headers and req_content. |
506
|
|
|
|
|
|
|
# This Should be helpful when debugging. |
507
|
4
|
|
|
|
|
17
|
my $msg = $res->error_string; |
508
|
4
|
50
|
|
|
|
15
|
if ($res->req_headers) { |
509
|
0
|
|
|
|
|
0
|
$msg .= "\n" . $res->req_headers . $res->req_content; |
510
|
|
|
|
|
|
|
} |
511
|
4
|
|
|
|
|
73
|
croak $msg; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
# Securing Graph API Requests > Verifying Graph API Calls with appsecret_proof > Generating the proof |
515
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/graph-api/securing-requests/ |
516
|
|
|
|
|
|
|
sub gen_appsecret_proof { |
517
|
2
|
|
|
2
|
1
|
62
|
my $self = shift; |
518
|
2
|
50
|
|
|
|
8
|
croak 'app secret must be set' unless $self->secret; |
519
|
2
|
50
|
|
|
|
7
|
croak 'access_token must be set' unless $self->access_token; |
520
|
2
|
|
|
|
|
6
|
return hmac_sha256_hex($self->access_token, $self->secret); |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# Platform Versioning > Making Versioned Requests |
524
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/apps/versions |
525
|
|
|
|
|
|
|
sub gen_versioned_path { |
526
|
56
|
|
|
56
|
0
|
686
|
my ($self, $path) = @_; |
527
|
|
|
|
|
|
|
|
528
|
56
|
100
|
|
|
|
171
|
$path = '/' unless $path; |
529
|
|
|
|
|
|
|
|
530
|
56
|
100
|
100
|
|
|
166
|
if ($self->version && $path !~ m{\A /v(?:\d+)\.(?:\d+)/ }x) { |
531
|
|
|
|
|
|
|
# If default platform version is set on initialisation |
532
|
|
|
|
|
|
|
# and given path doesn't contain version, |
533
|
|
|
|
|
|
|
# then prepend the default version. |
534
|
5
|
|
|
|
|
12
|
$path = sprintf('/%s%s', $self->version, $path); |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
56
|
|
|
|
|
228
|
return $path; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
sub js_cookie_name { |
541
|
2
|
|
|
2
|
1
|
90
|
my $self = shift; |
542
|
2
|
100
|
|
|
|
6
|
croak 'app_id must be set' unless $self->app_id; |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Cookie is set by JS SDK with a name of fbsr_{app_id}. Official document |
545
|
|
|
|
|
|
|
# is not provided for more than 3 yaers so I quote from PHP SDK's code. |
546
|
|
|
|
|
|
|
# "Constructs and returns the name of the cookie that potentially houses |
547
|
|
|
|
|
|
|
# the signed request for the app user. The cookie is not set by the |
548
|
|
|
|
|
|
|
# BaseFacebook class, but it may be set by the JavaScript SDK." |
549
|
|
|
|
|
|
|
# The cookie value can be parsed as signed request and it contains 'code' |
550
|
|
|
|
|
|
|
# to exchange for access toekn. |
551
|
1
|
|
|
|
|
3
|
return sprintf('fbsr_%d', $self->app_id); |
552
|
|
|
|
|
|
|
} |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
sub create_response { |
555
|
53
|
|
|
53
|
1
|
122
|
my $self = shift; |
556
|
|
|
|
|
|
|
return Facebook::OpenGraph::Response->new(+{ |
557
|
|
|
|
|
|
|
json => $self->json, |
558
|
|
|
|
|
|
|
map { |
559
|
53
|
|
|
|
|
203
|
$_ => shift |
|
318
|
|
|
|
|
924
|
|
560
|
|
|
|
|
|
|
} qw/code message headers content req_headers req_content/, |
561
|
|
|
|
|
|
|
}); |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
sub prep_param { |
565
|
44
|
|
|
44
|
1
|
191
|
my ($self, $param_ref) = @_; |
566
|
|
|
|
|
|
|
|
567
|
44
|
|
50
|
|
|
366
|
$param_ref = Data::Recursive::Encode->encode_utf8($param_ref || +{}); |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# /?ids=4,http://facebook-docs.oklahome.net |
570
|
44
|
100
|
|
|
|
4128
|
if (my $ids = $param_ref->{ids}) { |
571
|
1
|
50
|
|
|
|
7
|
$param_ref->{ids} = ref $ids ? join q{,}, @$ids : $ids; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
# mostly for /APP_ID/accounts/test-users |
575
|
44
|
100
|
|
|
|
145
|
if (my $perms = $param_ref->{permissions}) { |
576
|
5
|
100
|
|
|
|
26
|
$param_ref->{permissions} = ref $perms ? join q{,}, @$perms : $perms; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
# Source, file and video_file_chunk parameter contains file path. |
580
|
|
|
|
|
|
|
# It must be an array ref to work with HTTP::Request::Common. |
581
|
44
|
100
|
|
|
|
169
|
if (my $path = $param_ref->{source}) { |
582
|
3
|
50
|
|
|
|
14
|
$param_ref->{source} = ref $path ? $path : [$path]; |
583
|
|
|
|
|
|
|
} |
584
|
44
|
100
|
|
|
|
143
|
if (my $path = $param_ref->{file}) { |
585
|
1
|
50
|
|
|
|
5
|
$param_ref->{file} = ref $path ? $path : [$path]; |
586
|
|
|
|
|
|
|
} |
587
|
44
|
100
|
|
|
|
125
|
if (my $path = $param_ref->{video_file_chunk}) { |
588
|
1
|
50
|
|
|
|
6
|
$param_ref->{video_file_chunk} = ref $path ? $path : [$path]; |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
# use Field Expansion |
592
|
44
|
100
|
|
|
|
186
|
if (my $field_ref = $param_ref->{fields}) { |
593
|
2
|
|
|
|
|
8
|
$param_ref->{fields} = $self->prep_fields_recursive($field_ref); |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
# Using Objects: Using the Object API |
597
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/opengraph/using-objects/#objectapi |
598
|
44
|
|
|
|
|
90
|
my $object = $param_ref->{object}; |
599
|
44
|
100
|
66
|
|
|
140
|
if ($object && ref $object eq 'HASH') { |
600
|
1
|
|
|
|
|
3
|
$param_ref->{object} = $self->json->encode($object); |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
|
603
|
44
|
|
|
|
|
106
|
return $param_ref; |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# Using the Graph API: Reading > Choosing Fields > Making Nested Requests |
607
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/graph-api/using-graph-api/ |
608
|
|
|
|
|
|
|
sub prep_fields_recursive { |
609
|
4
|
|
|
4
|
1
|
10
|
my ($self, $val) = @_; |
610
|
|
|
|
|
|
|
|
611
|
4
|
|
|
|
|
10
|
my $ref = ref $val; |
612
|
4
|
100
|
|
|
|
22
|
if (!$ref) { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
613
|
3
|
|
|
|
|
13
|
return $val; |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
elsif ($ref eq 'ARRAY') { |
616
|
1
|
|
|
|
|
4
|
return join q{,}, map { $self->prep_fields_recursive($_) } @$val; |
|
2
|
|
|
|
|
7
|
|
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
elsif ($ref eq 'HASH') { |
619
|
0
|
|
|
|
|
0
|
my @strs = (); |
620
|
0
|
|
|
|
|
0
|
while (my ($k, $v) = each %$val) { |
621
|
0
|
|
|
|
|
0
|
my $r = ref $v; |
622
|
0
|
0
|
0
|
|
|
0
|
my $pattern = $r && $r eq 'HASH' ? '%s.%s' : '%s(%s)'; |
623
|
0
|
|
|
|
|
0
|
push @strs, sprintf($pattern, $k, $self->prep_fields_recursive($v)); |
624
|
|
|
|
|
|
|
} |
625
|
0
|
|
|
|
|
0
|
return join q{.}, @strs; |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
# Using Actions > Publishing Actions |
630
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/opengraph/using-actions/#publish |
631
|
|
|
|
|
|
|
sub publish_action { |
632
|
1
|
|
|
1
|
1
|
26
|
my $self = shift; |
633
|
1
|
|
|
|
|
3
|
my $action = shift; |
634
|
1
|
50
|
|
|
|
3
|
croak 'namespace is not set' unless $self->namespace; |
635
|
1
|
|
|
|
|
4
|
return $self->post(sprintf('/me/%s:%s', $self->namespace, $action), @_); |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
# Using Objects > Using the Object API > Images with the Object API |
639
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/opengraph/using-objects/ |
640
|
|
|
|
|
|
|
sub publish_staging_resource { |
641
|
1
|
|
|
1
|
1
|
24
|
my $self = shift; |
642
|
1
|
|
|
|
|
2
|
my $file = shift; |
643
|
1
|
|
|
|
|
5
|
return $self->post('/me/staging_resources', +{file => $file}, @_); |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
# Test Users: Creating |
647
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/test_users/ |
648
|
|
|
|
|
|
|
sub create_test_users { |
649
|
2
|
|
|
2
|
1
|
73
|
my $self = shift; |
650
|
2
|
|
|
|
|
4
|
my $settings_ref = shift; |
651
|
|
|
|
|
|
|
|
652
|
2
|
100
|
|
|
|
9
|
if (ref $settings_ref ne 'ARRAY') { |
653
|
1
|
|
|
|
|
3
|
$settings_ref = [$settings_ref]; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
2
|
|
|
|
|
6
|
my @settings = (); |
657
|
2
|
|
|
|
|
8
|
my $relative_url = sprintf('/%s/accounts/test-users', $self->app_id); |
658
|
2
|
|
|
|
|
7
|
for my $setting (@$settings_ref) { |
659
|
3
|
|
|
|
|
12
|
push @settings, +{ |
660
|
|
|
|
|
|
|
method => 'POST', |
661
|
|
|
|
|
|
|
relative_url => $relative_url, |
662
|
|
|
|
|
|
|
body => $setting, |
663
|
|
|
|
|
|
|
}; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
2
|
|
|
|
|
10
|
return $self->batch(\@settings); |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
# Using Objects > Using Self-Hosted Objects > Updating Objects |
670
|
|
|
|
|
|
|
# https://developers.facebook.com/docs/opengraph/using-objects/ |
671
|
|
|
|
|
|
|
sub check_object { |
672
|
6
|
|
|
6
|
1
|
251
|
my ($self, $target) = @_; |
673
|
6
|
|
|
|
|
31
|
my $param_ref = +{ |
674
|
|
|
|
|
|
|
id => $target, # $target is object url or open graph object id |
675
|
|
|
|
|
|
|
scrape => 'true', |
676
|
|
|
|
|
|
|
}; |
677
|
6
|
|
|
|
|
16
|
return $self->post(q{}, $param_ref); |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
1; |
681
|
|
|
|
|
|
|
__END__ |