source: lib/BarnOwl/Module/Twitter.pm @ 513da71

release-1.7release-1.8release-1.9
Last change on this file since 513da71 was 513da71, checked in by Kevin Riggle <kevinr@free-dissociation.com>, 12 years ago
Add :twitter-follow and :twitter-unfollow commands
  • Property mode set to 100644
File size: 9.3 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;
28my $user     = BarnOwl::zephyr_getsender();
29my ($class)  = ($user =~ /(^[^@]+)/);
30my $instance = "status";
31my $opcode   = "twitter";
32my $use_reply_to = 0;
33my $next_service_to_poll = 0;
34
35my $desc = <<'END_DESC';
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
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);
67
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
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
85my $conffile = BarnOwl::get_config_dir() . "/twitter";
86open(my $fh, "<", "$conffile") || fail("Unable to read $conffile");
87my $raw_cfg = do {local $/; <$fh>};
88close($fh);
89eval {
90    $raw_cfg = from_json($raw_cfg);
91};
92if($@) {
93    fail("Unable to parse $conffile: $@");
94}
95
96$raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
97
98for my $cfg (@$raw_cfg) {
99    my $twitter_args = { username   => $cfg->{user} || $user,
100                        password   => $cfg->{password},
101                        source     => 'barnowl', 
102                    };
103    if (defined $cfg->{service}) {
104        my $service = $cfg->{service};
105        $twitter_args->{apiurl} = $service;
106        my $apihost = $service;
107        $apihost =~ s/^\s*http:\/\///;
108        $apihost =~ s/\/.*$//;
109        $apihost .= ':80' unless $apihost =~ /:\d+$/;
110        $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
111        my $apirealm = "Laconica API";
112        $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
113    } else {
114        $cfg->{service} = 'http://twitter.com';
115    }
116
117    my $twitter_handle;
118    eval {
119         $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
120    };
121    if ($@) {
122        BarnOwl::error($@);
123        next;
124    }
125    push @twitter_handles, $twitter_handle;
126}
127
128$default_handle = first {$_->{cfg}->{default_sender}} @twitter_handles;
129if (!$default_handle && @twitter_handles) {
130    $default_handle = $twitter_handles[0];
131}
132
133sub match {
134    my $val = shift;
135    my $pat = shift;
136    return $pat eq "*" || ($val eq $pat);
137}
138
139sub handle_message {
140    my $m = shift;
141    ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
142    if($m->sender eq $user
143       && match($m->class, $class)
144       && match($m->instance, $instance)
145       && match($m->opcode, $opcode)
146       && $m->auth eq 'YES') {
147        for my $handle (@twitter_handles) {
148            $handle->twitter($m->body) if $handle->{cfg}->{publish_tweets};
149        }
150    }
151}
152
153sub poll_messages {
154    return unless @twitter_handles;
155
156    my $handle = $twitter_handles[$next_service_to_poll];
157    $next_service_to_poll = ($next_service_to_poll + 1) % scalar(@twitter_handles);
158   
159    $handle->poll_twitter() if $handle->{cfg}->{poll_for_tweets};
160    $handle->poll_direct() if $handle->{cfg}->{poll_for_dms};
161}
162
163sub find_account {
164    my $name = shift;
165    my $handle = first {$_->{cfg}->{account_nickname} eq $name} @twitter_handles;
166    if ($handle) {
167        return $handle;
168    } else {
169        die("No such Twitter account: $name\n");
170    }
171}
172
173sub twitter {
174    my $account = shift;
175
176    my $sent = 0;
177    if (defined $account) {
178        my $handle = find_account($account);
179        $handle->twitter(@_);
180    } 
181    else {
182        # broadcast
183        for my $handle (@twitter_handles) {
184            $handle->twitter(@_) if $handle->{cfg}->{publish_tweets};
185        }
186    }
187}
188
189sub twitter_direct {
190    my $account = shift;
191
192    my $sent = 0;
193    if (defined $account) {
194        my $handle = find_account($account);
195        $handle->twitter_direct(@_);
196    } elsif (defined $default_handle) {
197        $default_handle->twitter_direct(@_);
198    }
199    else {
200        $twitter_handles[0]->twitter_direct(@_);
201    }
202}
203
204sub twitter_atreply {
205    my $account = shift;
206
207    my $sent = 0;
208    if (defined $account) {
209        my $handle = find_account($account);
210        $handle->twitter_atreply(@_);
211    }
212    elsif (defined $default_handle) {
213        $default_handle->twitter_atreply(@_);
214    }
215    else {
216        $twitter_handles[0]->twitter_atreply(@_);
217    }
218}
219
220sub twitter_follow {
221    my $account = shift;
222
223    if (defined $account) {
224        my $handle = find_account($account);
225        $handle->twitter_follow(@_);
226    } elsif (defined $default_handle) {
227        $default_handle->twitter_follow(@_);
228    }
229    else {
230        $twitter_handles[0]->twitter_follow(@_);
231    }
232}
233
234sub twitter_unfollow {
235    my $account = shift;
236
237    if (defined $account) {
238        my $handle = find_account($account);
239        $handle->twitter_unfollow(@_);
240    } elsif (defined $default_handle) {
241        $default_handle->twitter_unfollow(@_);
242    }
243    else {
244        $twitter_handles[0]->twitter_unfollow(@_);
245    }
246}
247
248BarnOwl::new_command(twitter => \&cmd_twitter, {
249    summary     => 'Update Twitter from BarnOwl',
250    usage       => 'twitter [ACCOUNT] [MESSAGE]',
251    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
252    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
253    . "\nOtherwise, prompt for a status message to use."
254   });
255
256BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
257    summary     => 'Send a Twitter direct message',
258    usage       => 'twitter-direct USER [ACCOUNT]',
259    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
260    . "\nor first service if no default is provided)"
261   });
262
263BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
264    {
265    summary     => 'Send a Twitter @ message',
266    usage       => 'twitter-atreply USER [ACCOUNT]',
267    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,' 
268    . "\nor first service if no default is provided)"
269    }
270);
271
272BarnOwl::new_command( 'twitter-follow' => sub { cmd_twitter_follow(@_); },
273    {
274    summary     => 'Follow a user on Twitter',
275    usage       => 'twitter-follow USER [ACCOUNT]',
276    description => 'Follow USER on Twitter ACCOUNT (defaults to default_sender, or first service'
277    . "\nif no default is provided)"
278    }
279);
280
281BarnOwl::new_command( 'twitter-unfollow' => sub { cmd_twitter_unfollow(@_); },
282    {
283    summary     => 'Stop following a user on Twitter',
284    usage       => 'twitter-unfollow USER [ACCOUNT]',
285    description => 'Stop following USER on Twitter ACCOUNT (defaults to default_sender, or first'
286    . "\nservice if no default is provided)"
287    }
288);
289
290sub cmd_twitter {
291    my $cmd = shift;
292    my $account = shift;
293    if (defined $account) {
294        if(@_) {
295            my $status = join(" ", @_);
296            twitter($account, $status);
297            return;
298        }
299    }
300    BarnOwl::start_edit_win('What are you doing?' . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
301}
302
303sub cmd_twitter_direct {
304    my $cmd = shift;
305    my $user = shift;
306    die("Usage: $cmd USER\n") unless $user;
307    my $account = shift;
308    BarnOwl::start_edit_win("$cmd $user" . (defined $account ? " $account" : ""), sub{twitter_direct($account, $user, shift)});
309}
310
311sub cmd_twitter_atreply {
312    my $cmd  = shift;
313    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
314    my $id   = shift;
315    my $account = shift;
316    BarnOwl::start_edit_win("Reply to \@" . $user . (defined $account ? " on $account" : ""), sub { twitter_atreply($account, $user, $id, shift) });
317}
318
319sub cmd_twitter_follow {
320    my $cmd = shift;
321    my $user = shift;
322    die("Usage: $cmd USER\n") unless $user;
323    my $account = shift;
324    twitter_follow($account, $user);
325}
326
327sub cmd_twitter_unfollow {
328    my $cmd = shift;
329    my $user = shift;
330    die("Usage: $cmd USER\n") unless $user;
331    my $account = shift;
332    twitter_unfollow($account, $user);
333}
334
335eval {
336    $BarnOwl::Hooks::receiveMessage->add("BarnOwl::Module::Twitter::handle_message");
337    $BarnOwl::Hooks::mainLoop->add("BarnOwl::Module::Twitter::poll_messages");
338};
339if($@) {
340    $BarnOwl::Hooks::receiveMessage->add(\&handle_message);
341    $BarnOwl::Hooks::mainLoop->add(\&poll_messages);
342}
343
344BarnOwl::filter(qw{twitter type ^twitter$});
345
3461;
Note: See TracBrowser for help on using the repository browser.