source: lib/BarnOwl/Module/Twitter.pm @ 6249a76d

release-1.10release-1.7release-1.8release-1.9
Last change on this file since 6249a76d was 6249a76d, checked in by Nelson Elhage <nelhage@mit.edu>, 14 years ago
Restore lost handle_message hook. This was accidentally deleted in commit 36546fa4ddc4de9c607b0f7bb76f8041360cf9f1 when we removed the mainloop hook.
  • Property mode set to 100644
File size: 10.8 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;
[6e8eb1c]25use BarnOwl::Module::Twitter::Completion;
[e54f2fa]26
[d748296]27our @twitter_handles = ();
28our $default_handle = undef;
[188b745]29
[f3e44eb]30our $prefix;
31
[d689fc7]32my $desc = <<'END_DESC';
[d1bb4f3]33BarnOwl::Module::Twitter will watch for authentic zephyrs to
34-c $twitter:class -i $twitter:instance -O $twitter:opcode
35from your sender and mirror them to Twitter.
36
37A value of '*' in any of these fields acts a wildcard, accepting
38messages with any value of that field.
39END_DESC
[d689fc7]40BarnOwl::new_variable_string(
41    'twitter:class',
42    {
[40c9dac]43        default     => $ENV{USER},
[d689fc7]44        summary     => 'Class to watch for Twitter messages',
45        description => $desc
46    }
47);
48BarnOwl::new_variable_string(
49    'twitter:instance',
50    {
[40c9dac]51        default => 'status',
[d689fc7]52        summary => 'Instance on twitter:class to watch for Twitter messages.',
53        description => $desc
54    }
55);
56BarnOwl::new_variable_string(
57    'twitter:opcode',
58    {
[40c9dac]59        default => 'twitter',
[d689fc7]60        summary => 'Opcode for zephyrs that will be sent as twitter updates',
61        description => $desc
62    }
63);
[e54f2fa]64
[927c186]65BarnOwl::new_variable_bool(
66    'twitter:poll',
67    {
68        default => 1,
69        summary => 'Poll Twitter for incoming messages',
70        description => "If set, will poll Twitter every minute for normal updates,\n"
71        . 'and every two minutes for direct message'
72     }
73 );
74
[159aaad]75sub fail {
76    my $msg = shift;
77    undef @twitter_handles;
78    BarnOwl::admin_message('Twitter Error', $msg);
79    die("Twitter Error: $msg\n");
80}
81
[d775050]82my $conffile = BarnOwl::get_config_dir() . "/twitter";
83open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
[159aaad]84my $raw_cfg = do {local $/; <$fh>};
[e54f2fa]85close($fh);
86eval {
[159aaad]87    $raw_cfg = from_json($raw_cfg);
[e54f2fa]88};
[9bedca0]89if($@) {
[159aaad]90    fail("Unable to parse $conffile: $@");
[e54f2fa]91}
92
[159aaad]93$raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
94
[e010ee0]95# Perform some sanity checking on the configuration.
96{
97    my %nicks;
98    my $default = 0;
99
100    for my $cfg (@$raw_cfg) {
101        if(! exists $cfg->{user}) {
102            fail("Account has no username set.");
103        }
104        my $user = $cfg->{user};
105        if(! exists $cfg->{password}) {
[2fa9f803]106            fail("Account $user has no password set.");
[e010ee0]107        }
108        if(@$raw_cfg > 1&&
109           !exists($cfg->{account_nickname}) ) {
110            fail("Account $user has no account_nickname set.");
111        }
112        if($cfg->{account_nickname}) {
113            if($nicks{$cfg->{account_nickname}}++) {
114                fail("Nickname " . $cfg->{account_nickname} . " specified more than once.");
115            }
116        }
117        if($cfg->{default} || $cfg->{default_sender}) {
118            if($default++) {
119                fail("Multiple accounts marked as 'default'.");
120            }
121        }
122    }
123}
124
[f0de278]125# If there is only a single account, make publish_tweets default to
126# true.
127if (scalar @$raw_cfg == 1 && !exists($raw_cfg->[0]{publish_tweets})) {
128    $raw_cfg->[0]{publish_tweets} = 1;
129}
130
[159aaad]131for my $cfg (@$raw_cfg) {
[9eb9479]132    my $twitter_args = { username   => $cfg->{user},
[159aaad]133                        password   => $cfg->{password},
134                        source     => 'barnowl', 
135                    };
136    if (defined $cfg->{service}) {
137        my $service = $cfg->{service};
138        $twitter_args->{apiurl} = $service;
139        my $apihost = $service;
140        $apihost =~ s/^\s*http:\/\///;
141        $apihost =~ s/\/.*$//;
142        $apihost .= ':80' unless $apihost =~ /:\d+$/;
143        $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
144        my $apirealm = "Laconica API";
145        $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
146    } else {
147        $cfg->{service} = 'http://twitter.com';
148    }
[b56f2c3]149
[39dd366]150    my $twitter_handle;
[d748296]151    eval {
[39dd366]152         $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
[d748296]153    };
[39dd366]154    if ($@) {
155        BarnOwl::error($@);
156        next;
157    }
158    push @twitter_handles, $twitter_handle;
[efcd223]159}
160
[385dd69]161$default_handle = first {$_->{cfg}->{default}} @twitter_handles;
[efcd223]162if (!$default_handle && @twitter_handles) {
163    $default_handle = $twitter_handles[0];
[b56f2c3]164}
165
[d1bb4f3]166sub match {
167    my $val = shift;
168    my $pat = shift;
169    return $pat eq "*" || ($val eq $pat);
170}
171
[e54f2fa]172sub handle_message {
173    my $m = shift;
[40c9dac]174    my ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
[176434d]175    if($m->type eq 'zephyr'
176       && $m->sender eq BarnOwl::zephyr_getsender()
[d1bb4f3]177       && match($m->class, $class)
178       && match($m->instance, $instance)
179       && match($m->opcode, $opcode)
[e54f2fa]180       && $m->auth eq 'YES') {
[159aaad]181        for my $handle (@twitter_handles) {
[efcd223]182            $handle->twitter($m->body) if $handle->{cfg}->{publish_tweets};
[159aaad]183        }
[e54f2fa]184    }
185}
186
[4ae10de]187sub poll_messages {
188    # If we are reloaded into a barnowl with the old
189    # BarnOwl::Module::Twitter loaded, it still has a main loop hook
190    # that will call this function every second. If we just delete it,
191    # it will get the old version, which will call poll on each of our
192    # handles every second. However, they no longer include the time
193    # check, and so we will poll a handle every second until
194    # ratelimited.
195
196    # So we include an empty function here.
197}
198
[efcd223]199sub find_account {
200    my $name = shift;
201    my $handle = first {$_->{cfg}->{account_nickname} eq $name} @twitter_handles;
202    if ($handle) {
203        return $handle;
204    } else {
205        die("No such Twitter account: $name\n");
206    }
[927c186]207}
208
[8462b38]209sub find_account_default {
210    my $name = shift;
211    if(defined($name)) {
212        return find_account($name);
213    } else {
214        return $default_handle;
215    }
216}
217
[159aaad]218sub twitter {
219    my $account = shift;
220
221    my $sent = 0;
222    if (defined $account) {
[efcd223]223        my $handle = find_account($account);
224        $handle->twitter(@_);
[159aaad]225    } 
226    else {
227        # broadcast
228        for my $handle (@twitter_handles) {
[efcd223]229            $handle->twitter(@_) if $handle->{cfg}->{publish_tweets};
[159aaad]230        }
[8618438]231    }
232}
233
[4cf4067]234BarnOwl::new_command(twitter => \&cmd_twitter, {
235    summary     => 'Update Twitter from BarnOwl',
[159aaad]236    usage       => 'twitter [ACCOUNT] [MESSAGE]',
237    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
238    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
[4cf4067]239    . "\nOtherwise, prompt for a status message to use."
240   });
241
[927c186]242BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
243    summary     => 'Send a Twitter direct message',
[159aaad]244    usage       => 'twitter-direct USER [ACCOUNT]',
245    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
246    . "\nor first service if no default is provided)"
[927c186]247   });
248
[6babb75]249BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
250    {
251    summary     => 'Send a Twitter @ message',
[159aaad]252    usage       => 'twitter-atreply USER [ACCOUNT]',
253    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,' 
[513da71]254    . "\nor first service if no default is provided)"
255    }
256);
257
[5214546]258BarnOwl::new_command( 'twitter-retweet' => sub { cmd_twitter_retweet(@_) },
259    {
260    summary     => 'Retweet the current Twitter message',
261    usage       => 'twitter-retweet [ACCOUNT]',
262    description => <<END_DESCRIPTION
263Retweet the current Twitter message using ACCOUNT (defaults to the
264account that received the tweet).
265END_DESCRIPTION
266    }
267);
268
[513da71]269BarnOwl::new_command( 'twitter-follow' => sub { cmd_twitter_follow(@_); },
270    {
271    summary     => 'Follow a user on Twitter',
272    usage       => 'twitter-follow USER [ACCOUNT]',
273    description => 'Follow USER on Twitter ACCOUNT (defaults to default_sender, or first service'
274    . "\nif no default is provided)"
[6babb75]275    }
276);
277
[513da71]278BarnOwl::new_command( 'twitter-unfollow' => sub { cmd_twitter_unfollow(@_); },
279    {
280    summary     => 'Stop following a user on Twitter',
281    usage       => 'twitter-unfollow USER [ACCOUNT]',
282    description => 'Stop following USER on Twitter ACCOUNT (defaults to default_sender, or first'
283    . "\nservice if no default is provided)"
284    }
285);
[6babb75]286
[f3e44eb]287BarnOwl::new_command('twitter-count-chars' => \&cmd_count_chars, {
288    summary     => 'Count the number of characters in the edit window',
289    usage       => 'twitter-count-chars',
290    description => <<END_DESCRIPTION
291Displays the number of characters entered in the edit window so far. Correctly
292takes into account any \@user prefix that will be added by the Twitter plugin.
293END_DESCRIPTION
294   });
295
296
[4cf4067]297sub cmd_twitter {
298    my $cmd = shift;
[159aaad]299    my $account = shift;
300    if (defined $account) {
301        if(@_) {
302            my $status = join(" ", @_);
303            twitter($account, $status);
304            return;
305        }
[4cf4067]306    }
[f3e44eb]307    undef $prefix;
[22fce654]308    BarnOwl::start_edit_win("What's happening?" . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
[4cf4067]309}
310
[927c186]311sub cmd_twitter_direct {
312    my $cmd = shift;
313    my $user = shift;
314    die("Usage: $cmd USER\n") unless $user;
[8462b38]315    my $account = find_account_default(shift);
[f3e44eb]316    undef $prefix;
[8462b38]317    BarnOwl::start_edit_win("$cmd $user " . ($account->nickname||""),
318                            sub { $account->twitter_direct($user, shift) });
[927c186]319}
320
[6babb75]321sub cmd_twitter_atreply {
322    my $cmd  = shift;
[acdd52e]323    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
324    my $id   = shift;
[8462b38]325    my $account = find_account_default(shift);
326
[8f19ba3]327    $prefix = "\@$user ";
[8462b38]328    BarnOwl::start_edit_win("Reply to \@" . $user . ($account->nickname ? (" on " . $account->nickname) : ""),
329                            sub { $account->twitter_atreply($user, $id, shift) });
[6babb75]330}
331
[5214546]332sub cmd_twitter_retweet {
333    my $cmd = shift;
334    my $account = shift;
335    my $m = BarnOwl::getcurmsg();
336    if(!$m || $m->type ne 'Twitter') {
337        die("$cmd must be used with a Twitter message selected.\n");
338    }
339
340    $account = $m->account unless defined($account);
341    find_account($account)->twitter_retweet($m);
[986c9b1]342    return;
[5214546]343}
344
[513da71]345sub cmd_twitter_follow {
346    my $cmd = shift;
347    my $user = shift;
348    die("Usage: $cmd USER\n") unless $user;
349    my $account = shift;
[8462b38]350    find_account_default($account)->twitter_follow($user);
[513da71]351}
352
353sub cmd_twitter_unfollow {
354    my $cmd = shift;
355    my $user = shift;
356    die("Usage: $cmd USER\n") unless $user;
357    my $account = shift;
[8462b38]358    find_account_default($account)->twitter_unfollow($user);
[513da71]359}
360
[f3e44eb]361use BarnOwl::Editwin qw(:all);
362sub cmd_count_chars {
363    my $cmd = shift;
364    my $text = save_excursion {
365        move_to_buffer_start();
366        set_mark();
367        move_to_buffer_end();
368        get_region();
369    };
370    my $len = length($text);
371    $len += length($prefix) if $prefix;
372    BarnOwl::message($len);
373    return $len;
374}
375
[6249a76d]376eval {
377    $BarnOwl::Hooks::receiveMessage->add("BarnOwl::Module::Twitter::handle_message");
378};
379if($@) {
380    $BarnOwl::Hooks::receiveMessage->add(\&handle_message);
381}
382
383
384
[f93b81b]385BarnOwl::filter(qw{twitter type ^twitter$});
[8618438]386
[e54f2fa]3871;
Note: See TracBrowser for help on using the repository browser.