Ignore:
Timestamp:
Sep 20, 2011, 11:15:32 PM (11 years ago)
Author:
Edward Z. Yang <ezyang@mit.edu>
Branches:
master, release-1.9
Children:
b7fa912
Parents:
2a42248
git-author:
Edward Z. Yang <ezyang@mit.edu> (07/12/11 09:17:04)
git-committer:
Edward Z. Yang <ezyang@mit.edu> (09/20/11 23:15:32)
Message:
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>
File:
1 edited

Legend:

Unmodified
Added
Removed
  • perl/modules/Facebook/lib/BarnOwl/Module/Facebook/Handle.pm

    rd9c6631 r7777ac2  
    165165sub check_result {
    166166    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") {
    170168        $self->{logged_in} = 0;
    171169        $self->facebook_do_auth;
     
    185183    return unless $self->{logged_in};
    186184
    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            }
    222223        }
    223     }
    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.
     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    });
    231232}
    232233
     
    244245    $self->{topics} = {};
    245246
    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
    324295                my $msg = BarnOwl::Message->new(
    325296                    type      => 'Facebook',
    326                     sender    => $comment->{from}{name},
    327                     sender_id => $comment->{from}{id},
     297                    sender    => $post->{from}{name},
     298                    sender_id => $post->{from}{id},
    328299                    name      => $name,
    329300                    name_id   => $name_id,
    330301                    direction => 'in',
    331                     body      => $comment->{message},
     302                    body      => $self->format_body($post),
    332303                    post_id   => $post_id,
    333304                    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},
    335310                   );
    336311                BarnOwl::queue_message($msg);
    337312            }
     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            }
    338343        }
    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    });
    346348}
    347349
     
    375377    my $msg = shift;
    376378
     379    my $cont = sub { $self->sleep(0); };
     380
    377381    if (defined $user) {
    378382        $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);
    381384    } 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    }
    386387}
    387388
     
    392393    my $msg = shift;
    393394
    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); });
    397396}
    398397
     
    416415
    417416    $self->{cfg}->{token} = $1;
    418     if ($self->facebook_do_auth) {
     417    $self->facebook_do_auth(sub {
    419418        my $raw_cfg = to_json($self->{cfg});
    420419        BarnOwl::admin_message('Facebook', "Add this as the contents of your ~/.owl/facebook file:\n$raw_cfg");
    421     }
     420    });
     421    return;
    422422}
    423423
    424424sub facebook_do_auth {
    425425    my $self = shift;
     426    my $success = shift || sub {};
    426427    if (!defined $self->{cfg}->{token}) {
    427428        BarnOwl::admin_message('Facebook', "Login to Facebook at ".$self->{login_url}
     
    437438    $self->{facebook}->access_token($self->{cfg}->{token});
    438439    # 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    });
    452454}
    453455
Note: See TracChangeset for help on using the changeset viewer.