Ignore:
Timestamp:
Jul 21, 2009, 9:27:32 PM (12 years ago)
Author:
Nelson Elhage <nelhage@mit.edu>
Branches:
master, release-1.7, release-1.8, release-1.9
Children:
7430aa4
Parents:
f93b81b
git-author:
Kevin Riggle <kevinr@free-dissociation.com> (07/21/09 21:27:02)
git-committer:
Nelson Elhage <nelhage@mit.edu> (07/21/09 21:27:32)
Message:
Multiple account support

Accounts are specified as a list of hashes in the ~/.owl/twitter file.

Adds 'poll_for_tweets', 'poll_for_dms', 'publish_tweets', 'default_sender',
and 'account_nickname' options to the twitter account hashes.  They do about
what they say on the tin.

Add arguments to :twitter, :twitter-direct, and :twitter-atreply to specify
the service to use (by nickname), with sane defaults, plus documentation.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • lib/BarnOwl/Module/Twitter.pm

    rf93b81b r159aaad  
    1414package BarnOwl::Module::Twitter;
    1515
    16 our $VERSION = 0.1;
     16our $VERSION = 0.2;
    1717
    1818use Net::Twitter;
     
    2121use BarnOwl;
    2222use BarnOwl::Hooks;
    23 use BarnOwl::Message::Twitter;
    24 use HTML::Entities;
    25 
    26 our $twitter;
     23use BarnOwl::Module::Twitter::Handle;
     24
     25our @twitter_handles;
     26our $default_handle;
    2727my $user     = BarnOwl::zephyr_getsender();
    2828my ($class)  = ($user =~ /(^[^@]+)/);
     
    3030my $opcode   = "twitter";
    3131my $use_reply_to = 0;
    32 
    33 sub fail {
    34     my $msg = shift;
    35     undef $twitter;
    36     BarnOwl::admin_message('Twitter Error', $msg);
    37     die("Twitter Error: $msg\n");
    38 }
    39 
    40 if($Net::Twitter::VERSION >= 2.06) {
    41     $use_reply_to = 1;
    42 }
     32my $next_service_to_poll = 0;
    4333
    4434my $desc = <<'END_DESC';
     
    8575 );
    8676
     77sub fail {
     78    my $msg = shift;
     79    undef @twitter_handles;
     80    BarnOwl::admin_message('Twitter Error', $msg);
     81    die("Twitter Error: $msg\n");
     82}
     83
    8784my $conffile = BarnOwl::get_config_dir() . "/twitter";
    8885open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
    89 my $cfg = do {local $/; <$fh>};
     86my $raw_cfg = do {local $/; <$fh>};
    9087close($fh);
    9188eval {
    92     $cfg = from_json($cfg);
     89    $raw_cfg = from_json($raw_cfg);
    9390};
    9491if($@) {
    95     fail("Unable to parse ~/.owl/twitter: $@");
    96 }
    97 
    98 my $twitter_args = { username   => $cfg->{user} || $user,
    99                      password   => $cfg->{password},
    100                      source     => 'barnowl',
    101                    };
    102 if (defined $cfg->{service}) {
    103     my $service = $cfg->{service};
    104     $twitter_args->{apiurl} = $service;
    105     my $apihost = $service;
    106     $apihost =~ s/^\s*http:\/\///;
    107     $apihost =~ s/\/.*$//;
    108     $apihost .= ':80' unless $apihost =~ /:\d+$/;
    109     $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
    110     my $apirealm = "Laconica API";
    111     $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
    112 }
    113 
    114 $twitter  = Net::Twitter->new(%$twitter_args);
    115 
    116 if(!defined($twitter->verify_credentials())) {
    117     fail("Invalid twitter credentials");
    118 }
    119 
    120 our $last_poll        = 0;
    121 our $last_direct_poll = 0;
    122 our $last_id          = undef;
    123 our $last_direct      = undef;
    124 
    125 unless(defined($last_id)) {
    126     eval {
    127         $last_id = $twitter->friends_timeline({count => 1})->[0]{id};
    128     };
    129     $last_id = 0 unless defined($last_id);
    130 }
    131 
    132 unless(defined($last_direct)) {
    133     eval {
    134         $last_direct = $twitter->direct_messages()->[0]{id};
    135     };
    136     $last_direct = 0 unless defined($last_direct);
    137 }
    138 
    139 eval {
    140     $twitter->{ua}->timeout(1);
    141 };
     92    fail("Unable to parse $conffile: $@");
     93}
     94
     95$raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
     96
     97for my $cfg (@$raw_cfg) {
     98    my $twitter_args = { username   => $cfg->{user} || $user,
     99                        password   => $cfg->{password},
     100                        source     => 'barnowl',
     101                    };
     102    if (defined $cfg->{service}) {
     103        my $service = $cfg->{service};
     104        $twitter_args->{apiurl} = $service;
     105        my $apihost = $service;
     106        $apihost =~ s/^\s*http:\/\///;
     107        $apihost =~ s/\/.*$//;
     108        $apihost .= ':80' unless $apihost =~ /:\d+$/;
     109        $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
     110        my $apirealm = "Laconica API";
     111        $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
     112    } else {
     113        $cfg->{service} = 'http://twitter.com';
     114    }
     115
     116    my $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
     117    push @twitter_handles, $twitter_handle;
     118    $default_handle = $twitter_handle if (!defined $twitter_handle && exists $cfg->{default_sender} && $cfg->{default_sender});
     119}
    142120
    143121sub match {
     
    155133       && match($m->opcode, $opcode)
    156134       && $m->auth eq 'YES') {
    157         twitter($m->body);
     135        for my $handle (@twitter_handles) {
     136            $handle->twitter($m->body);
     137        }
    158138    }
    159139}
    160140
    161141sub poll_messages {
    162     poll_twitter();
    163     poll_direct();
    164 }
    165 
    166 sub twitter_error {
    167     my $ratelimit = $twitter->rate_limit_status;
    168     unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
    169         # Twitter's just sucking, sleep for 5 minutes
    170         $last_direct_poll = $last_poll = time + 60*5;
    171         # die("Twitter seems to be having problems.\n");
    172         return;
    173     }
    174     if(exists($ratelimit->{remaining_hits})
    175        && $ratelimit->{remaining_hits} <= 0) {
    176         $last_direct_poll = $last_poll = $ratelimit->{reset_time_in_seconds};
    177         die("Twitter: ratelimited until " . $ratelimit->{reset_time} . "\n");
    178     } elsif(exists($ratelimit->{error})) {
    179         die("Twitter: ". $ratelimit->{error} . "\n");
    180         $last_direct_poll = $last_poll = time + 60*20;
    181     }
    182 }
    183 
    184 sub poll_twitter {
    185     return unless ( time - $last_poll ) >= 60;
    186     $last_poll = time;
    187     return unless BarnOwl::getvar('twitter:poll') eq 'on';
    188 
    189     my $timeline = $twitter->friends_timeline( { since_id => $last_id } );
    190     unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
    191         twitter_error();
    192         return;
    193     };
    194     if ( scalar @$timeline ) {
    195         for my $tweet ( reverse @$timeline ) {
    196             if ( $tweet->{id} <= $last_id ) {
    197                 next;
     142    my $handle = $twitter_handles[$next_service_to_poll];
     143    $next_service_to_poll = ($next_service_to_poll + 1) % scalar(@twitter_handles);
     144   
     145    $handle->poll_twitter() if (!exists $handle->{cfg}->{poll_for_tweets} || $handle->{cfg}->{poll_for_tweets});
     146    $handle->poll_direct() if (!exists $handle->{cfg}->{poll_for_dms} || $handle->{cfg}->{poll_for_dms});
     147}
     148
     149sub twitter {
     150    my $account = shift;
     151
     152    my $sent = 0;
     153    if (defined $account) {
     154        for my $handle (@twitter_handles) {
     155            if (defined $handle->{cfg}->{account_nickname} && $account eq $handle->{cfg}->{account_nickname}) {
     156                $handle->twitter(@_);
     157                $sent = 1;
     158                last;
    198159            }
    199             my $msg = BarnOwl::Message->new(
    200                 type      => 'Twitter',
    201                 sender    => $tweet->{user}{screen_name},
    202                 recipient => $cfg->{user} || $user,
    203                 direction => 'in',
    204                 source    => decode_entities($tweet->{source}),
    205                 location  => decode_entities($tweet->{user}{location}||""),
    206                 body      => decode_entities($tweet->{text}),
    207                 status_id => $tweet->{id},
    208                 service   => $cfg->{service},
    209                );
    210             BarnOwl::queue_message($msg);
    211         }
    212         $last_id = $timeline->[0]{id};
    213     } else {
    214         # BarnOwl::message("No new tweets...");
    215     }
    216 }
    217 
    218 sub poll_direct {
    219     return unless ( time - $last_direct_poll) >= 120;
    220     $last_direct_poll = time;
    221     return unless BarnOwl::getvar('twitter:poll') eq 'on';
    222 
    223     my $direct = $twitter->direct_messages( { since_id => $last_direct } );
    224     unless(defined($direct) && ref($direct) eq 'ARRAY') {
    225         twitter_error();
    226         return;
    227     };
    228     if ( scalar @$direct ) {
    229         for my $tweet ( reverse @$direct ) {
    230             if ( $tweet->{id} <= $last_direct ) {
    231                 next;
     160        }
     161        BarnOwl::message("No Twitter account named " . $account) unless $sent == 1
     162    }
     163    else {
     164        # broadcast
     165        for my $handle (@twitter_handles) {
     166            $handle->twitter(@_) if (!exists $handle->{cfg}->{publish_tweets} || $handle->{cfg}->{publish_tweets});
     167        }
     168    }
     169}
     170
     171sub twitter_direct {
     172    my $account = shift;
     173
     174    my $sent = 0;
     175    if (defined $account) {
     176        for my $handle (@twitter_handles) {
     177            if (defined $handle->{cfg}->{account_nickname} && $account eq $handle->{cfg}->{account_nickname}) {
     178                $handle->twitter_direct(@_);
     179                $sent = 1;
     180                last;
    232181            }
    233             my $msg = BarnOwl::Message->new(
    234                 type      => 'Twitter',
    235                 sender    => $tweet->{sender}{screen_name},
    236                 recipient => $cfg->{user} || $user,
    237                 direction => 'in',
    238                 location  => decode_entities($tweet->{sender}{location}||""),
    239                 body      => decode_entities($tweet->{text}),
    240                 isprivate => 'true',
    241                 service   => $cfg->{service},
    242                );
    243             BarnOwl::queue_message($msg);
    244         }
    245         $last_direct = $direct->[0]{id};
    246     } else {
    247         # BarnOwl::message("No new tweets...");
    248     }
    249 }
    250 
    251 sub twitter {
    252     my $msg = shift;
    253     my $reply_to = shift;
    254 
    255     if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
    256         twitter_direct($1, $2);
    257     } elsif(defined $twitter) {
    258         if($use_reply_to && defined($reply_to)) {
    259             $twitter->update({
    260                 status => $msg,
    261                 in_reply_to_status_id => $reply_to
    262                });
    263         } else {
    264             $twitter->update($msg);
    265         }
    266     }
    267 }
    268 
    269 sub twitter_direct {
    270     my $who = shift;
    271     my $msg = shift;
    272     if(defined $twitter) {
    273         $twitter->new_direct_message({
    274             user => $who,
    275             text => $msg
    276            });
    277         if(BarnOwl::getvar("displayoutgoing") eq 'on') {
    278             my $tweet = BarnOwl::Message->new(
    279                 type      => 'Twitter',
    280                 sender    => $cfg->{user} || $user,
    281                 recipient => $who,
    282                 direction => 'out',
    283                 body      => $msg,
    284                 isprivate => 'true',
    285                 service   => $cfg->{service},
    286                );
    287             BarnOwl::queue_message($tweet);
    288         }
     182        }
     183        BarnOwl::message("No Twitter account named " . $account) unless $sent == 1
     184    }
     185    elsif (defined $default_handle) {
     186        $default_handle->twitter_direct(@_);
     187    }
     188    else {
     189        $twitter_handles[0]->twitter_direct(@_);
    289190    }
    290191}
    291192
    292193sub twitter_atreply {
    293     my $to  = shift;
    294     my $id  = shift;
    295     my $msg = shift;
    296     if(defined($id)) {
    297         twitter("@".$to." ".$msg, $id);
    298     } else {
    299         twitter("@".$to." ".$msg);
     194    my $account = shift;
     195
     196    my $sent = 0;
     197    if (defined $account) {
     198        for my $handle (@twitter_handles) {
     199            if (defined $handle->{cfg}->{account_nickname} && $account eq $handle->{cfg}->{account_nickname}) {
     200                $handle->twitter_atreply(@_);
     201                $sent = 1;
     202                last;
     203            }
     204        }
     205        BarnOwl::message("No Twitter account named " . $account) unless $sent == 1
     206    }
     207    elsif (defined $default_handle) {
     208        $default_handle->twitter_atreply(@_);
     209    }
     210    else {
     211        $twitter_handles[0]->twitter_atreply(@_);
    300212    }
    301213}
     
    303215BarnOwl::new_command(twitter => \&cmd_twitter, {
    304216    summary     => 'Update Twitter from BarnOwl',
    305     usage       => 'twitter [message]',
    306     description => 'Update Twitter. If MESSAGE is provided, use it as your status.'
     217    usage       => 'twitter [ACCOUNT] [MESSAGE]',
     218    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
     219    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
    307220    . "\nOtherwise, prompt for a status message to use."
    308221   });
     
    310223BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
    311224    summary     => 'Send a Twitter direct message',
    312     usage       => 'twitter-direct USER',
    313     description => 'Send a Twitter Direct Message to USER'
     225    usage       => 'twitter-direct USER [ACCOUNT]',
     226    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
     227    . "\nor first service if no default is provided)"
    314228   });
    315229
     
    317231    {
    318232    summary     => 'Send a Twitter @ message',
    319     usage       => 'twitter-atreply USER',
    320     description => 'Send a Twitter @reply Message to USER'
     233    usage       => 'twitter-atreply USER [ACCOUNT]',
     234    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,'
     235    . "or first service if no default is provided)"
    321236    }
    322237);
     
    325240sub cmd_twitter {
    326241    my $cmd = shift;
    327     if(@_) {
    328         my $status = join(" ", @_);
    329         twitter($status);
    330     } else {
    331       BarnOwl::start_edit_win('What are you doing?', \&twitter);
    332     }
     242    my $account = shift;
     243    if (defined $account) {
     244        if(@_) {
     245            my $status = join(" ", @_);
     246            twitter($account, $status);
     247            return;
     248        }
     249    }
     250    BarnOwl::start_edit_win('What are you doing?' . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
    333251}
    334252
     
    337255    my $user = shift;
    338256    die("Usage: $cmd USER\n") unless $user;
    339     BarnOwl::start_edit_win("$cmd $user", sub{twitter_direct($user, shift)});
     257    my $account = shift;
     258    BarnOwl::start_edit_win("$cmd $user" . (defined $account ? " $account" : ""), sub{twitter_direct($account, $user, shift)});
    340259}
    341260
     
    344263    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
    345264    my $id   = shift;
    346     BarnOwl::start_edit_win("Reply to \@" . $user, sub { twitter_atreply($user, $id, shift) });
     265    my $account = shift;
     266    BarnOwl::start_edit_win("Reply to \@" . $user . (defined $account ? " on $account" : ""), sub { twitter_atreply($account, $user, $id, shift) });
    347267}
    348268
Note: See TracChangeset for help on using the changeset viewer.