Ignore:
Timestamp:
Sep 20, 2011, 11:15:32 PM (13 years ago)
Author:
Edward Z. Yang <ezyang@mit.edu>
Branches:
master, release-1.10, release-1.9
Children:
a4ae221
Parents:
65c2b3c
git-author:
Edward Z. Yang <ezyang@mit.edu> (06/22/11 21:53:17)
git-committer:
Edward Z. Yang <ezyang@mit.edu> (09/20/11 23:15:32)
Message:
Improve docs, error handling and refactor.

Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
Location:
perl/modules/Facebook/lib/BarnOwl/Module
Files:
2 edited

Legend:

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

    r8b62088 r01d186f  
    2424our $facebook_handle = undef;
    2525
    26 # did not implement class monitoring
    27 # did not implement multiple accounts
    28 
    2926BarnOwl::new_variable_bool(
    3027    'facebook:poll',
     
    3229        default => 1,
    3330        summary => 'Poll Facebook for wall updates',
    34         # TODO: Make this configurable
     31        # XXX: Make poll time configurable
    3532        description => "If set, will poll Facebook every minute for updates.\n"
    3633     }
    3734 );
    3835
    39 sub fail {
    40     my $msg = shift;
    41     # reset global state here
    42     BarnOwl::admin_message('Facebook Error', $msg);
    43     die("Facebook Error: $msg\n");
     36sub init {
     37    my $conffile = BarnOwl::get_config_dir() . "/facebook";
     38    my $cfg = {};
     39    if (open(my $fh, "<", "$conffile")) {
     40        my $raw_cfg = do {local $/; <$fh>};
     41        close($fh);
     42
     43        eval { $cfg = from_json($raw_cfg); };
     44        if ($@) { BarnOwl::admin_message('Facebook', "Unable to parse $conffile: $@"); }
     45    }
     46    eval { $facebook_handle = BarnOwl::Module::Facebook::Handle->new($cfg); };
     47    if ($@) { BarnOwl::error($@); }
    4448}
    4549
    46 # We only load up when the conf file is present, to reduce resource
    47 # usage.  Though, probably not by very much, so maybe a 'facebook-init'
    48 # command would be more appropriate.
    49 
    50 my $conffile = BarnOwl::get_config_dir() . "/facebook";
    51 
    52 if (open(my $fh, "<", "$conffile")) {
    53     read_config($fh);
    54     close($fh);
    55 }
    56 
    57 sub read_config {
    58     my $fh = shift;
    59     my $raw_cfg = do {local $/; <$fh>};
    60     close($fh);
    61 
    62     my $cfg;
    63     if ($raw_cfg) {
    64         eval { $cfg = from_json($raw_cfg); };
    65         if($@) {
    66             fail("Unable to parse $conffile: $@");
    67         }
    68     } else {
    69         $cfg = {};
    70     }
    71 
    72     eval {
    73         $facebook_handle = BarnOwl::Module::Facebook::Handle->new($cfg);
    74     };
    75     if ($@) {
    76         BarnOwl::error($@);
    77         next;
    78     }
    79 }
    80 
    81 # Ostensibly here as a convenient shortcut for Perl hackery
    82 sub facebook {
    83     $facebook_handle->facebook(@_);
    84 }
     50init();
    8551
    8652# Should also add support for posting to other people's walls (this
     
    8955BarnOwl::new_command('facebook' => \&cmd_facebook, {
    9056    summary     => 'Post a status update to your wall from BarnOwl',
    91     usage       => 'facebook',
    92     description => 'Post a status update to your wall.'
     57    usage       => 'facebook [USER]',
     58    description => 'Post a status update to your wall, or post on another user\'s wall. Autocomplete is supported.'
    9359});
    9460
    95 # How do we allow people to specify the USER?
    9661#BarnOwl::new_command('facebook-message' => \&cmd_facebook_direct, {
    9762#    summary     => 'Send a Facebook message',
     
    10368    summary     => 'Comment on a wall post.',
    10469    usage       => 'facebook-comment POST_ID',
    105     description => 'Comment on a friend\'s wall post.  Using r is recommended.'
     70    description => 'Comment on a friend\'s wall post.'
    10671});
    10772
     
    11681    summary     => 'Force a poll of Facebook.',
    11782    usage       => 'facebook-poll',
    118     description => 'Get updates from Facebook.'
     83    description => 'Get updates (news, friends) from Facebook.'
    11984});
    120 
    121 # XXX: UI: probably should bug out immediately if we're not logged in.
    12285
    12386sub cmd_facebook {
     
    12588    my $user = shift;
    12689
     90    return unless check_ready();
     91
    12792    BarnOwl::start_edit_win(
    12893        defined $user ? "Write something to $user..." : "What's on your mind?",
    129         sub{ facebook($user, shift) }
     94        sub{ $facebook_handle->facebook($user, shift) }
    13095    );
    13196}
     
    135100    my $post_id = shift;
    136101
     102    return unless check_ready();
     103
    137104    my $topic = $facebook_handle->get_topic($post_id);
    138105
    139     # XXX UI should give some (better) indication /which/ conversation
    140     # is being commented on
    141106    BarnOwl::start_edit_win("Write a comment on '$topic'...",
    142107                            sub { $facebook_handle->facebook_comment($post_id, shift) });
     
    145110sub cmd_facebook_poll {
    146111    my $cmd = shift;
     112
     113    return unless check_ready();
    147114
    148115    $facebook_handle->sleep(0);
     
    154121    my $url = shift;
    155122
     123    if ($facebook_handle->{logged_in}) {
     124        BarnOwl::message("Already logged in. (To force, run ':reload-module Facebook', or deauthorize BarnOwl.)");
     125        return;
     126    }
     127
    156128    $facebook_handle->facebook_auth($url);
    157129}
    158130
     131sub check_ready {
     132    if (!$facebook_handle->{logged_in}) {
     133        BarnOwl::message("Need to login to Facebook first with ':facebook-auth'.");
     134        return 0;
     135    }
     136    return 1;
     137}
     138
    159139BarnOwl::filter(qw{facebook type ^facebook$});
    160 
    161 # Autocompletion support
    162140
    163141sub complete_user { return keys %{$facebook_handle->{friends}}; }
  • perl/modules/Facebook/lib/BarnOwl/Module/Facebook/Handle.pm

    r65c2b3c r01d186f  
    3535use Date::Parse;
    3636use POSIX;
     37use Ouch;
    3738
    3839use Scalar::Util qw(weaken);
     
    6566#   comments less frequently than polling for new posts.
    6667
    67 sub fail {
    68     my $self = shift;
    69     my $msg  = shift;
    70     undef $self->{facebook};
    71     die("[Facebook] Error: $msg\n");
    72 }
    73 
    7468sub new {
    7569    my $class = shift;
     
    8377        # but we can't assume that the BarnOwl lives on a publically
    8478        # addressable server (XXX maybe we can setup an option for this.)
    85         'last_friend_poll' => 0,
    8679        'friend_timer' => undef,
    8780
    8881        # Initialized with our 'time', but will be synced to Facebook
    8982        # soon enough. (Subtractive amount is just to preseed with some
    90         # values.)
    91         'last_poll' => time - 60 * 60 * 24 * 2,
     83        # values.) XXX Remove subtraction altogether.
     84        'last_poll' => time - 60 * 60,
    9285        'timer' => undef,
    9386
     
    9992        # $fb->authorize, but at time of writing (1.0300) they didn't support
    10093        # the response_type parameter.
    101         # 'login_url' => 'https://www.facebook.com/dialog/oauth?client_id=235537266461636&scope=read_stream,read_mailbox,publish_stream,offline_access&redirect_uri=http://www.facebook.com/connect/login_success.html&response_type=token',
     94        # 'login_url' => 'https://www.facebook.com/dialog/oauth?client_id=235537266461636&scope=read_stream,read_mailbox,publish_stream,offline_access,read_friendlists,rsvp_event,user_events&redirect_uri=http://www.facebook.com/connect/login_success.html&response_type=token',
    10295        # minified to fit in most terminal windows.
    103         'login_url' => 'http://goo.gl/yA42G',
     96        # Be careful about updating these values, since BarnOwl will not
     97        # notice that it is missing necessary permissions until it
     98        # attempt to perform an operation which fails due to lack of
     99        # permissions.
     100        'login_url' => 'http://goo.gl/rcM9s',
    104101
    105102        'logged_in' => 0,
     
    164161}
    165162
     163sub check_result {
     164    my $self = shift;
     165    if (kiss 400) {
     166        # Ugh, no easy way of accessing the JSON error type
     167        # which is OAuthException.
     168        $self->{logged_in} = 0;
     169        $self->facebook_do_auth;
     170        return 0;
     171    } elsif (hug) {
     172        my $code = $@->code;
     173        warn "Poll failed with $code: $@";
     174        return 0;
     175    }
     176    return 1;
     177}
     178
    166179sub poll_friends {
    167180    my $self = shift;
     
    171184
    172185    my $friends = eval { $self->{facebook}->fetch('me/friends'); };
    173     if ($@) {
    174         warn "Poll failed $@";
    175         return;
    176     }
    177 
    178     $self->{last_friend_poll} = time;
     186    return unless $self->check_result;
     187
    179188    $self->{friends} = {};
    180189
     
    195204            # The most recent one.)  Since getting this information
    196205            # involves extra queries, there are also caching and
    197             # efficiency concerns.
    198             #   We may want a facility for users to specify custom
     206            # efficiency concerns (though hopefully you don't have too
     207            # many friends with the same name).  Furthermore, accessing
     208            # this information requires a pretty hefty extra set of
     209            # permissions requests, which we don't currently ask for.
     210            #   It may just be better to let users specify custom
    199211            # aliases for Facebook users, which are added into this
    200212            # hash.  See also username support.
     
    213225    # query, it would require a rather expensive set of queries. We
    214226    # might try to preserve old data, but all-in-all it's a bit
    215     # complicated, so we don't bother.
     227    # complicated.  One possible way of fixing this is to construct a
     228    # custom FQL query that joins the friends table and the users table.
    216229}
    217230
     
    219232    my $self = shift;
    220233
    221     #return unless ( time - $self->{last_poll} ) >= 60;
    222234    return unless BarnOwl::getvar('facebook:poll') eq 'on';
    223235    return unless $self->{logged_in};
    224 
    225     #BarnOwl::message("Polling Facebook...");
    226236
    227237    # XXX Oh no! This blocks the user interface.  Not good.
     
    237247             ->from("my_news")
    238248             # Not using this, because we want to pick up comment
    239              # updates. We need to manually de-dup, though.
     249             # updates. We need to manually de-duplicate, though.
    240250             # ->where_since( "@" . $self->{last_poll} )
     251             # Facebook doesn't actually give us that many results.
     252             # But it can't hurt to ask!
    241253             ->limit_results( 200 )
    242              ->request()
    243              ->as_hashref()
     254             ->request
     255             ->as_hashref
    244256    };
    245     if ($@) {
    246         warn "Poll failed $@";
    247         return;
    248     }
     257    return unless $self->check_result;
    249258
    250259    my $new_last_poll = $self->{last_poll};
     
    262271        }
    263272
    264         # XXX Need to somehow access Facebook's user hiding
    265         # mechanism
    266 
    267273        # There can be multiple recipients! Strange! Pick the first one.
    268274        my $name    = $post->{to}{data}[0]{name} || $post->{from}{name};
     
    270276        my $post_id  = $post->{id};
    271277
     278        my $topic;
    272279        if (defined $old_topics->{$post_id}) {
    273             $self->{topics}->{$post_id} = $old_topics->{$post_id};
     280            $topic = $old_topics->{$post_id};
     281            $self->{topics}->{$post_id} = $topic;
    274282        } else {
    275283            my @keywords = keywords($post->{name} || $post->{message});
    276             my $topic = $keywords[0] || 'personal';
     284            $topic = $keywords[0] || 'personal';
    277285            $topic =~ s/ /-/g;
    278286            $self->{topics}->{$post_id} = $topic;
     
    292300                body      => $self->format_body($post),
    293301                post_id   => $post_id,
    294                 topic     => $self->get_topic($post_id),
     302                topic     => $topic,
    295303                time      => asctime(localtime $created_time),
    296304                # XXX The intent is to get the 'Comment' link, which also
     
    302310        }
    303311
    304         # This will have funky interleaving of times (they'll all be
    305         # sorted linearly), but since we don't expect too many updates between
     312        # This will interleave times (they'll all be organized by parent
     313        # post), but since we don't expect too many updates between
    306314        # polls this is pretty acceptable.
    307315        my $updated_time = str2time($post->{updated_time});
     
    320328                    direction => 'in',
    321329                    body      => $comment->{message},
    322                     post_id    => $post_id,
    323                     topic     => $self->get_topic($post_id),
     330                    post_id   => $post_id,
     331                    topic     => $topic,
    324332                    time      => asctime(localtime $comment_time),
    325333                   );
     
    355363}
    356364
     365# Invariant: we don't become logged out between entering text field
     366# and actually processing the request.  XXX I don't think this actually
     367# holds, but such a case would rarely happen.
     368
    357369sub facebook {
    358370    my $self = shift;
     
    361373    my $msg = shift;
    362374
    363     if (!defined $self->{facebook} || !$self->{logged_in}) {
    364         BarnOwl::admin_message('Facebook', 'You are not currently logged into Facebook.');
    365         return;
    366     }
    367375    if (defined $user) {
    368376        $user = $self->{friends}{$user} || $user;
    369         $self->{facebook}->add_post( $user )->set_message( $msg )->publish;
     377        eval { $self->{facebook}->add_post( $user )->set_message( $msg )->publish; };
     378        return unless $self->check_result;
    370379    } else {
    371         $self->{facebook}->add_post->set_message( $msg )->publish;
     380        eval { $self->{facebook}->add_post->set_message( $msg )->publish; };
     381        return unless $self->check_result;
    372382    }
    373383    $self->sleep(0);
     
    380390    my $msg = shift;
    381391
    382     $self->{facebook}->add_comment( $post_id )->set_message( $msg )->publish;
     392    eval { $self->{facebook}->add_comment( $post_id )->set_message( $msg )->publish; };
     393    return unless $self->check_result;
    383394    $self->sleep(0);
    384395}
     
    388399
    389400    my $url = shift;
     401
    390402    # http://www.facebook.com/connect/login_success.html#access_token=TOKEN&expires_in=0
    391403    $url =~ /access_token=([^&]+)/; # XXX Ew regex
     404
     405    if (!defined $1) {
     406        BarnOwl::message("Invalid URL.");
     407        return;
     408    }
    392409
    393410    $self->{cfg}->{token} = $1;
     
    402419    if ( ! defined $self->{cfg}->{token} ) {
    403420        BarnOwl::admin_message('Facebook', "Login to Facebook at ".$self->{login_url}
    404             . "\nand run command ':facebook-auth URL' with the URL you are redirected to.");
     421            . "\nand run command ':facebook-auth URL' with the URL you are redirected to."
     422            . "\n\nWhat does Barnowl use these permissions for?  As a desktop"
     423            . "\nmessaging application, we need persistent read/write access to your"
     424            . "\nnews feed and your inbox.  Other permissions are for pending"
     425            . "\nfeatures: we intend on adding support for event streaming, RSVP,"
     426            . "\nand BarnOwl filtering on friend lists."
     427        );
    405428        return 0;
    406429    }
    407430    $self->{facebook}->access_token($self->{cfg}->{token});
    408431    # Do a quick check to see if things are working
    409     my $result = eval { $self->{facebook}->fetch('me'); };
     432    my $result = eval { $self->{facebook}->query()->find('me')->select_fields('name')->request->as_hashref; };
    410433    if ($@) {
    411434        BarnOwl::admin_message('Facebook', "Failed to authenticate! Login to Facebook at ".$self->{login_url}
     
    421444}
    422445
    423 sub get_topic {
    424     my $self = shift;
    425 
    426     my $post_id = shift;
    427 
    428     return $self->{topics}->{$post_id} || 'personal';
    429 }
    430 
    4314461;
Note: See TracChangeset for help on using the changeset viewer.