File Coverage

blib/lib/Mojo/Facebook.pm
Criterion Covered Total %
statement 91 95 95.7
branch 18 28 64.2
condition 12 16 75.0
subroutine 16 16 100.0
pod 5 5 100.0
total 142 160 88.7


line stmt bran cond sub pod time code
1             package Mojo::Facebook;
2              
3             =head1 NAME
4              
5             Mojo::Facebook - Talk with Facebook
6              
7             =head1 VERSION
8              
9             0.0402
10              
11             =head1 DESCRIPTION
12              
13             This module implements basic actions to the Facebook graph protocol.
14              
15             =head1 SYNOPSIS
16              
17             use Mojo::Facebook;
18             my $fb = Mojo::Facebook->new(access_token => $some_secret);
19              
20             # fetch facebook name
21             Mojo::IOLoop->delay(
22             sub {
23             my($delay) = @_;
24             $fb->fetch({
25             from => '1234567890',
26             fields => 'name',
27             }, $delay->begin);
28             },
29             sub {
30             my($delay, $res) = @_;
31             warn $res->{error} || $res->{name};
32             },
33             )
34              
35             # fetch cover photo url
36             $fb->fetch({
37             from => '1234567890',
38             fields => ['cover']
39             }, sub {
40             my($fb, $res) = @_;
41             return $res->{errors} if $res->{error};
42             warn $res->{cover}{source}; # URL
43             });
44              
45             =head1 ERROR HANDLING
46              
47             Facebook JSON errors will be set in the C<$res> hash returned to the callback:
48              
49             =head2 Error messages
50              
51             =over 4
52              
53             =item * Could not decode JSON from Facebook
54              
55             =item * $fb_json->{error}{message}
56              
57             =item * HTTP status message
58              
59             =item * Unknown error from JSON structure
60              
61             =back
62              
63             =cut
64              
65 8     8   4013486 use Mojo::Base -base;
  8         15318  
  8         73  
66 8     8   2905 use Mojo::UserAgent;
  8         445871  
  8         67  
67 8     8   218 use Mojo::Util qw/ url_unescape /;
  8         16  
  8         507  
68 8     8   43 use constant TEST => $INC{'Test/Mojo.pm'};
  8         14  
  8         15753  
