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

release-1.10release-1.7release-1.8release-1.9
Last change on this file since c9bdb46 was 40c9dac, checked in by Nelson Elhage <nelhage@mit.edu>, 14 years ago
Kill some defunct variables.
  • Property mode set to 100644
File size: 9.8 KB
Line 
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
16our $VERSION = 0.2;
17
18use Net::Twitter;
19use JSON;
20use List::Util qw(first);
21
22use BarnOwl;
23use BarnOwl::Hooks;
24use BarnOwl::Module::Twitter::Handle;
25
26our @twitter_handles = ();
27our $default_handle = undef;
28
29my $desc = <<'END_DESC';
30BarnOwl::Module::Twitter will watch for authentic zephyrs to
31-c $twitter:class -i $twitter:instance -O $twitter:opcode
32from your sender and mirror them to Twitter.
33
34A value of '*' in any of these fields acts a wildcard, accepting
35messages with any value of that field.
36END_DESC
37BarnOwl::new_variable_string(
38    'twitter:class',
39    {
40        default     => $ENV{USER},
41        summary     => 'Class to watch for Twitter messages',
42        description => $desc
43    }
44);
45BarnOwl::new_variable_string(
46    'twitter:instance',
47    {
48        default => 'status',
49        summary => 'Instance on twitter:class to watch for Twitter messages.',
50        description => $desc
51    }
52);
53BarnOwl::new_variable_string(
54    'twitter:opcode',
55    {
56        default => 'twitter',
57        summary => 'Opcode for zephyrs that will be sent as twitter updates',
58        description => $desc
59    }
60);
61
62BarnOwl::new_variable_bool(
63    'twitter:poll',
64    {
65        default => 1,
66        summary => 'Poll Twitter for incoming messages',
67        description => "If set, will poll Twitter every minute for normal updates,\n"
68        . 'and every two minutes for direct message'
69     }
70 );
71
72sub fail {
73    my $msg = shift;
74    undef @twitter_handles;
75    BarnOwl::admin_message('Twitter Error', $msg);
76    die("Twitter Error: $msg\n");
77}
78
79my $conffile = BarnOwl::get_config_dir() . "/twitter";
80open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
81my $raw_cfg = do {local $/; <$fh>};
82close($fh);
83eval {
84    $raw_cfg = from_json($raw_cfg);
85};
86if($@) {
87    fail("Unable to parse $conffile: $@");
88}
89
90$raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
91
92# Perform some sanity checking on the configuration.
93{
94    my %nicks;
95    my $default = 0;
96
97    for my $cfg (@$raw_cfg) {
98        if(! exists $cfg->{user}) {
99            fail("Account has no username set.");
100        }
101        my $user = $cfg->{user};
102        if(! exists $cfg->{password}) {
103            fail("Account $user has no password set.");
104        }
105        if(@$raw_cfg > 1&&
106           !exists($cfg->{account_nickname}) ) {
107            fail("Account $user has no account_nickname set.");
108        }
109        if($cfg->{account_nickname}) {
110            if($nicks{$cfg->{account_nickname}}++) {
111                fail("Nickname " . $cfg->{account_nickname} . " specified more than once.");
112            }
113        }
114        if($cfg->{default} || $cfg->{default_sender}) {
115            if($default++) {
116                fail("Multiple accounts marked as 'default'.");
117            }
118        }
119    }
120}
121
122# If there is only a single account, make publish_tweets default to
123# true.
124if (scalar @$raw_cfg == 1 && !exists($raw_cfg->[0]{publish_tweets})) {
125    $raw_cfg->[0]{publish_tweets} = 1;
126}
127
128for my $cfg (@$raw_cfg) {
129    my $twitter_args = { username   => $cfg->{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    }
146
147    my $twitter_handle;
148    eval {
149         $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
150    };
151    if ($@) {
152        BarnOwl::error($@);
153        next;
154    }
155    push @twitter_handles, $twitter_handle;
156}
157
158$default_handle = first {$_->{cfg}->{default}} @twitter_handles;
159if (!$default_handle && @twitter_handles) {
160    $default_handle = $twitter_handles[0];
161}
162
163sub match {
164    my $val = shift;
165    my $pat = shift;
166    return $pat eq "*" || ($val eq $pat);
167}
168
169sub handle_message {
170    my $m = shift;
171    my ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
172    if($m->type eq 'zephyr'
173       && $m->sender eq BarnOwl::zephyr_getsender()
174       && match($m->class, $class)
175       && match($m->instance, $instance)
176       && match($m->opcode, $opcode)
177       && $m->auth eq 'YES') {
178        for my $handle (@twitter_handles) {
179            $handle->twitter($m->body) if $handle->{cfg}->{publish_tweets};
180        }
181    }
182}
183
184sub poll_messages {
185    # If we are reloaded into a barnowl with the old
186    # BarnOwl::Module::Twitter loaded, it still has a main loop hook
187    # that will call this function every second. If we just delete it,
188    # it will get the old version, which will call poll on each of our
189    # handles every second. However, they no longer include the time
190    # check, and so we will poll a handle every second until
191    # ratelimited.
192
193    # So we include an empty function here.
194}
195
196sub find_account {
197    my $name = shift;
198    my $handle = first {$_->{cfg}->{account_nickname} eq $name} @twitter_handles;
199    if ($handle) {
200        return $handle;
201    } else {
202        die("No such Twitter account: $name\n");
203    }
204}
205
206sub find_account_default {
207    my $name = shift;
208    if(defined($name)) {
209        return find_account($name);
210    } else {
211        return $default_handle;
212    }
213}
214
215sub twitter {
216    my $account = shift;
217
218    my $sent = 0;
219    if (defined $account) {
220        my $handle = find_account($account);
221        $handle->twitter(@_);
222    } 
223    else {
224        # broadcast
225        for my $handle (@twitter_handles) {
226            $handle->twitter(@_) if $handle->{cfg}->{publish_tweets};
227        }
228    }
229}
230
231BarnOwl::new_command(twitter => \&cmd_twitter, {
232    summary     => 'Update Twitter from BarnOwl',
233    usage       => 'twitter [ACCOUNT] [MESSAGE]',
234    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
235    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
236    . "\nOtherwise, prompt for a status message to use."
237   });
238
239BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
240    summary     => 'Send a Twitter direct message',
241    usage       => 'twitter-direct USER [ACCOUNT]',
242    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
243    . "\nor first service if no default is provided)"
244   });
245
246BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
247    {
248    summary     => 'Send a Twitter @ message',
249    usage       => 'twitter-atreply USER [ACCOUNT]',
250    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,' 
251    . "\nor first service if no default is provided)"
252    }
253);
254
255BarnOwl::new_command( 'twitter-retweet' => sub { cmd_twitter_retweet(@_) },
256    {
257    summary     => 'Retweet the current Twitter message',
258    usage       => 'twitter-retweet [ACCOUNT]',
259    description => <<END_DESCRIPTION
260Retweet the current Twitter message using ACCOUNT (defaults to the
261account that received the tweet).
262END_DESCRIPTION
263    }
264);
265
266BarnOwl::new_command( 'twitter-follow' => sub { cmd_twitter_follow(@_); },
267    {
268    summary     => 'Follow a user on Twitter',
269    usage       => 'twitter-follow USER [ACCOUNT]',
270    description => 'Follow USER on Twitter ACCOUNT (defaults to default_sender, or first service'
271    . "\nif no default is provided)"
272    }
273);
274
275BarnOwl::new_command( 'twitter-unfollow' => sub { cmd_twitter_unfollow(@_); },
276    {
277    summary     => 'Stop following a user on Twitter',
278    usage       => 'twitter-unfollow USER [ACCOUNT]',
279    description => 'Stop following USER on Twitter ACCOUNT (defaults to default_sender, or first'
280    . "\nservice if no default is provided)"
281    }
282);
283
284sub cmd_twitter {
285    my $cmd = shift;
286    my $account = shift;
287    if (defined $account) {
288        if(@_) {
289            my $status = join(" ", @_);
290            twitter($account, $status);
291            return;
292        }
293    }
294    BarnOwl::start_edit_win("What's happening?" . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
295}
296
297sub cmd_twitter_direct {
298    my $cmd = shift;
299    my $user = shift;
300    die("Usage: $cmd USER\n") unless $user;
301    my $account = find_account_default(shift);
302    BarnOwl::start_edit_win("$cmd $user " . ($account->nickname||""),
303                            sub { $account->twitter_direct($user, shift) });
304}
305
306sub cmd_twitter_atreply {
307    my $cmd  = shift;
308    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
309    my $id   = shift;
310    my $account = find_account_default(shift);
311
312    BarnOwl::start_edit_win("Reply to \@" . $user . ($account->nickname ? (" on " . $account->nickname) : ""),
313                            sub { $account->twitter_atreply($user, $id, shift) });
314}
315
316sub cmd_twitter_retweet {
317    my $cmd = shift;
318    my $account = shift;
319    my $m = BarnOwl::getcurmsg();
320    if(!$m || $m->type ne 'Twitter') {
321        die("$cmd must be used with a Twitter message selected.\n");
322    }
323
324    $account = $m->account unless defined($account);
325    find_account($account)->twitter_retweet($m);
326}
327
328sub cmd_twitter_follow {
329    my $cmd = shift;
330    my $user = shift;
331    die("Usage: $cmd USER\n") unless $user;
332    my $account = shift;
333    find_account_default($account)->twitter_follow($user);
334}
335
336sub cmd_twitter_unfollow {
337    my $cmd = shift;
338    my $user = shift;
339    die("Usage: $cmd USER\n") unless $user;
340    my $account = shift;
341    find_account_default($account)->twitter_unfollow($user);
342}
343
344BarnOwl::filter(qw{twitter type ^twitter$});
345
3461;
Note: See TracBrowser for help on using the repository browser.