source: perl/modules/Facebook/lib/Facebook/Graph.pm @ 9820d55

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