source: lib/BarnOwl/Module/Twitter.pm @ a3874bce

release-1.10release-1.7release-1.8release-1.9
Last change on this file since a3874bce was e010ee0, checked in by Nelson Elhage <nelhage@mit.edu>, 15 years ago
Perform more sanity checking on config before creating handles.
  • Property mode set to 100644
File size: 9.2 KB
RevLine 
[e54f2fa]1use warnings;
2use strict;
3
4=head1 NAME
5
6BarnOwl::Module::Twitter
7
8=head1 DESCRIPTION
9
10Post outgoing zephyrs from -c $USER -i status -O TWITTER to Twitter
11
12=cut
13
14package BarnOwl::Module::Twitter;
15
[159aaad]16our $VERSION = 0.2;
[af3415c]17
[e54f2fa]18use Net::Twitter;
19use JSON;
[efcd223]20use List::Util qw(first);
[e54f2fa]21
22use BarnOwl;
23use BarnOwl::Hooks;
[159aaad]24use BarnOwl::Module::Twitter::Handle;
[e54f2fa]25
[d748296]26our @twitter_handles = ();
27our $default_handle = undef;
[9bedca0]28my $user     = BarnOwl::zephyr_getsender();
29my ($class)  = ($user =~ /(^[^@]+)/);
30my $instance = "status";
31my $opcode   = "twitter";
[d658c29]32my $use_reply_to = 0;
[159aaad]33my $next_service_to_poll = 0;
[188b745]34
[d689fc7]35my $desc = <<'END_DESC';
[d1bb4f3]36BarnOwl::Module::Twitter will watch for authentic zephyrs to
37-c $twitter:class -i $twitter:instance -O $twitter:opcode
38from your sender and mirror them to Twitter.
39
40A value of '*' in any of these fields acts a wildcard, accepting
41messages with any value of that field.
42END_DESC
[d689fc7]43BarnOwl::new_variable_string(
44    'twitter:class',
45    {
46        default     => $class,
47        summary     => 'Class to watch for Twitter messages',
48        description => $desc
49    }
50);
51BarnOwl::new_variable_string(
52    'twitter:instance',
53    {
54        default => $instance,
55        summary => 'Instance on twitter:class to watch for Twitter messages.',
56        description => $desc
57    }
58);
59BarnOwl::new_variable_string(
60    'twitter:opcode',
61    {
62        default => $opcode,
63        summary => 'Opcode for zephyrs that will be sent as twitter updates',
64        description => $desc
65    }
66);
[e54f2fa]67
[927c186]68BarnOwl::new_variable_bool(
69    'twitter:poll',
70    {
71        default => 1,
72        summary => 'Poll Twitter for incoming messages',
73        description => "If set, will poll Twitter every minute for normal updates,\n"
74        . 'and every two minutes for direct message'
75     }
76 );
77
[159aaad]78sub fail {
79    my $msg = shift;
80    undef @twitter_handles;
81    BarnOwl::admin_message('Twitter Error', $msg);
82    die("Twitter Error: $msg\n");
83}
84
[d775050]85my $conffile = BarnOwl::get_config_dir() . "/twitter";
86open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
[159aaad]87my $raw_cfg = do {local $/; <$fh>};
[e54f2fa]88close($fh);
89eval {
[159aaad]90    $raw_cfg = from_json($raw_cfg);
[e54f2fa]91};
[9bedca0]92if($@) {
[159aaad]93    fail("Unable to parse $conffile: $@");
[e54f2fa]94}
95
[159aaad]96$raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
97
[e010ee0]98# Perform some sanity checking on the configuration.
99{
100    my %nicks;
101    my $default = 0;
102
103    for my $cfg (@$raw_cfg) {
104        if(! exists $cfg->{user}) {
105            fail("Account has no username set.");
106        }
107        my $user = $cfg->{user};
108        if(! exists $cfg->{password}) {
109            fail("Account $user has no username set.");
110        }
111        if(@$raw_cfg > 1&&
112           !exists($cfg->{account_nickname}) ) {
113            fail("Account $user has no account_nickname set.");
114        }
115        if($cfg->{account_nickname}) {
116            if($nicks{$cfg->{account_nickname}}++) {
117                fail("Nickname " . $cfg->{account_nickname} . " specified more than once.");
118            }
119        }
120        if($cfg->{default} || $cfg->{default_sender}) {
121            if($default++) {
122                fail("Multiple accounts marked as 'default'.");
123            }
124        }
125    }
126}
127
[159aaad]128for my $cfg (@$raw_cfg) {
129    my $twitter_args = { username   => $cfg->{user} || $user,
130                        password   => $cfg->{password},
131                        source     => 'barnowl', 
132                    };
133    if (defined $cfg->{service}) {
134        my $service = $cfg->{service};
135        $twitter_args->{apiurl} = $service;
136        my $apihost = $service;
137        $apihost =~ s/^\s*http:\/\///;
138        $apihost =~ s/\/.*$//;
139        $apihost .= ':80' unless $apihost =~ /:\d+$/;
140        $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
141        my $apirealm = "Laconica API";
142        $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
143    } else {
144        $cfg->{service} = 'http://twitter.com';
145    }
[b56f2c3]146
[39dd366]147    my $twitter_handle;
[d748296]148    eval {
[39dd366]149         $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
[d748296]150    };
[39dd366]151    if ($@) {
152        BarnOwl::error($@);
153        next;
154    }
155    push @twitter_handles, $twitter_handle;
[efcd223]156}
157
[385dd69]158$default_handle = first {$_->{cfg}->{default}} @twitter_handles;
[efcd223]159if (!$default_handle && @twitter_handles) {
160    $default_handle = $twitter_handles[0];
[b56f2c3]161}
162
[d1bb4f3]163sub match {
164    my $val = shift;
165    my $pat = shift;
166    return $pat eq "*" || ($val eq $pat);
167}
168
[e54f2fa]169sub handle_message {
170    my $m = shift;
171    ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
172    if($m->sender eq $user
[d1bb4f3]173       && match($m->class, $class)
174       && match($m->instance, $instance)
175       && match($m->opcode, $opcode)
[e54f2fa]176       && $m->auth eq 'YES') {
[159aaad]177        for my $handle (@twitter_handles) {
[efcd223]178            $handle->twitter($m->body) if $handle->{cfg}->{publish_tweets};
[159aaad]179        }
[e54f2fa]180    }
181}
182
[8618438]183sub poll_messages {
[d748296]184    return unless @twitter_handles;
185
[159aaad]186    my $handle = $twitter_handles[$next_service_to_poll];
187    $next_service_to_poll = ($next_service_to_poll + 1) % scalar(@twitter_handles);
188   
[efcd223]189    $handle->poll_twitter() if $handle->{cfg}->{poll_for_tweets};
190    $handle->poll_direct() if $handle->{cfg}->{poll_for_dms};
191}
192
193sub find_account {
194    my $name = shift;
195    my $handle = first {$_->{cfg}->{account_nickname} eq $name} @twitter_handles;
196    if ($handle) {
197        return $handle;
198    } else {
199        die("No such Twitter account: $name\n");
200    }
[927c186]201}
202
[8462b38]203sub find_account_default {
204    my $name = shift;
205    if(defined($name)) {
206        return find_account($name);
207    } else {
208        return $default_handle;
209    }
210}
211
[159aaad]212sub twitter {
213    my $account = shift;
214
215    my $sent = 0;
216    if (defined $account) {
[efcd223]217        my $handle = find_account($account);
218        $handle->twitter(@_);
[159aaad]219    } 
220    else {
221        # broadcast
222        for my $handle (@twitter_handles) {
[efcd223]223            $handle->twitter(@_) if $handle->{cfg}->{publish_tweets};
[159aaad]224        }
[8618438]225    }
226}
227
[4cf4067]228BarnOwl::new_command(twitter => \&cmd_twitter, {
229    summary     => 'Update Twitter from BarnOwl',
[159aaad]230    usage       => 'twitter [ACCOUNT] [MESSAGE]',
231    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
232    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
[4cf4067]233    . "\nOtherwise, prompt for a status message to use."
234   });
235
[927c186]236BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
237    summary     => 'Send a Twitter direct message',
[159aaad]238    usage       => 'twitter-direct USER [ACCOUNT]',
239    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
240    . "\nor first service if no default is provided)"
[927c186]241   });
242
[6babb75]243BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
244    {
245    summary     => 'Send a Twitter @ message',
[159aaad]246    usage       => 'twitter-atreply USER [ACCOUNT]',
247    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,' 
[513da71]248    . "\nor first service if no default is provided)"
249    }
250);
251
252BarnOwl::new_command( 'twitter-follow' => sub { cmd_twitter_follow(@_); },
253    {
254    summary     => 'Follow a user on Twitter',
255    usage       => 'twitter-follow USER [ACCOUNT]',
256    description => 'Follow USER on Twitter ACCOUNT (defaults to default_sender, or first service'
257    . "\nif no default is provided)"
[6babb75]258    }
259);
260
[513da71]261BarnOwl::new_command( 'twitter-unfollow' => sub { cmd_twitter_unfollow(@_); },
262    {
263    summary     => 'Stop following a user on Twitter',
264    usage       => 'twitter-unfollow USER [ACCOUNT]',
265    description => 'Stop following USER on Twitter ACCOUNT (defaults to default_sender, or first'
266    . "\nservice if no default is provided)"
267    }
268);
[6babb75]269
[4cf4067]270sub cmd_twitter {
271    my $cmd = shift;
[159aaad]272    my $account = shift;
273    if (defined $account) {
274        if(@_) {
275            my $status = join(" ", @_);
276            twitter($account, $status);
277            return;
278        }
[4cf4067]279    }
[159aaad]280    BarnOwl::start_edit_win('What are you doing?' . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
[4cf4067]281}
282
[927c186]283sub cmd_twitter_direct {
284    my $cmd = shift;
285    my $user = shift;
286    die("Usage: $cmd USER\n") unless $user;
[8462b38]287    my $account = find_account_default(shift);
288    BarnOwl::start_edit_win("$cmd $user " . ($account->nickname||""),
289                            sub { $account->twitter_direct($user, shift) });
[927c186]290}
291
[6babb75]292sub cmd_twitter_atreply {
293    my $cmd  = shift;
[acdd52e]294    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
295    my $id   = shift;
[8462b38]296    my $account = find_account_default(shift);
297
298    BarnOwl::start_edit_win("Reply to \@" . $user . ($account->nickname ? (" on " . $account->nickname) : ""),
299                            sub { $account->twitter_atreply($user, $id, shift) });
[6babb75]300}
301
[513da71]302sub cmd_twitter_follow {
303    my $cmd = shift;
304    my $user = shift;
305    die("Usage: $cmd USER\n") unless $user;
306    my $account = shift;
[8462b38]307    find_account_default($account)->twitter_follow($user);
[513da71]308}
309
310sub cmd_twitter_unfollow {
311    my $cmd = shift;
312    my $user = shift;
313    die("Usage: $cmd USER\n") unless $user;
314    my $account = shift;
[8462b38]315    find_account_default($account)->twitter_unfollow($user);
[513da71]316}
317
[72b61dd]318eval {
319    $BarnOwl::Hooks::receiveMessage->add("BarnOwl::Module::Twitter::handle_message");
[8618438]320    $BarnOwl::Hooks::mainLoop->add("BarnOwl::Module::Twitter::poll_messages");
[72b61dd]321};
322if($@) {
323    $BarnOwl::Hooks::receiveMessage->add(\&handle_message);
[8618438]324    $BarnOwl::Hooks::mainLoop->add(\&poll_messages);
[72b61dd]325}
[e54f2fa]326
[f93b81b]327BarnOwl::filter(qw{twitter type ^twitter$});
[8618438]328
[e54f2fa]3291;
Note: See TracBrowser for help on using the repository browser.