Changeset 9820d55 for perl/modules
- Timestamp:
- Sep 19, 2011, 1:31:35 PM (13 years ago)
- Children:
- c104b43
- Parents:
- cfca761
- git-author:
- Edward Z. Yang <ezyang@mit.edu> (07/12/11 09:17:04)
- git-committer:
- Edward Z. Yang <ezyang@mit.edu> (09/19/11 13:31:35)
- Location:
- perl/modules/Facebook
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
perl/modules/Facebook/README
r9776805 r9820d55 24 24 25 25 INFELICITIES 26 * Polling Facebook is pretty slow (on order of a second or more),27 and blocks the entire BarnOwl interface. We have a plan for28 fixing this, which involves creating an async version of29 Facebook::Graph. I have been in contact with the original30 author JT Smith about this.31 32 26 * BarnOwl will not receive all comments for news feed items, only 33 27 comments for items that were recently published. There is not -
perl/modules/Facebook/lib/BarnOwl/Module/Facebook/Handle.pm
rcb5d448 r9820d55 165 165 sub check_result { 166 166 my $self = shift; 167 if (kiss 400) { 168 # Ugh, no easy way of accessing the JSON error type 169 # which is OAuthException. 167 if (kiss "OAuthException") { 170 168 $self->{logged_in} = 0; 171 169 $self->facebook_do_auth; … … 185 183 return unless $self->{logged_in}; 186 184 187 my $friends = eval { $self->{facebook}->fetch('me/friends'); }; 188 return unless $self->check_result; 189 190 $self->{friends} = {}; 191 192 for my $friend (@{$friends->{data}}) { 193 if (defined $self->{friends}{$friend->{name}}) { 194 # XXX We should try a little harder here, rather than just 195 # tacking on a number. Ideally, we should be able to 196 # calculate some extra piece of information that the user 197 # needs to disambiguate between the two users. An old 198 # version of Facebook used to disambiguate with your primary 199 # network (so you might have Edward Yang (MIT) and Edward 200 # Yang (Cambridge), the idea being that users in the same 201 # network would probably have already disambiguated 202 # themselves with middle names or nicknames. We no longer 203 # get network information, since Facebook axed that 204 # information, but the Education/Work fields may still be 205 # a reasonable approximation (but which one do you pick?! 206 # The most recent one.) Since getting this information 207 # involves extra queries, there are also caching and 208 # efficiency concerns (though hopefully you don't have too 209 # many friends with the same name). Furthermore, accessing 210 # this information requires a pretty hefty extra set of 211 # permissions requests, which we don't currently ask for. 212 # It may just be better to let users specify custom 213 # aliases for Facebook users, which are added into this 214 # hash. See also username support. 215 warn "Duplicate friend name " . $friend->{name}; 216 my $name = $friend->{name}; 217 my $i = 2; 218 while (defined $self->{friends}{$friend->{name} . ' ' . $i}) { $i++; } 219 $self->{friends}{$friend->{name} . ' ' . $i} = $friend->{id}; 220 } else { 221 $self->{friends}{$friend->{name}} = $friend->{id}; 185 $self->{facebook}->query->find('me/friends')->request(sub { 186 my $response = shift; 187 my $friends = eval { $response->as_hashref }; 188 return unless $self->check_result; 189 190 $self->{friends} = {}; 191 192 for my $friend (@{$friends->{data}}) { 193 if (defined $self->{friends}{$friend->{name}}) { 194 # XXX We should try a little harder here, rather than just 195 # tacking on a number. Ideally, we should be able to 196 # calculate some extra piece of information that the user 197 # needs to disambiguate between the two users. An old 198 # version of Facebook used to disambiguate with your primary 199 # network (so you might have Edward Yang (MIT) and Edward 200 # Yang (Cambridge), the idea being that users in the same 201 # network would probably have already disambiguated 202 # themselves with middle names or nicknames. We no longer 203 # get network information, since Facebook axed that 204 # information, but the Education/Work fields may still be 205 # a reasonable approximation (but which one do you pick?! 206 # The most recent one.) Since getting this information 207 # involves extra queries, there are also caching and 208 # efficiency concerns (though hopefully you don't have too 209 # many friends with the same name). Furthermore, accessing 210 # this information requires a pretty hefty extra set of 211 # permissions requests, which we don't currently ask for. 212 # It may just be better to let users specify custom 213 # aliases for Facebook users, which are added into this 214 # hash. See also username support. 215 warn "Duplicate friend name " . $friend->{name}; 216 my $name = $friend->{name}; 217 my $i = 2; 218 while (defined $self->{friends}{$friend->{name} . ' ' . $i}) { $i++; } 219 $self->{friends}{$friend->{name} . ' ' . $i} = $friend->{id}; 220 } else { 221 $self->{friends}{$friend->{name}} = $friend->{id}; 222 } 222 223 } 223 } 224 225 # XXX We should also have support for usernames, and not just real226 # names. However, since this data is not returned by the friends227 # query, it would require a rather expensive set of queries. We228 # might try to preserve old data, but all-in-all it's a bit229 # complicated. One possible way of fixing this is to construct a230 # custom FQL query that joins the friends table and the users table.224 225 # XXX We should also have support for usernames, and not just real 226 # names. However, since this data is not returned by the friends 227 # query, it would require a rather expensive set of queries. We 228 # might try to preserve old data, but all-in-all it's a bit 229 # complicated. One possible way of fixing this is to construct a 230 # custom FQL query that joins the friends table and the users table. 231 }); 231 232 } 232 233 … … 244 245 $self->{topics} = {}; 245 246 246 my $updates = eval { 247 $self->{facebook} 248 ->query 249 ->from("my_news") 250 # Not using this, because we want to pick up comment 251 # updates. We need to manually de-duplicate, though. 252 # ->where_since("@" . $self->{last_poll}) 253 # Facebook doesn't actually give us that many results. 254 # But it can't hurt to ask! 255 ->limit_results(200) 256 ->request 257 ->as_hashref 258 }; 259 return unless $self->check_result; 260 261 my $new_last_poll = $self->{last_poll}; 262 for my $post (reverse @{$updates->{data}}) { 263 # No app invites, thanks! (XXX make configurable) 264 if ($post->{type} eq 'link' && $post->{application}) { 265 next; 266 } 267 268 # XXX Filtering out interest groups for now 269 # A more reasonable strategy may be to show their 270 # posts, but not the comments. 271 if (defined $post->{from}{category}) { 272 next; 273 } 274 275 # There can be multiple recipients! Strange! Pick the first one. 276 my $name = $post->{to}{data}[0]{name} || $post->{from}{name}; 277 my $name_id = $post->{to}{data}[0]{id} || $post->{from}{id}; 278 my $post_id = $post->{id}; 279 280 my $topic; 281 if (defined $old_topics->{$post_id}) { 282 $topic = $old_topics->{$post_id}; 283 $self->{topics}->{$post_id} = $topic; 284 } else { 285 my @keywords = keywords($post->{name} || $post->{message}); 286 $topic = $keywords[0] || 'personal'; 287 $topic =~ s/ /-/g; 288 $self->{topics}->{$post_id} = $topic; 289 } 290 291 # Only handle post if it's new 292 my $created_time = str2time($post->{created_time}); 293 if ($created_time >= $self->{last_poll}) { 294 # XXX indexing is fragile 295 my $msg = BarnOwl::Message->new( 296 type => 'Facebook', 297 sender => $post->{from}{name}, 298 sender_id => $post->{from}{id}, 299 name => $name, 300 name_id => $name_id, 301 direction => 'in', 302 body => $self->format_body($post), 303 post_id => $post_id, 304 topic => $topic, 305 time => asctime(localtime $created_time), 306 # XXX The intent is to get the 'Comment' link, which also 307 # serves as a canonical link to the post. The {name} 308 # field should equal 'Comment'. 309 permalink => $post->{actions}[0]{link}, 310 ); 311 BarnOwl::queue_message($msg); 312 } 313 314 # This will interleave times (they'll all be organized by parent 315 # post), but since we don't expect too many updates between 316 # polls this is pretty acceptable. 317 my $updated_time = str2time($post->{updated_time}); 318 if ($updated_time >= $self->{last_poll} && defined $post->{comments}{data}) { 319 for my $comment (@{$post->{comments}{data}}) { 320 my $comment_time = str2time($comment->{created_time}); 321 if ($comment_time < $self->{last_poll}) { 322 next; 323 } 247 $self->{facebook} 248 ->query 249 ->from("my_news") 250 # Not using this, because we want to pick up comment 251 # updates. We need to manually de-duplicate, though. 252 # ->where_since("@" . $self->{last_poll}) 253 # Facebook doesn't actually give us that many results. 254 # But it can't hurt to ask! 255 ->limit_results(200) 256 ->request(sub { 257 258 my $updates = eval { shift->as_hashref }; 259 return unless $self->check_result; 260 261 my $new_last_poll = $self->{last_poll}; 262 for my $post (reverse @{$updates->{data}}) { 263 # No app invites, thanks! (XXX make configurable) 264 if ($post->{type} eq 'link' && $post->{application}) { 265 next; 266 } 267 268 # XXX Filtering out interest groups for now 269 # A more reasonable strategy may be to show their 270 # posts, but not the comments. 271 if (defined $post->{from}{category}) { 272 next; 273 } 274 275 # There can be multiple recipients! Strange! Pick the first one. 276 my $name = $post->{to}{data}[0]{name} || $post->{from}{name}; 277 my $name_id = $post->{to}{data}[0]{id} || $post->{from}{id}; 278 my $post_id = $post->{id}; 279 280 my $topic; 281 if (defined $old_topics->{$post_id}) { 282 $topic = $old_topics->{$post_id}; 283 $self->{topics}->{$post_id} = $topic; 284 } else { 285 my @keywords = keywords($post->{name} || $post->{message}); 286 $topic = $keywords[0] || 'personal'; 287 $topic =~ s/ /-/g; 288 $self->{topics}->{$post_id} = $topic; 289 } 290 291 # Only handle post if it's new 292 my $created_time = str2time($post->{created_time}); 293 if ($created_time >= $self->{last_poll}) { 294 # XXX indexing is fragile 324 295 my $msg = BarnOwl::Message->new( 325 296 type => 'Facebook', 326 sender => $ comment->{from}{name},327 sender_id => $ comment->{from}{id},297 sender => $post->{from}{name}, 298 sender_id => $post->{from}{id}, 328 299 name => $name, 329 300 name_id => $name_id, 330 301 direction => 'in', 331 body => $ comment->{message},302 body => $self->format_body($post), 332 303 post_id => $post_id, 333 304 topic => $topic, 334 time => asctime(localtime $comment_time), 305 time => asctime(localtime $created_time), 306 # XXX The intent is to get the 'Comment' link, which also 307 # serves as a canonical link to the post. The {name} 308 # field should equal 'Comment'. 309 permalink => $post->{actions}[0]{link}, 335 310 ); 336 311 BarnOwl::queue_message($msg); 337 312 } 313 314 # This will interleave times (they'll all be organized by parent 315 # post), but since we don't expect too many updates between 316 # polls this is pretty acceptable. 317 my $updated_time = str2time($post->{updated_time}); 318 if ($updated_time >= $self->{last_poll} && defined $post->{comments}{data}) { 319 for my $comment (@{$post->{comments}{data}}) { 320 my $comment_time = str2time($comment->{created_time}); 321 if ($comment_time < $self->{last_poll}) { 322 next; 323 } 324 my $msg = BarnOwl::Message->new( 325 type => 'Facebook', 326 sender => $comment->{from}{name}, 327 sender_id => $comment->{from}{id}, 328 name => $name, 329 name_id => $name_id, 330 direction => 'in', 331 body => $comment->{message}, 332 post_id => $post_id, 333 topic => $topic, 334 time => asctime(localtime $comment_time), 335 permalink => "", 336 ); 337 BarnOwl::queue_message($msg); 338 } 339 } 340 if ($updated_time + 1 > $new_last_poll) { 341 $new_last_poll = $updated_time + 1; 342 } 338 343 } 339 if ($updated_time + 1 > $new_last_poll) { 340 $new_last_poll = $updated_time + 1; 341 } 342 } 343 # old_topics gets GC'd 344 345 $self->{last_poll} = $new_last_poll; 344 # old_topics gets GC'd 345 346 $self->{last_poll} = $new_last_poll; 347 }); 346 348 } 347 349 … … 375 377 my $msg = shift; 376 378 379 my $cont = sub { $self->sleep(0); }; 380 377 381 if (defined $user) { 378 382 $user = $self->{friends}{$user} || $user; 379 eval { $self->{facebook}->add_post($user)->set_message($msg)->publish; }; 380 return unless $self->check_result; 383 $self->{facebook}->add_post($user)->set_message($msg)->publish($cont); 381 384 } else { 382 eval { $self->{facebook}->add_post->set_message($msg)->publish; }; 383 return unless $self->check_result; 384 } 385 $self->sleep(0); 385 $self->{facebook}->add_post->set_message($msg)->publish($cont); 386 } 386 387 } 387 388 … … 392 393 my $msg = shift; 393 394 394 eval { $self->{facebook}->add_comment($post_id)->set_message($msg)->publish; }; 395 return unless $self->check_result; 396 $self->sleep(0); 395 $self->{facebook}->add_comment($post_id)->set_message($msg)->publish(sub { $self->sleep(0); }); 397 396 } 398 397 … … 416 415 417 416 $self->{cfg}->{token} = $1; 418 if ($self->facebook_do_auth){417 $self->facebook_do_auth(sub { 419 418 my $raw_cfg = to_json($self->{cfg}); 420 419 BarnOwl::admin_message('Facebook', "Add this as the contents of your ~/.owl/facebook file:\n$raw_cfg"); 421 } 420 }); 421 return; 422 422 } 423 423 424 424 sub facebook_do_auth { 425 425 my $self = shift; 426 my $success = shift || sub {}; 426 427 if (!defined $self->{cfg}->{token}) { 427 428 BarnOwl::admin_message('Facebook', "Login to Facebook at ".$self->{login_url} … … 437 438 $self->{facebook}->access_token($self->{cfg}->{token}); 438 439 # Do a quick check to see if things are working 439 my $result = eval { $self->{facebook}->query()->find('me')->select_fields('name')->request->as_hashref; }; 440 if ($@) { 441 BarnOwl::admin_message('Facebook', "Failed to authenticate with '$@'!" 442 . "\nLogin to Facebook at ".$self->{login_url} 443 . "\nand run command ':facebook-auth URL' with the URL you are redirected to."); 444 return 0; 445 } else { 446 my $name = $result->{'name'}; 447 BarnOwl::admin_message('Facebook', "Successfully logged in to Facebook as $name!"); 448 $self->{logged_in} = 1; 449 $self->sleep(0); # start polling 450 return 1; 451 } 440 $self->{facebook}->query()->find('me')->select_fields('name')->request(sub { 441 my $result = eval { shift->as_hashref }; 442 if ($@) { 443 BarnOwl::admin_message('Facebook', "Failed to authenticate with '$@'!" 444 . "\nLogin to Facebook at ".$self->{login_url} 445 . "\nand run command ':facebook-auth URL' with the URL you are redirected to."); 446 } else { 447 my $name = $result->{'name'}; 448 BarnOwl::admin_message('Facebook', "Successfully logged in to Facebook as $name!"); 449 $self->{logged_in} = 1; 450 $self->sleep(0); # start polling 451 $success->(); 452 } 453 }); 452 454 } 453 455 -
perl/modules/Facebook/lib/Facebook/Graph.pm
rcfca761 r9820d55 62 62 63 63 sub request_access_token { 64 my ($self, $code ) = @_;65 my $token =Facebook::Graph::AccessToken->new(64 my ($self, $code, $cb) = @_; 65 Facebook::Graph::AccessToken->new( 66 66 code => $code, 67 67 postback => $self->postback, 68 68 secret => $self->secret, 69 69 app_id => $self->app_id, 70 )->request; 71 $self->access_token($token->token); 72 return $token; 70 )->request(sub { 71 my ($response) = @_; 72 $self->access_token($response->token); 73 $cb->($response); 74 }); 73 75 } 74 76 75 77 sub convert_sessions { 76 my ($self, $sessions ) = @_;77 returnFacebook::Graph::Session->new(78 my ($self, $sessions, $cb) = @_; 79 Facebook::Graph::Session->new( 78 80 secret => $self->secret, 79 81 app_id => $self->app_id, 80 82 sessions => $sessions, 81 ) 82 ->request 83 ->as_hashref; 83 )->request($cb); # API change 84 84 } 85 85 … … 92 92 } 93 93 94 sub fetch { 95 my ($self, $object_name) = @_; 96 return $self->query->find($object_name)->request->as_hashref; 97 } 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 #} 98 102 99 103 sub query { … … 540 544 L<Any::Moose> 541 545 L<JSON> 542 L<LWP> 543 L<LWP::Protocol::https> 546 L<AnyEvent::HTTP> 544 547 L<Mozilla::CA> 545 548 L<URI> -
perl/modules/Facebook/lib/Facebook/Graph/AccessToken.pm
rcfca761 r9820d55 7 7 use Facebook::Graph::AccessToken::Response; 8 8 with 'Facebook::Graph::Role::Uri'; 9 use LWP::UserAgent;9 use AnyEvent::HTTP; 10 10 11 11 has app_id => ( … … 43 43 44 44 sub request { 45 my ($self) = @_; 46 my $response = LWP::UserAgent->new->get($self->uri_as_string); 47 return Facebook::Graph::AccessToken::Response->new(response => $response); 45 my ($self, $cb) = @_; 46 http_get $self->uri_as_string, sub { 47 my ($response, $headers) = @_; 48 $cb->(Facebook::Graph::AccessToken::Response->new( 49 response => $response, 50 headers => $headers, 51 uri => $self->uri_as_string 52 )); 53 }; 48 54 } 49 55 -
perl/modules/Facebook/lib/Facebook/Graph/AccessToken/Response.pm
rcfca761 r9820d55 18 18 lazy => 1, 19 19 default => sub { 20 # LOL error handling 20 21 my $self = shift; 21 my $response = $self->response; 22 if ($response->is_success) { 23 return URI->new('?'.$response->content)->query_param('access_token'); 24 } 25 else { 26 ouch $response->code, 'Could not fetch access token: '.$response->message, $response->request->uri->as_string; 27 } 22 return URI->new('?'.$self->response)->query_param('access_token'); 23 #else { 24 # ouch $response->code, 'Could not fetch access token: '.$response->message, $response->request->uri->as_string; 25 #} 28 26 } 29 27 ); … … 33 31 lazy => 1, 34 32 default => sub { 33 # LOL error handling 35 34 my $self = shift; 36 my $response = $self->response; 37 if ($response->is_success) { 38 return URI->new('?'.$response->content)->query_param('expires'); 39 } 40 else { 41 ouch $response->code, 'Could not fetch access token: '.$response->message, $response->request->uri->as_string; 42 } 35 return URI->new('?'.$self->response)->query_param('expires'); 36 #else { 37 # ouch $response->code, 'Could not fetch access token: '.$response->message, $response->request->uri->as_string; 38 #} 43 39 } 44 40 ); -
perl/modules/Facebook/lib/Facebook/Graph/Publish.pm
rcfca761 r9820d55 7 7 use Facebook::Graph::Response; 8 8 with 'Facebook::Graph::Role::Uri'; 9 use LWP::UserAgent; 9 use AnyEvent::HTTP; 10 use LWP::UserAgent; # XXX blegh 11 use HTTP::Request::Common; 10 12 use URI::Encode qw(uri_decode); 11 13 … … 42 44 43 45 sub publish { 44 my ($self ) = @_;46 my ($self, $cb) = @_; 45 47 my $uri = $self->uri; 46 48 $uri->path($self->object_name.$self->object_path); 47 my $response = LWP::UserAgent->new->post($uri, $self->get_post_params); 48 my %params = (response => $response); 49 if ($self->has_secret) { 50 $params{secret} = $self->secret; 49 # XXX blegh 50 my $request = LWP::UserAgent->new->request(POST $uri->as_string, $self->get_post_params); 51 http_post $uri->as_string, $request->content, sub { 52 warn "whooo"; 53 my ($response, $headers) = @_; 54 my %params = ( 55 response => $response, 56 headers => $headers, 57 uri => $uri->as_string 58 ); 59 if ($self->has_secret) { 60 $params{secret} = $self->secret; 61 } 62 $cb->(Facebook::Graph::Response->new(%params)); 51 63 } 52 return Facebook::Graph::Response->new(%params);53 64 } 54 65 -
perl/modules/Facebook/lib/Facebook/Graph/Query.pm
rcfca761 r9820d55 7 7 use Facebook::Graph::Response; 8 8 with 'Facebook::Graph::Role::Uri'; 9 use LWP::UserAgent;9 use AnyEvent::HTTP; 10 10 use URI::Encode qw(uri_decode); 11 11 … … 183 183 184 184 sub request { 185 my ($self, $uri) = @_; 186 $uri ||= $self->uri_as_string; 187 my $response = LWP::UserAgent->new->get($uri); 188 my %params = (response => $response); 189 if ($self->has_secret) { 190 $params{secret} = $self->secret; 191 } 192 return Facebook::Graph::Response->new(%params); 185 my ($self, $cb) = @_; 186 my $uri = $self->uri_as_string; 187 http_get $uri, sub { 188 my ($response, $headers) = @_; 189 my %params = ( 190 response => $response, 191 headers => $headers, 192 uri => $uri 193 ); 194 if ($self->has_secret) { 195 $params{secret} = $self->secret; 196 } 197 $cb->(Facebook::Graph::Response->new(%params)); 198 }; 193 199 } 194 200 -
perl/modules/Facebook/lib/Facebook/Graph/Response.pm
rcfca761 r9820d55 13 13 ); 14 14 15 has headers => ( 16 is => 'ro', 17 required=> 1, 18 ); 19 20 has uri => ( 21 is => 'ro', 22 required=> 1, 23 ); 24 15 25 has as_string => ( 16 26 is => 'ro', … … 18 28 default => sub { 19 29 my $self = shift; 20 return $self->response->content; 30 if (!defined $self->response) { 31 ouch $self->headers->{Status}, $self->headers->{Reason}, $self->uri; 32 } 33 if ($self->headers->{Status} < 200 || $self->headers->{Status} >= 300) { 34 my $type = $self->headers->{Status}; 35 my $message = $self->response; 36 my $error = eval { JSON->new->decode($self->response) }; 37 unless ($@) { 38 $type = $error->{error}{type}; 39 $message = $error->{error}{message}; 40 } 41 ouch $type, 'Could not execute request ('.$self->uri.'): '.$message, $self->uri; 42 } 43 return $self->response; 21 44 }, 22 45 ); … … 27 50 default => sub { 28 51 my $self = shift; 29 my $response = $self->response; 30 if ($response->is_success) { 31 return $response->content; 32 } 33 else { 34 my $message = $response->message; 35 my $error = eval { JSON->new->decode($response->content) }; 36 unless ($@) { 37 $message = $error->{error}{type} . ' - ' . $error->{error}{message}; 38 } 39 ouch $response->code, 'Could not execute request ('.$response->request->uri->as_string.'): '.$message, $response->request->uri->as_string; 40 } 52 return $self->as_string; 41 53 }, 42 54 ); -
perl/modules/Facebook/lib/Facebook/Graph/Session.pm
rcfca761 r9820d55 7 7 use Facebook::Graph::Response; 8 8 with 'Facebook::Graph::Role::Uri'; 9 use LWP::UserAgent;9 use AnyEvent::HTTP; 10 10 11 11 has app_id => ( … … 38 38 39 39 sub request { 40 my ($self) = @_; 41 my $response = LWP::UserAgent->new->get($self->uri_as_string); 42 return Facebook::Graph::Response->new(response => $response); 40 my ($self, $cb) = @_; 41 http_get $self->uri_as_string, sub { 42 my ($response, $headers) = @_; 43 $cb->(Facebook::Graph::Response->new( 44 response => $response, 45 headers => $headers, 46 uri => $self->uri_as_string 47 )); 48 }; 43 49 } 44 50
Note: See TracChangeset
for help on using the changeset viewer.