| 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__ |