Fix link to WWW::Shorten. Add docs for twitter_add_follow_extra, twitter_del_follow_...
[twirssi-net-twitter-lite.git] / twirssi.pl
1 use strict;
2 use Irssi;
3 use Irssi::Irc;
4 use HTTP::Date;
5 use HTML::Entities;
6 use File::Temp;
7 use LWP::Simple;
8 use Data::Dumper;
9 use Encode;
10 $Data::Dumper::Indent = 1;
11
12 use vars qw($VERSION %IRSSI);
13
14 $VERSION = "2.3.1beta";
15 %IRSSI   = (
16     authors     => 'Dan Boger',
17     contact     => 'zigdon@gmail.com',
18     name        => 'twirssi',
19     description => 'Send twitter updates using /tweet.  '
20       . 'Can optionally set your bitlbee /away message to same',
21     license => 'GNU GPL v2',
22     url     => 'http://twirssi.com',
23     changed => '$Date: 2009-08-07 01:24:53 -0700 (Fri, 07 Aug 2009) $',
24 );
25
26 my $window;
27 my $twit;
28 my %twits;
29 my $user;
30 my $defservice;
31 my $poll;
32 my $last_poll;
33 my $last_friends_poll = 0;
34 my %nicks;
35 my %friends;
36 my %tweet_cache;
37 my %id_map;
38 my $failwhale  = 0;
39 my $first_call = 1;
40 my $child_pid;
41 my %fix_replies_index;
42
43 my %irssi_to_mirc_colors = (
44     '%k' => '01',
45     '%r' => '05',
46     '%g' => '03',
47     '%y' => '07',
48     '%b' => '02',
49     '%m' => '06',
50     '%c' => '10',
51     '%w' => '15',
52     '%K' => '14',
53     '%R' => '04',
54     '%G' => '09',
55     '%Y' => '08',
56     '%B' => '12',
57     '%M' => '13',
58     '%C' => '11',
59     '%W' => '00',
60 );
61
62 sub cmd_direct {
63     my ( $data, $server, $win ) = @_;
64
65     return unless &logged_in($twit);
66
67     my ( $target, $text ) = split ' ', $data, 2;
68     unless ( $target and $text ) {
69         &notice("Usage: /dm <nick> <message>");
70         return;
71     }
72
73     &cmd_direct_as( "$user $data", $server, $win );
74 }
75
76 sub cmd_direct_as {
77     my ( $data, $server, $win ) = @_;
78
79     return unless &logged_in($twit);
80
81     my ( $username, $target, $text ) = split ' ', $data, 3;
82     unless ( $username and $target and $text ) {
83         &notice("Usage: /dm_as <username> <nick> <message>");
84         return;
85     }
86
87     return unless $username = &valid_username($username);
88
89     eval {
90         if ( $twits{$username}
91             ->new_direct_message( { user => $target, text => $text } ) )
92         {
93             &notice("DM sent to $target: $text");
94             $nicks{$target} = time;
95         } else {
96             my $error;
97             eval {
98                 $error = JSON::Any->jsonToObj( $twits{$username}->get_error() );
99                 $error = $error->{error};
100             };
101             die $error if $error;
102             &notice("DM to $target failed");
103         }
104     };
105
106     if ($@) {
107         &notice("DM caused an error: $@");
108         return;
109     }
110 }
111
112 sub cmd_retweet {
113     my ( $data, $server, $win ) = @_;
114
115     return unless &logged_in($twit);
116
117     $data =~ s/^\s+|\s+$//;
118     unless ($data) {
119         &notice("Usage: /retweet <nick[:num]> [comment]");
120         return;
121     }
122
123     my ( $id, $data ) = split ' ', $data, 2;
124
125     &cmd_retweet_as( "$user $id $data", $server, $win );
126 }
127
128 sub cmd_retweet_as {
129     my ( $data, $server, $win ) = @_;
130
131     unless ( Irssi::settings_get_bool("twirssi_track_replies") ) {
132         &notice("twirssi_track_replies is required in order to reteet.");
133         return;
134     }
135
136     return unless &logged_in($twit);
137
138     $data =~ s/^\s+|\s+$//;
139     my ( $username, $id, $data ) = split ' ', $data, 3;
140
141     unless ($username) {
142         &notice("Usage: /retweet_as <username> <nick[:num]> [comment]");
143         return;
144     }
145
146     return unless $username = &valid_username($username);
147
148     my $nick;
149     $id =~ s/[^\w\d\-:]+//g;
150     ( $nick, $id ) = split /:/, $id;
151     unless ( exists $id_map{ lc $nick } ) {
152         &notice("Can't find a tweet from $nick to retweet!");
153         return;
154     }
155
156     $id = $id_map{__indexes}{$nick} unless $id;
157     unless ( $id_map{ lc $nick }[$id] ) {
158         &notice("Can't find a tweet numbered $id from $nick to retweet!");
159         return;
160     }
161
162     unless ( $id_map{__tweets}{ lc $nick }[$id] ) {
163         &notice("The text of this tweet isn't saved, sorry!");
164         return;
165     }
166
167 # Irssi::settings_add_str( "twirssi", "twirssi_retweet_format", 'RT $n: $t ${-- $c$}' );
168     my $text = Irssi::settings_get_str("twirssi_retweet_format");
169     $text =~ s/\$n/\@$nick/g;
170     if ($data) {
171         $text =~ s/\${|\$}//g;
172         $text =~ s/\$c/$data/;
173     } else {
174         $text =~ s/\${.*?\$}//;
175     }
176     $text =~ s/\$t/$id_map{__tweets}{ lc $nick }[$id]/;
177
178     $data = &shorten($text);
179
180     return if &too_long($data);
181
182     my $success = 1;
183     eval {
184         unless (
185             $twits{$username}->update(
186                 {
187                     status => $data,
188
189                     # in_reply_to_status_id => $id_map{ lc $nick }[$id]
190                 }
191             )
192           )
193         {
194             &notice("Update failed");
195             $success = 0;
196         }
197     };
198     return unless $success;
199
200     if ($@) {
201         &notice("Update caused an error: $@.  Aborted");
202         return;
203     }
204
205     foreach ( $data =~ /@([-\w]+)/ ) {
206         $nicks{$1} = time;
207     }
208
209     &notice("Retweet sent");
210 }
211
212 sub cmd_tweet {
213     my ( $data, $server, $win ) = @_;
214
215     return unless &logged_in($twit);
216
217     $data =~ s/^\s+|\s+$//;
218     unless ($data) {
219         &notice("Usage: /tweet <update>");
220         return;
221     }
222
223     &cmd_tweet_as( "$user\@$defservice $data", $server, $win );
224 }
225
226 sub cmd_tweet_as {
227     my ( $data, $server, $win ) = @_;
228
229     return unless &logged_in($twit);
230
231     $data =~ s/^\s+|\s+$//;
232     $data =~ s/\s\s+/ /g;
233     my ( $username, $data ) = split ' ', $data, 2;
234
235     unless ( $username and $data ) {
236         &notice("Usage: /tweet_as <username> <update>");
237         return;
238     }
239
240     return unless $username = &valid_username($username);
241
242     $data = &shorten($data);
243
244     return if &too_long($data);
245
246     my $success = 1;
247     eval {
248         unless ( $twits{$username}->update($data) )
249         {
250             &notice("Update failed");
251             $success = 0;
252         }
253     };
254     return unless $success;
255
256     if ($@) {
257         &notice("Update caused an error: $@.  Aborted.");
258         return;
259     }
260
261     foreach ( $data =~ /@([-\w]+)/ ) {
262         $nicks{$1} = time;
263     }
264
265     my $away = &update_away($data);
266
267     &notice( "Update sent" . ( $away ? " (and away msg set)" : "" ) );
268 }
269
270 sub cmd_reply {
271     my ( $data, $server, $win ) = @_;
272
273     return unless &logged_in($twit);
274
275     $data =~ s/^\s+|\s+$//;
276     unless ($data) {
277         &notice("Usage: /reply <nick[:num]> <update>");
278         return;
279     }
280
281     my ( $id, $data ) = split ' ', $data, 2;
282     unless ( $id and $data ) {
283         &notice("Usage: /reply <nick[:num]> <update>");
284         return;
285     }
286
287     &cmd_reply_as( "$user $id $data", $server, $win );
288 }
289
290 sub cmd_reply_as {
291     my ( $data, $server, $win ) = @_;
292
293     unless ( Irssi::settings_get_bool("twirssi_track_replies") ) {
294         &notice("twirssi_track_replies is required in order to reply to "
295               . "specific tweets.  Either enable it, or just use /tweet "
296               . "\@username <text>." );
297         return;
298     }
299
300     return unless &logged_in($twit);
301
302     $data =~ s/^\s+|\s+$//;
303     my ( $username, $id, $data ) = split ' ', $data, 3;
304
305     unless ( $username and $data ) {
306         &notice("Usage: /reply_as <username> <nick[:num]> <update>");
307         return;
308     }
309
310     return unless $username = &valid_username($username);
311
312     my $nick;
313     $id =~ s/[^\w\d\-:]+//g;
314     ( $nick, $id ) = split /:/, $id;
315     unless ( exists $id_map{ lc $nick } ) {
316         &notice("Can't find a tweet from $nick to reply to!");
317         return;
318     }
319
320     $id = $id_map{__indexes}{$nick} unless $id;
321     unless ( $id_map{ lc $nick }[$id] ) {
322         &notice("Can't find a tweet numbered $id from $nick to reply to!");
323         return;
324     }
325
326     if ( Irssi::settings_get_bool("twirssi_replies_autonick") ) {
327
328         # remove any @nick at the beginning of the reply, as we'll add it anyway
329         $data =~ s/^\s*\@?$nick\s*//;
330         $data = "\@$nick " . $data;
331     }
332
333     $data = &shorten($data);
334
335     return if &too_long($data);
336
337     my $success = 1;
338     eval {
339         unless (
340             $twits{$username}->update(
341                 {
342                     status                => $data,
343                     in_reply_to_status_id => $id_map{ lc $nick }[$id]
344                 }
345             )
346           )
347         {
348             &notice("Update failed");
349             $success = 0;
350         }
351     };
352     return unless $success;
353
354     if ($@) {
355         &notice("Update caused an error: $@.  Aborted");
356         return;
357     }
358
359     foreach ( $data =~ /@([-\w]+)/ ) {
360         $nicks{$1} = time;
361     }
362
363     my $away = &update_away($data);
364
365     &notice( "Update sent" . ( $away ? " (and away msg set)" : "" ) );
366 }
367
368 sub gen_cmd {
369     my ( $usage_str, $api_name, $post_ref ) = @_;
370
371     return sub {
372         my ( $data, $server, $win ) = @_;
373
374         return unless &logged_in($twit);
375
376         $data =~ s/^\s+|\s+$//;
377         unless ($data) {
378             &notice("Usage: $usage_str");
379             return;
380         }
381
382         my $success = 1;
383         eval {
384             unless ( $twit->$api_name($data) )
385             {
386                 &notice("$api_name failed");
387                 $success = 0;
388             }
389         };
390         return unless $success;
391
392         if ($@) {
393             &notice("$api_name caused an error.  Aborted.");
394             return;
395         }
396
397         &$post_ref($data) if $post_ref;
398       }
399 }
400
401 sub cmd_switch {
402     my ( $data, $server, $win ) = @_;
403
404     $data =~ s/^\s+|\s+$//g;
405     $data = &normalize_username($data);
406     if ( exists $twits{$data} ) {
407         &notice("Switching to $data");
408         $twit = $twits{$data};
409         if ( $data =~ /(.*)\@(.*)/ ) {
410             $user       = $1;
411             $defservice = $2;
412         } else {
413             &notice("Couldn't figure out what service '$data' is on");
414         }
415     } else {
416         &notice("Unknown user $data");
417     }
418 }
419
420 sub cmd_logout {
421     my ( $data, $server, $win ) = @_;
422
423     $data =~ s/^\s+|\s+$//g;
424     $data = $user unless $data;
425     return unless $data = &valid_username($data);
426
427     &notice("Logging out $data...");
428     $twits{$data}->end_session();
429     delete $twits{$data};
430     undef $twit;
431     if ( keys %twits ) {
432         &cmd_switch( ( keys %twits )[0], $server, $win );
433     } else {
434         Irssi::timeout_remove($poll) if $poll;
435         undef $poll;
436     }
437 }