source: perl/modules/Facebook/lib/Facebook/Graph.pm @ 5ef98c7

release-1.9
Last change on this file since 5ef98c7 was 5ef98c7, checked in by Edward Z. Yang <ezyang@mit.edu>, 10 years ago
Fix broken posts, better permission detection, implement deletion. Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
  • Property mode set to 100644
File size: 15.7 KB
Line 
1package Facebook::Graph;
2BEGIN {
3  $Facebook::Graph::VERSION = '1.0300';
4}
5
6use Any::Moose;
7use MIME::Base64::URLSafe;
8use JSON;
9with 'Facebook::Graph::Role::Uri';
10use Facebook::Graph::AccessToken;
11use Facebook::Graph::Authorize;
12use Facebook::Graph::Query;
13use Facebook::Graph::Picture;
14use Facebook::Graph::Publish::Post;
15use Facebook::Graph::Publish::Checkin;
16use Facebook::Graph::Publish::Like;
17use Facebook::Graph::Publish::Comment;
18use Facebook::Graph::Publish::Note;
19use Facebook::Graph::Publish::Link;
20use Facebook::Graph::Publish::Event;
21use Facebook::Graph::Publish::RSVPMaybe;
22use Facebook::Graph::Publish::RSVPAttending;
23use Facebook::Graph::Publish::RSVPDeclined;
24use URI::Encode qw(uri_decode);
25use AnyEvent::HTTP;
26use Ouch;
27
28has app_id => (
29    is      => 'ro',
30);
31
32has secret => (
33    is          => 'ro',
34    predicate   => 'has_secret',
35);
36
37has postback => (
38    is      => 'ro',
39);
40
41has access_token => (
42    is          => 'rw',
43    predicate   => 'has_access_token',
44);
45
46
47sub parse_signed_request {
48    my ($self, $signed_request) = @_;
49    require Digest::SHA;
50    my ($encoded_sig, $payload) = split(/\./, $signed_request);
51
52        my $sig = urlsafe_b64decode($encoded_sig);
53    my $data = JSON->new->decode(urlsafe_b64decode($payload));
54
55    if (uc($data->{'algorithm'}) ne "HMAC-SHA256") {
56        ouch '500', 'Unknown algorithm. Expected HMAC-SHA256';
57    }
58
59    my $expected_sig = Digest::SHA::hmac_sha256($payload, $self->secret);
60    if ($sig ne $expected_sig) {
61        ouch '500', 'Bad Signed JSON signature!';
62    }
63    return $data;
64}
65
66sub request_access_token {
67    my ($self, $code, $cb) = @_;
68    Facebook::Graph::AccessToken->new(
69        code            => $code,
70        postback        => $self->postback,
71        secret          => $self->secret,
72        app_id          => $self->app_id,
73    )->request(sub {
74        my ($response) = @_;
75        $self->access_token($response->token);
76        $cb->($response);
77    });
78}
79
80sub convert_sessions {
81    my ($self, $sessions, $cb) = @_;
82    Facebook::Graph::Session->new(
83        secret          => $self->secret,
84        app_id          => $self->app_id,
85        sessions        => $sessions,
86    )->request($cb); # API change
87}
88
89sub authorize { 
90    my ($self) = @_;
91    return Facebook::Graph::Authorize->new(
92        app_id          => $self->app_id,
93        postback        => $self->postback,
94    );
95}
96
97# XXX error handling
98#sub fetch {
99#    my ($self, $object_name, $cb) = @_;
100#    $self->query->find($object_name)->request(sub {
101#        my ($result) = @_;
102#        $cb->($result->as_hashref);
103#    });
104#}
105
106sub query {
107    my ($self) = @_;
108    my %params;
109    if ($self->has_access_token) {
110        $params{access_token} = $self->access_token;
111    }
112    if ($self->has_secret) {
113        $params{secret} = $self->secret;
114    }
115    return Facebook::Graph::Query->new(%params);
116}
117
118sub delete {
119    my ($self, $id, $cb) = @_;
120    my %query;
121    if ($self->has_access_token) {
122        $query{access_token} = uri_decode($self->access_token);
123    }
124    my $uri = $self->uri;
125    $uri->path($id);
126    $uri->query_form(%query);
127    http_request(DELETE => $uri->as_string, sub {
128        my ($response, $headers) = @_;
129        my %params = (
130            response => $response,
131            headers => $headers,
132            uri => $uri
133        );
134        if ($self->has_secret) {
135            $params{secret} = $self->secret;
136        }
137        $cb->(Facebook::Graph::Response->new(%params));
138    });
139    () # return nothing
140}
141
142sub picture {
143    my ($self, $object_name) = @_;
144    return Facebook::Graph::Picture->new( object_name => $object_name );
145}
146
147sub add_post {
148    my ($self, $object_name) = @_;
149    my %params;
150    if ($object_name) {
151        $params{object_name} = $object_name;
152    }
153    if ($self->has_access_token) {
154        $params{access_token} = $self->access_token;
155    }
156    if ($self->has_secret) {
157        $params{secret} = $self->secret;
158    }
159    return Facebook::Graph::Publish::Post->new( %params );
160}
161
162sub add_checkin {
163    my ($self, $object_name) = @_;
164    my %params;
165    if ($object_name) {
166        $params{object_name} = $object_name;
167    }
168    if ($self->has_access_token) {
169        $params{access_token} = $self->access_token;
170    }
171    if ($self->has_secret) {
172        $params{secret} = $self->secret;
173    }
174    return Facebook::Graph::Publish::Checkin->new( %params );
175}
176
177sub add_like {
178    my ($self, $object_name) = @_;
179    my %params = (
180        object_name => $object_name,
181    );
182    if ($self->has_access_token) {
183        $params{access_token} = $self->access_token;
184    }
185    if ($self->has_secret) {
186        $params{secret} = $self->secret;
187    }
188    return Facebook::Graph::Publish::Like->new( %params );
189}
190
191sub add_comment {
192    my ($self, $object_name) = @_;
193    my %params = (
194        object_name => $object_name,
195    );
196    if ($self->has_access_token) {
197        $params{access_token} = $self->access_token;
198    }
199    if ($self->has_secret) {
200        $params{secret} = $self->secret;
201    }
202    return Facebook::Graph::Publish::Comment->new( %params );
203}
204
205sub add_note {
206    my ($self) = @_;
207    my %params;
208    if ($self->has_access_token) {
209        $params{access_token} = $self->access_token;
210    }
211    if ($self->has_secret) {
212        $params{secret} = $self->secret;
213    }
214    return Facebook::Graph::Publish::Note->new( %params );
215}
216
217sub add_link {
218    my ($self) = @_;
219    my %params;
220    if ($self->has_access_token) {
221        $params{access_token} = $self->access_token;
222    }
223    if ($self->has_secret) {
224        $params{secret} = $self->secret;
225    }
226    return Facebook::Graph::Publish::Link->new( %params );
227}
228
229sub add_event {
230    my ($self, $object_name) = @_;
231    my %params;
232    if ($object_name) {
233        $params{object_name} = $object_name;
234    }
235    if ($self->has_access_token) {
236        $params{access_token} = $self->access_token;
237    }
238    if ($self->has_secret) {
239        $params{secret} = $self->secret;
240    }
241    return Facebook::Graph::Publish::Event->new( %params );
242}
243
244sub rsvp_maybe {
245    my ($self, $object_name) = @_;
246    my %params = (
247        object_name => $object_name,
248    );
249    if ($self->has_access_token) {
250        $params{access_token} = $self->access_token;
251    }
252    if ($self->has_secret) {
253        $params{secret} = $self->secret;
254    }
255    return Facebook::Graph::Publish::RSVPMaybe->new( %params );
256}
257
258sub rsvp_attending {
259    my ($self, $object_name) = @_;
260    my %params = (
261        object_name => $object_name,
262    );
263    if ($self->has_access_token) {
264        $params{access_token} = $self->access_token;
265    }
266    if ($self->has_secret) {
267        $params{secret} = $self->secret;
268    }
269    return Facebook::Graph::Publish::RSVPAttending->new( %params );
270}
271
272sub rsvp_declined {
273    my ($self, $object_name) = @_;
274    my %params = (
275        object_name => $object_name,
276    );
277    if ($self->has_access_token) {
278        $params{access_token} = $self->access_token;
279    }
280    if ($self->has_secret) {
281        $params{secret} = $self->secret;
282    }
283    return Facebook::Graph::Publish::RSVPDeclined->new( %params );
284}
285
286
287
288no Any::Moose;
289__PACKAGE__->meta->make_immutable;
290
291=head1 NAME
292
293Facebook::Graph - A fast and easy way to integrate your apps with Facebook.
294
295=head1 VERSION
296
297version 1.0300
298
299=head1 SYNOPSIS
300
301 my $fb = Facebook::Graph->new;
302 my $sarah_bownds = $fb->fetch('sarahbownds');
303 my $perl_page = $fb->fetch('16665510298');
304 
305Or better yet:
306
307 my $sarah_bownds = $fb->query
308    ->find('sarahbownds')
309    ->include_metadata
310    ->select_fields(qw( id name picture ))
311    ->request
312    ->as_hashref;
313   
314 my $sarahs_picture_uri = $fb->picture('sarahbownds')->get_large->uri_as_string;
315
316Or fetching a response from a URI you already have:
317
318 my $response = $fb->query
319    ->request('https://graph.facebook.com/btaylor')
320    ->as_hashref;
321 
322 
323=head2 Building A Privileged App
324
325 my $fb = Facebook::Graph->new(
326    app_id          => $facebook_application_id,
327    secret          => $facebook_application_secret,
328    postback        => 'https://www.yourapplication.com/facebook/oauth/postback',
329 );
330
331Get the user to authorize your app (only needed if you want to fetch non-public information or publish stuff):
332
333 my $uri = $fb
334    ->authorize
335    ->extend_permissions(qw(offline_access publish_stream))
336    ->uri_as_string;
337
338 # redirect the user's browser to $uri
339
340Handle the Facebook authorization code postback:
341
342 my $q = Plack::Request->new($env);
343 $fb->request_access_token($q->query_param('code'));
344 
345Or if you already had the access token:
346
347 $fb->access_token($token);
348 
349Get some info:
350
351 my $user = $fb->fetch('me');
352 my $friends = $fb->fetch('me/friends');
353 my $sarah_bownds = $fb->fetch('sarahbownds');
354
355=head1 DESCRIPTION
356
357This is a Perl interface to the Facebook Graph API L<http://developers.facebook.com/docs/api>. With this module you can currently query public Facebook data, query privileged Facebook data, and build a privileged Facebook application. See the TODO for all that this module cannot yet do.
358
359For example code, see L<Facebook::Graph::Cookbook>.
360
361B<WARNING:> The work on this module has only just begun because the Graph API itself isn't very new, and I'm only working on it as I have some tuits. Therefore things are potentially subject to change drastically with each release.
362
363
364=head1 METHODS
365
366=head2 new ( [ params ] )
367
368The constructor.
369
370=head3 params
371
372A hash of base parameters, just so you don't have to pass them around. If you only want to do public queries then these params are not needed.
373
374=over
375
376=item access_token
377
378An access token string used to make Facebook requests as a privileged user. Required if you want to make privileged queries or perform privileged actions on Facebook objects.
379
380=item app_id
381
382The application id that you get from Facebook after registering (L<http://developers.facebook.com/setup/>) your application on their site. Required if you'll be calling the C<request_access_token>, C<convert_sessions>, or C<authorize> methods.
383
384=item secret
385
386The application secret that you get from Facebook after registering your application. Required if you'll be calling the C<request_access_token> or C<convert_sessions> methods.
387
388=item postback
389
390The URI that Facebook should post your authorization code back to. Required if you'll be calling the C<request_access_token> or C<authorize> methods.
391
392B<NOTE:> It must be a sub URI of the URI that you put in the Application Settings > Connect > Connect URL field of your application's profile on Facebook.
393
394=back
395
396
397=head2 authorize ( )
398
399Creates a L<Facebook::Graph::Authorize> object, which can be used to get permissions from a user for your application.
400
401
402=head2 request_access_token ( code )
403
404Creates a L<Facebook::Graph::AccessToken> object and fetches an access token from Facebook, which will allow everything you do with Facebook::Graph to work within user privileges rather than through the public interface. Returns a L<Facebook::Graph::AccessToken::Response> object, and also sets the C<access_token> property in the Facebook::Graph object.
405
406=head3 code
407
408An authorization code string that you should have gotten by going through the C<authorize> process.
409
410
411=head2 query ( )
412
413Creates a L<Facebook::Graph::Query> object, which can be used to fetch and search data from Facebook.
414
415
416=head2 fetch ( id )
417
418Returns a hash reference of an object from facebook. A quick way to grab an object from Facebook. These two statements are identical:
419
420 my $sarah = $fb->fetch('sarahbownds');
421 
422 my $sarah = $fb->query->find('sarahbownds')->request->as_hashref;
423
424=head3 id
425
426An profile id like C<sarahbownds> or an object id like C<16665510298> for the Perl page.
427
428
429=head2 picture ( id )
430
431Returns a L<Facebook::Graph::Picture> object, which can be used to generate the URLs of the pictures of any object on Facebook.
432
433=head3 id
434
435An profile id like C<sarahbownds> or an object id like C<16665510298> for the Perl page.
436
437
438
439=head2 add_post ( [ id ] )
440
441Creates a L<Facebook::Graph::Publish::Post> object, which can be used to publish data to a user's feed/wall.
442
443=head3 id
444
445Optionally provide an object id to place it on. Requires that you have administrative access to that page/object.
446
447
448=head2 add_checkin ( [ id ] )
449
450Creates a L<Facebook::Graph::Publish::Checkin> object, which can be used to publish a checkin to a location.
451
452=head3 id
453
454Optionally provide an user id to check in. Requires that you have administrative access to that user.
455
456
457=head2 add_like ( id )
458
459Creates a L<Facebook::Graph::Publish::Like> object to tell everyone about a post you like.
460
461=head3 id
462
463The id of a post you like.
464
465
466=head2 add_comment ( id )
467
468Creates a L<Facebook::Graph::Publish::Comment> object that you can use to comment on a note.
469
470=head3 id
471
472The id of the post you want to comment on.
473
474
475=head2 add_note ( )
476
477Creates a L<Facebook::Graph::Publish::Note> object, which can be used to publish notes.
478
479
480=head2 add_link ( )
481
482Creates a L<Facebook::Graph::Publish::Link> object, which can be used to publish links.
483
484
485=head2 add_event ( [id] )
486
487Creates a L<Facebook::Graph::Publish::Event> object, which can be used to publish events.
488
489=head3 id
490
491Optionally provide an object id to place it on. Requires that you have administrative access to that page/object.
492
493
494
495=head2 rsvp_maybe ( id )
496
497RSVP as 'maybe' to an event.
498
499=head3 id
500
501The id of an event.
502
503=head2 rsvp_attending ( id )
504
505RSVP as 'attending' to an event.
506
507=head3 id
508
509The id of an event.
510
511=head2 rsvp_declined ( id )
512
513RSVP as 'declined' to an event.
514
515=head3 id
516
517The id of an event.
518
519
520
521=head2 convert_sessions ( sessions )
522
523A utility method to convert old sessions into access tokens that can be used with the Graph API. Returns an array reference of hash references of access tokens.
524
525 [
526   {
527     "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
528     "expires": 1271649600,
529   },
530   ...
531 ]
532
533See also L<Facebook::Graph::Session>.
534
535=head3 sessions
536
537An array reference of session ids from the old Facebook API.
538
539
540=head2 parse_signed_request ( signed_request )
541
542Allows the decoding of signed requests for canvas applications to ensure data passed back from Facebook isn't tampered with. You can read more about this at L<http://developers.facebook.com/docs/authentication/canvas>.
543
544=head3 signed_request
545
546A signature string passed from Facebook. To capture a signed request your app must be displayed within the Facebook canvas page and then you must pull the query parameter called C<signed_request> from the query string.
547
548B<NOTE:> To get this passed to your app you must enable it in your migration settings for your app (L<http://www.facebook.com/developers/>).
549
550=head1 EXCEPTIONS
551
552This module throws exceptions when it encounters a problem. It uses L<Ouch> to throw the exception, and the Exception typically takes 3 parts: code, message, and a data portion that is the URI that was originally requested. For example:
553
554 eval { $fb->call_some_method };
555 if (kiss 500) {
556   say "error: ". $@->message;
557   say "uri: ".$@->data;
558 }
559 else {
560   throw $@; # rethrow the error
561 }
562
563
564=head1 TODO
565
566I still need to add publishing albums/photos, deleting of content, impersonation, and analytics to have a feature complete API. In addition, the module could use a lot more tests.
567
568
569=head1 PREREQS
570
571L<Any::Moose>
572L<JSON>
573L<AnyEvent::HTTP>
574L<Mozilla::CA>
575L<URI>
576L<DateTime>
577L<DateTime::Format::Strptime>
578L<MIME::Base64::URLSafe>
579L<URI::Encode>
580L<Ouch>
581
582=head2 Optional
583
584L<Digest::SHA> is used for signed requests. If you don't plan on using the signed request feature, then you do not need to install Digest::SHA.
585
586=head1 SUPPORT
587
588=over
589
590=item Repository
591
592L<http://github.com/rizen/Facebook-Graph>
593
594=item Bug Reports
595
596L<http://github.com/rizen/Facebook-Graph/issues>
597
598=back
599
600
601=head1 SEE ALSO
602
603If you're looking for a fully featured Facebook client in Perl I highly recommend L<WWW::Facebook::API>. It does just about everything, it just uses the old Facebook API.
604
605=head1 AUTHOR
606
607JT Smith <jt_at_plainblack_dot_com>
608
609=head1 LEGAL
610
611Facebook::Graph is Copyright 2010 Plain Black Corporation (L<http://www.plainblack.com>) and is licensed under the same terms as Perl itself.
612
613=cut
Note: See TracBrowser for help on using the repository browser.