source: lib/BarnOwl/Module/Twitter.pm @ 159aaad

release-1.10release-1.7release-1.8release-1.9
Last change on this file since 159aaad was 159aaad, checked in by Nelson Elhage <nelhage@mit.edu>, 15 years ago
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.
  • Property mode set to 100644
File size: 8.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;
20
21use BarnOwl;
22use BarnOwl::Hooks;
[159aaad]23use BarnOwl::Module::Twitter::Handle;
[e54f2fa]24
[159aaad]25our @twitter_handles;
26our $default_handle;
[9bedca0]27my $user     = BarnOwl::zephyr_getsender();
28my ($class)  = ($user =~ /(^[^@]+)/);
29my $instance = "status";
30my $opcode   = "twitter";
[d658c29]31my $use_reply_to = 0;
[159aaad]32my $next_service_to_poll = 0;
[188b745]33
[d689fc7]34my $desc = <<'END_DESC';
[d1bb4f3]35BarnOwl::Module::Twitter will watch for authentic zephyrs to
36-c $twitter:class -i $twitter:instance -O $twitter:opcode
37from your sender and mirror them to Twitter.
38
39A value of '*' in any of these fields acts a wildcard, accepting
40messages with any value of that field.
41END_DESC
[d689fc7]42BarnOwl::new_variable_string(
43    'twitter:class',
44    {
45        default     => $class,
46        summary     => 'Class to watch for Twitter messages',
47        description => $desc
48    }
49);
50BarnOwl::new_variable_string(
51    'twitter:instance',
52    {
53        default => $instance,
54        summary => 'Instance on twitter:class to watch for Twitter messages.',
55        description => $desc
56    }
57);
58BarnOwl::new_variable_string(
59    'twitter:opcode',
60    {
61        default => $opcode,
62        summary => 'Opcode for zephyrs that will be sent as twitter updates',
63        description => $desc
64    }
65);
[e54f2fa]66
[927c186]67BarnOwl::new_variable_bool(
68    'twitter:poll',
69    {
70        default => 1,
71        summary => 'Poll Twitter for incoming messages',
72        description => "If set, will poll Twitter every minute for normal updates,\n"
73        . 'and every two minutes for direct message'
74     }
75 );
76
[159aaad]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
[d775050]84my $conffile = BarnOwl::get_config_dir() . "/twitter";
85open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
[159aaad]86my $raw_cfg = do {local $/; <$fh>};
[e54f2fa]87close($fh);
88eval {
[159aaad]89    $raw_cfg = from_json($raw_cfg);
[e54f2fa]90};
[9bedca0]91if($@) {
[159aaad]92    fail("Unable to parse $conffile: $@");
[e54f2fa]93}
94
[159aaad]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    }
[b56f2c3]115
[159aaad]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});
[b56f2c3]119}
120
[d1bb4f3]121sub match {
122    my $val = shift;
123    my $pat = shift;
124    return $pat eq "*" || ($val eq $pat);
125}
126
[e54f2fa]127sub handle_message {
128    my $m = shift;
129    ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
130    if($m->sender eq $user
[d1bb4f3]131       && match($m->class, $class)
132       && match($m->instance, $instance)
133       && match($m->opcode, $opcode)
[e54f2fa]134       && $m->auth eq 'YES') {
[159aaad]135        for my $handle (@twitter_handles) {
136            $handle->twitter($m->body);
137        }
[e54f2fa]138    }
139}
140
[8618438]141sub poll_messages {
[159aaad]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});
[927c186]147}
148
[159aaad]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;
[8618438]159            }
160        }
[159aaad]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        }
[8618438]168    }
169}
170
[159aaad]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;
[927c186]181            }
182        }
[159aaad]183        BarnOwl::message("No Twitter account named " . $account) unless $sent == 1
[927c186]184    }
[159aaad]185    elsif (defined $default_handle) {
186        $default_handle->twitter_direct(@_);
[9bedca0]187    }
[159aaad]188    else {
189        $twitter_handles[0]->twitter_direct(@_);
[927c186]190    }
191}
192
[6babb75]193sub twitter_atreply {
[159aaad]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(@_);
[acdd52e]212    }
[6babb75]213}
214
[4cf4067]215BarnOwl::new_command(twitter => \&cmd_twitter, {
216    summary     => 'Update Twitter from BarnOwl',
[159aaad]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."
[4cf4067]220    . "\nOtherwise, prompt for a status message to use."
221   });
222
[927c186]223BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
224    summary     => 'Send a Twitter direct message',
[159aaad]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)"
[927c186]228   });
229
[6babb75]230BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
231    {
232    summary     => 'Send a Twitter @ message',
[159aaad]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)"
[6babb75]236    }
237);
238
239
[4cf4067]240sub cmd_twitter {
241    my $cmd = shift;
[159aaad]242    my $account = shift;
243    if (defined $account) {
244        if(@_) {
245            my $status = join(" ", @_);
246            twitter($account, $status);
247            return;
248        }
[4cf4067]249    }
[159aaad]250    BarnOwl::start_edit_win('What are you doing?' . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
[4cf4067]251}
252
[927c186]253sub cmd_twitter_direct {
254    my $cmd = shift;
255    my $user = shift;
256    die("Usage: $cmd USER\n") unless $user;
[159aaad]257    my $account = shift;
258    BarnOwl::start_edit_win("$cmd $user" . (defined $account ? " $account" : ""), sub{twitter_direct($account, $user, shift)});
[927c186]259}
260
[6babb75]261sub cmd_twitter_atreply {
262    my $cmd  = shift;
[acdd52e]263    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
264    my $id   = shift;
[159aaad]265    my $account = shift;
266    BarnOwl::start_edit_win("Reply to \@" . $user . (defined $account ? " on $account" : ""), sub { twitter_atreply($account, $user, $id, shift) });
[6babb75]267}
268
[72b61dd]269eval {
270    $BarnOwl::Hooks::receiveMessage->add("BarnOwl::Module::Twitter::handle_message");
[8618438]271    $BarnOwl::Hooks::mainLoop->add("BarnOwl::Module::Twitter::poll_messages");
[72b61dd]272};
273if($@) {
274    $BarnOwl::Hooks::receiveMessage->add(\&handle_message);
[8618438]275    $BarnOwl::Hooks::mainLoop->add(\&poll_messages);
[72b61dd]276}
[e54f2fa]277
[f93b81b]278BarnOwl::filter(qw{twitter type ^twitter$});
[8618438]279
[e54f2fa]2801;
Note: See TracBrowser for help on using the repository browser.