69              
70             our $VERSION = '0.0402';
71              
72             =head1 ATTRIBUTES
73              
74             =head2 access_token
75              
76             This attribute need to be set when doing L on private objects
77             or when issuing L. This is not "code" query param from the
78             Facebook authentication process, something which need to be fetched
79             from Facebook later on. See the source code forL
80             for details.
81              
82             $oauth2->get_token(facebook => sub {
83             my($oauth2, $access_token) = @_;
84             $fb = Mojo::Facebook->new(access_token => $access_token);
85             $fb->post({
86             to => $fb_uid,
87             message => "Mojo::Facebook works!",
88             }, sub {
89             # ...
90             });
91             });
92              
93             =head2 app_namespace
94              
95             This attribute is used by L as prefix to the publish URL:
96              
97             https://graph.facebook.com/$id/$app_namespace:$action
98              
99             =head2 scheme
100              
101             Used to either run requests over "http" or "https". Default to "https".
102              
103             =cut
104              
105             has access_token => '';
106             has app_namespace => '';
107             has scheme => 'https';
108             has _ua => sub { Mojo::UserAgent->new };
109              
110             =head1 METHODS
111              
112             =head2 fetch
113              
114             $self->fetch({
115             from => $id,
116             fields => [...]
117             ids => [...],
118             limit => $Int,
119             offset => $Int,
120             }, $callback);
121              
122             Will fetch information from Facebook about a user.
123              
124             C<$id> can be ommitted and will then default to "me".
125             C<$callback> will be called like this:
126              
127             $callback->($self, $res);
128              
129             C<$res> will be a hash-ref containing the result. Look for the "error" key to
130             check for errors.
131              
132             =cut
133              
134             sub fetch {
135 1     1 1 871 my($self, $args, $cb) = @_;
136 1         5 my $tx = $self->_tx('GET');
137 1         999 my $url = $tx->req->url;
138              
139 1         34 Scalar::Util::weaken($self);
140              
141 1 50       26 if($self->access_token) {
142 1         33 $url->query([ access_token => url_unescape $self->access_token ]);
143             }
144              
145 1         87 for my $key (qw/ fields ids /) {
146 2 100       72 my $value = $args->{$key} or next;
147 1 50       9 $url->query([ $key => ref $value eq 'ARRAY' ? join ',', @$value : $value ]);
148             }
149 1         6 for my $key (qw/ date_format limit metadata offset since until /) {
150 6 100       158 defined $args->{$key} or next;
151 2         13 $url->query([ $key => $args->{$key} ]);
152             }
153              
154 1   50     2 push @{ $url->path->parts }, $args->{from} || 'me';
  1         5  
155 1     1   155 $self->_ua->start($tx, sub { $self->$cb(__check_response(@_)) });
  1         61139  
156             }
157              
158             sub _message_to_tags {
159 1     1   3 my($self, $message) = @_;
160 1         3 my @tags;
161              
162 1         3 while(1) {
163 2 100       20 $message =~ s/\@\[ (\w+) : ([^\]]+) \]/$2/ox or last;
164              
165 1         15 push @tags, {
166             id => int $1,
167             name => $2,
168             offset => $-[0],
169             length => length $2,
170             };
171             }
172              
173 1         7 return $message, \@tags;
174             }
175              
176             =head2 comment
177              
178             $self->comment({ on => $id, message => $str }, $callback);
179              
180             Will add a comment to a graph element with the given C<$id>.
181              
182             C<$callback> will be called like this:
183              
184             $callback->($self, $res);
185              
186             C<$res> will be a hash-ref containing the result. Look for the "error" key to
187             check for errors.
188              
189             =cut
190              
191             sub comment {
192 1     1 1 987 my($self, $args, $cb) = @_;
193 1         6 my $tx = $self->_tx('POST');
194 1         1123 my $p = Mojo::Parameters->new;
195              
196 1         14 Scalar::Util::weaken($self);
197              
198 1         32 $p->append(access_token => $self->access_token);
199 1         42 $p->append(message => $args->{message});
200 1         60 $tx->req->body($p->to_string);
201 1         405 push @{ $tx->req->url->path->parts }, $args->{on}, 'comments';
  1         30  
202 1     1   199 $self->_ua->start($tx, sub { $self->$cb(__check_response(@_)) });
  1         50821  
203             }
204              
205             =head2 publish
206              
207             $self->publish({
208             to => $id,
209             action => $str,
210             $object_name => $object_url,
211              
212             # optional
213             start_time => $DateTime,
214             end_time => $DateTime,
215             expires_in => $int,
216             message => $str,
217             place => $facebook_id,
218             ref => String,
219             tags => "$facebook_id,...",
220              
221             # any other key/value is considered to be custom
222             $custom_attribute => $any,
223             });
224              
225             Publish a story at C<$who>'s wall, looking like this:
226              
227             .--------------------------------------.
228             | $who $action a $object_name ... $app |
229             | |
230             | .----------. |
231             | | $image | [$url]($title) |
232             | | | $descripton ... |
233             | '----------' |
234             '--------------------------------------'
235              
236             Required HTML:
237              
238            
239            
240            
241            
242            
243            
244              
245             C<$callback> will be called like this:
246              
247             $callback->($self, $res);
248              
249             C<$res> will be a hash-ref containing the result. Look for the "error" key to
250             check for errors.
251              
252             =cut
253              
254             sub publish {
255 1     1 1 1196 my($self, $args, $cb) = @_;
256 1         5 my $tx = $self->_tx('POST');
257 1         1374 my $p = Mojo::Parameters->new;
258 1         14 my $tags = [];
259              
260 1         6 Scalar::Util::weaken($self);
261              
262 1 50       8 if($args->{message}) {
263 1         7 ($args->{message}, $tags) = $self->_message_to_tags($args->{message});
264             }
265              
266 1         9 while(my($name, $value) = each %$args) {
267 5 100 100     81 next if $name eq 'to' or $name eq 'action';
268 3         13 $p->append($name => $value);
269             }
270 1 50       29 if(@$tags) {
271 1         3 $p->append(tags => join ',', map { $_->{id} } @$tags);
  1         9  
272             }
273              
274 1         67 $p->append(access_token => $self->access_token);
275              
276 1         29 push @{ $tx->req->url->path }, $args->{to}, join ':', $self->app_namespace, $args->{action};
  1         28  
277 1         523 $tx->req->body($p->to_string);
278 1     1   714 $self->_ua->start($tx, sub { $self->$cb(__check_response(@_)) });
  1         56825  
279             }
280              
281             =head2 delete_object
282              
283             $self->delete_object($id, $callback);
284              
285             Will try to remove an object from Facebook.
286              
287             C<&callback> will be called like this:
288              
289             $callback->($self, $res);
290              
291             C<$res> will be a hash-ref containing the result. Look for the "error" key to
292             check for errors.
293              
294             =cut
295              
296             sub delete_object {
297 1     1 1 967 my($self, $id, $cb) = @_;
298 1         6 my $tx = $self->_tx('DELETE');
299              
300 1         1003 Scalar::Util::weaken($self);
301              
302 1         23 $tx->req->url->query->param(access_token => $self->access_token);
303 1         134 push @{ $tx->req->url->path->parts }, $id;
  1         23  
304 1     1   170 $self->_ua->start($tx, sub { $self->$cb(__check_response(@_)) });
  1         63882  
305             }
306              
307             =head2 picture
308              
309             $url = $self->picture;
310             $url = $self->picture($who, $type);
311              
312             Returns a L object with the URL to a Facebook image.
313              
314             C<$who> defaults to "me".
315             C<$type> can be "square", "small" or "large". Default to "square".
316              
317             =cut
318              
319             sub picture {
320 4     4 1 1589 my $self = shift;
321 4   100     30 my $who = shift || 'me';
322 4   100     21 my $type = shift || 'square';
323 4   50     54 my $url = Mojo::URL->new($ENV{FAKE_FACEBOOK_URL} || 'https://graph.facebook.com');
324              
325 4         6514 push @{ $url->path->parts }, $who, 'picture';
  4         83  
326 4         164 $url->query(type => $type);
327 4 50       726 $url->scheme($self->scheme) if $url->host;
328 4         476 $url;
329             }
330              
331             sub __check_response {
332 4     4   15 my($ua, $tx) = @_;
333 4         333 my $res = $tx->res;
334 4         67 my $json = $res->json;
335              
336 4 50 33     1218 if(ref $json eq 'HASH' and $json->{error}) {
    50          
    50          
337 0 0       0 $json->{error} = $json->{error}{message} if $json->{error}{message};
338 0         0 $json->{code} = $res->code;
339             }
340             elsif($res->error) {
341 0         0 $json = { error => ($res->error)[0], code => $res->code };
342             }
343             elsif(!$json) {
344 0         0 $json = { error => 'Could not decode JSON from Facebook', code => $res->code };
345             }
346              
347 4         57 $json->{__tx} = $tx if TEST;
348 4         40 $json;
349             }
350              
351             sub _tx {
352 5     5   17841 my($self, $method) = @_;
353 5   100     127 my $url = Mojo::URL->new($ENV{FAKE_FACEBOOK_URL} || 'https://graph.facebook.com');
354              
355 5 100       2394 $url->scheme($self->scheme) if $url->host;
356 5         494 $self->_ua->build_tx($method => $url);
357             }
358              
359             =head1 COPYRIGHT & LICENSE
360              
361             This library is free software. You can redistribute it and/or modify
362             it under the same terms as Perl itself.
363              
364             =head1 AUTHOR
365              
366             Jan Henning Thorsen - jhthorsen@cpan.org
367              
368             =cut
369              
370             1;
371              
372             1;