html/mailcheck_imap.pl
1 # mailcheck_imap.pl
2
3 # Contains code from centericq.pl (public domain) and imapbiff (GPL) and
4 # hence this is also GPL'd.
5
6 use vars qw($VERSION %IRSSI);
7 $VERSION = "0.5";
8 %IRSSI = (
9 authors => "David \"Legooolas\" Gardner",
10 contact => "irssi\@icmfp.com",
11 name => "mailcheck_imap",
12 description => "Staturbar item which indicates how many new emails you have in the specified IMAP[S] mailbox",
13 license => "GNU GPLv2",
14 url => "http://icmfp.com/irssi",
15 );
16
17
18 # TODO:
19 #
20 # - command to show status, so we can see if we are currently connected
21 # - add to statusbar item to say connected/not
22 #
23 # ? get user to type in password instead of storing it in a setting...
24 # - eg. /mailcheck_imap_pass <password>
25 #
26 # - settings
27 # - execute arbitrary command (with /exec?) on new mail?
28 # - for 'spoing' or something ;)
29 # - auto-reconnect on/off
30 #
31 #
32 # LATER:
33 # - show subject/sender/whatever of new mail (customizable)
34 # - multiple accounts?
35 # - multiple mailboxes?
36
37
38 # Known bugs: segfaults on exit of irssi when script loaded :/
39
40
41 use Irssi;
42 use Irssi::TextUI;
43
44
45 use strict;
46 use IO::Socket;
47
48 # TODO : avoid requiring SSL when it's not in use?
49 #if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
50 # Irssi::print("Using SSL.") if $debug_msgs;
51 # $port = 993;
52 require IO::Socket::SSL;
53 # - you need the package libio-socket-ssl-perl on Debian
54 #}
55
56 #
57 # TODO : Set up signal handling for clean shutdown...
58 #
59 #$SIG{'ALRM'} = sub { die "socket timeout" };
60 #$SIG{'QUIT'} = 'cleanup';
61 #$SIG{'HUP'} = 'cleanup';
62 #$SIG{'INT'} = 'cleanup';
63 #$SIG{'KILL'} = 'cleanup';
64 #$SIG{'TERM'} = 'cleanup';
65
66
67
68 sub draw_box ($$$$) {
69 my ($title, $text, $footer, $colour) = @_;
70 my $box = '';
71 $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
72 foreach (split(/\n/, $text)) {
73 $box .= '%R|%n '.$_."\n";
74 }
75 $box .= '%R`--<%n'.$footer.'%R>->%n';
76 $box =~ s/%.//g unless $colour;
77 return $box;
78 }
79
80
81 sub show_help() {
82 my $help = $IRSSI{name}." ".$VERSION."
83 /mailcheck_imap_help
84 Display this help.
85 /mailcheck_imap
86 Check for new mail immediately, opening the connection if required.
87 /mailcheck_imap_stop
88 Close connection to server and stop checking for new mail.
89 /set mailcheck_imap
90 Show all mailcheck_imap settings.
91 Note: You need to set at least host, user and password.
92 /statusbar <name> add mailcheck_imap
93 Add statusbar item for mailcheck.
94
95
96 Formats in theme for statusbar item:
97 (number of new mails in $0, total number of message in $1)
98 sb_mailcheck_imap = \"{sb Mail: $0 new, $1 total}\";
99 sb_mailcheck_imap_zero = \"{sb Mail: None new, $1 total}\";
100
101 Format in theme for 'new mail arrived' message in current window:
102 (number of new mails in $0, total number of message in $1)
103 mailcheck_imap_echo = \"You have $0 new message(s)!\";
104
105 Note: You have to set at least the mailcheck_imap_host, user,
106 and password settings.
107
108 IMPORTANT NOTE: As this stores the password in your irssi config
109 file, you should really set the mode of the file to 0600 so that
110 it's only readable by your user.
111 ";
112 my $text = "";
113 foreach (split(/\n/, $help)) {
114 $_ =~ s/^\/(.*)$/%9\/$1%9/;
115 $text .= $_."\n";
116 }
117 print CLIENTCRAP draw_box($IRSSI{name}, $text, "Help", 1);
118 }
119
120
121 sub cmd_mailcheck_imap_help {
122 show_help();
123 }
124
125
126 #
127 # Global variables.
128 #
129 my $handle;
130 my ($logged_in, $sleep);
131 my ($last_refresh_time, $refresh_tag);
132 my ($new_messages, $old_new_messages);
133 my ($total_messages, $old_total_messages);
134
135 $handle = 0;
136 $logged_in = 0;
137 $old_new_messages = -1;
138 $old_total_messages = -1;
139
140
141 #
142 # Subroutine to update status, called every N seconds.
143 #
144 sub refresh_mailcheck_imap {
145
146 # For now, just print a message and return :)
147 Irssi::print("update hit.") if Irssi::settings_get_bool('mailcheck_imap_debug');
148
149 # ensure we have details for the login..
150 if(!check_details()) {
151 return 0;
152 }
153
154 if(!$handle) {
155 if(!setup_socket()) {
156 error("Couldn't setup socket to imap server!",0);
157 return 0;
158 }
159 }
160 Irssi::print("Socket is setup.") if Irssi::settings_get_bool('mailcheck_imap_debug');
161
162 if(!$logged_in) {
163 if(!login()) {
164 return 0;
165 }
166 }
167 $new_messages = check_imap("UNSEEN");
168 $total_messages = check_imap("MESSAGES");
169
170 $new_messages = 0 if (! $new_messages);
171 $total_messages = 0 if (! $total_messages);
172
173 if ($new_messages eq "-1" || $total_messages eq "-1") {
174 Irssi::print("check_imap returned an error, no updates.") if Irssi::settings_get_bool('mailcheck_imap_debug');
175 }
176
177 # update statusbar if changed rather than updating every the time...
178 if(($new_messages != $old_new_messages) ||
179 ($total_messages != $old_total_messages)) {
180 update_statusbar_item();
181 }
182
183
184 # TODO : This doesn't work if you get a sequence such as:
185 # check -> arrive, delete, arrive -> check
186 # as it is just done on the number of unseen messages and won't know..
187 if(($new_messages > $old_new_messages) &&
188 (Irssi::settings_get_bool('mailcheck_imap_echo_new_in_window'))) {
189 # If set, echo to the current window...
190 my $theme = Irssi::current_theme();
191 my $format = $theme->format_expand("{mailcheck_imap_echo}");
192
193 if ($format) {
194 # use theme-specific look
195 $format = $theme->format_expand("{mailcheck_imap_echo $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
196 } else {
197 # use the default look
198 $format = "mailcheck_imap: You have ".$new_messages." new message(s).";
199 }
200
201 print CLIENTCRAP $format;
202 }
203 $old_new_messages = $new_messages;
204 $old_total_messages = $total_messages;
205
206 # Adding new timeout to make sure that this function will be called again
207 if ($refresh_tag) {
208 Irssi::timeout_remove($refresh_tag);
209 }
210 my $time = Irssi::settings_get_int('mailcheck_imap_interval');
211 $refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
212
213 return 1;
214 }
215
216
217 #
218 # Subroutine to setup socket handle.
219 #
220 sub setup_socket {
221 # Set an alarm in case we can not connect or get hung. Older versions
222 # the IO::Socket perl module caused errors with the alarm we set before
223 # setting up the socket. If this program dies in debug mode saying:
224 # "Alarm clock", then you can probably fix it by upgrading your perl
225 # IO module.
226 my ($host,$port);
227
228 $host = Irssi::settings_get_str('mailcheck_imap_host');
229 $port = Irssi::settings_get_int('mailcheck_imap_port');
230
231 # change port number if SSL enabled and original imap port unchanged
232 if($port == 143 && Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
233 $port = 993;
234 }
235
236 eval {
237 alarm 30;
238 Irssi::print("mailcheck_imap connecting to mail server...");
239
240 if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
241 Irssi::print("Using ssl...") if Irssi::settings_get_bool('mailcheck_imap_debug');
242 $handle = IO::Socket::SSL->new(Proto => "tcp",
243 SSL_verify_mode => 0x00,
244 PeerAddr => $host,
245 PeerPort => $port,
246 )
247 or error("Can't connect to port $port on $host: $!",0), return 0;
248 } else {
249 $handle = IO::Socket::INET->new(Proto => "tcp",
250 PeerAddr => $host,
251 PeerPort => $port,
252 )
253 or error("Can't connect to port $port on $host: $!",0), return 0;
254 }
255 $handle->autoflush(1); # So output gets there right away.
256 Irssi::print("...done");
257 receive();
258 alarm 0;
259 };
260 if ($@) {
261 alarm 0;
262 if ($@ =~ /timeout/) {
263 alarm();
264 return 0;
265 } else {
266 error("$@",0);
267 return 0;
268 }
269 }
270 return 1;
271 }
272
273 #
274 # Subroutine to login to the mailbox.
275 #
276 sub login {
277 my ($response,$success);
278 my ($user,$password);
279
280
281 $user = Irssi::settings_get_str('mailcheck_imap_user');
282 $password = Irssi::settings_get_str('mailcheck_imap_password');
283
284
285 $logged_in = 0;
286 # Set an alarm in case we can not connect or get hung. Older versions
287 # the IO::Socket perl module caused errors with the alarm we set before
288 # setting up the socket. If this program dies in debug mode saying:
289 # "Alarm clock", then you can probably fix it by upgrading your perl
290 # IO module.
291 eval {
292 alarm 30;
293 send_data("A001 LOGIN \"$user\" \"$password\"","\"$user\"");
294 while (1) {
295 ($success,$response) = receive();
296 if (! $success) {
297 return 0;
298 }
299 last if $response =~ /LOGIN|OK/;
300 }
301 if ($response =~ /fail|BAD/) {
302 return 0;
303 } else {
304 $logged_in = 1;
305 }
306 alarm 0;
307 };
308 if ($@) {
309 alarm 0;
310 if ($@ =~ /timeout/) {
311 alarm();
312 return 0;
313 } else {
314 error("$@",0);
315 return 0;
316 }
317 }
318 # Success! :D
319 return 1;
320 }
321
322 #
323 # Subroutine that does check of imap mailbox.
324 #
325 sub check_imap {
326 my ($type) = @_;
327
328 #my ($type) = ("MESSAGES");
329
330 my ($response,$success,$num_messages);
331 # Set an alarm in case we can not connect or get hung. Older versions
332 # the IO::Socket perl module caused errors with the alarm we set before
333 # setting up the socket. If this program dies in debug mode saying:
334 # "Alarm clock", then you can probably fix it by upgrading your perl
335 # IO module.
336 eval {
337 alarm 30;
338 send_data("A003 STATUS INBOX ($type)");
339 while (1) {
340 ($success,$response) = receive();
341 if (! $success) {
342 return "-1";
343 }
344 last if $response =~ /STATUS\s+.*?\s+\($type/;
345 }
346 ($num_messages) = $response =~ /\($type\s+(\d+)\)/;
347 alarm 0;
348 };
349 if ($@) {
350 alarm 0;
351 if ($@ =~ /timeout/) {
352 alarm();
353 return "-1";
354 } else {
355 error("$@",0);
356 return "-1";
357 }
358 }
359 return $num_messages;
360 }
361
362
363 #
364 # Subroutine to send a line to the imap server.
365 # Block everything after $block.
366 #
367 sub send_data {
368 my ($line,$block) = (@_);
369 print $handle "$line\r\n";
370 $line =~ s/(.*$block).*/$1 ----/ if ($block);
371 Irssi::print("sent: $line") if Irssi::settings_get_bool('mailcheck_imap_debug');
372 return 1;
373 }
374
375
376 #
377 # Subroutine to get a response from the imap server and print.
378 # that response if in debug mode.
379 #
380 sub receive {
381 my ($response,$success);
382 $response = "";
383 $success = 0;
384 chomp($response = <$handle>);
385 if ($response) {
386 Irssi::print("got: $response") if Irssi::settings_get_bool('mailcheck_imap_debug');
387 $success = 1;
388 } else {
389 Irssi::print("no response!") if Irssi::settings_get_bool('mailcheck_imap_debug');
390 }
391 return ($success,$response);
392 }
393
394 #
395 # Subroutine to display and error message in a text box.
396 #
397 sub error {
398 my ($error,$fatal) = (@_);
399
400 if ($fatal) {
401 # TODO : Print some useful message and die?
402 Irssi::print("mailcheck_imap FATAL : $error");
403 return 0;
404 } else {
405 Irssi::print("mailcheck_imap error : $error");
406
407 if ($refresh_tag) {
408 Irssi::timeout_remove($refresh_tag)
409 }
410 my $time = Irssi::settings_get_int('mailcheck_imap_interval');
411 $refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
412 $handle = 0;
413 return 0;
414 }
415 }
416
417 #
418 # Subroutine to call when alarm times out.
419 #
420 sub alarm {
421 Irssi::print("Alarm went off!") if Irssi::settings_get_bool('mailcheck_imap_debug');
422 return 1;
423 }
424
425
426 #
427 # Subroutine to clean up.
428 #
429 sub cleanup {
430 if ($handle) {
431 send_data("A999 LOGOUT");
432 $handle->close();
433 }
434 Irssi::print("mailcheck_imap logged out.");
435 }
436
437
438
439 #######################################################################
440 # Simply requests a statusbar item redraw.
441
442 sub update_statusbar_item {
443 Irssi::statusbar_items_redraw('mailcheck_imap');
444 }
445
446
447 #######################################################################
448 # This is the function called by irssi to obtain the statusbar item.
449
450 sub mailcheck_imap {
451 my ($item, $get_size_only) = @_;
452
453 my $theme = Irssi::current_theme();
454 my $format = $theme->format_expand("{sb_mailcheck_imap}");
455
456 if ($format) {
457 # use theme-specific look
458 $format = $theme->format_expand("{sb_mailcheck_imap $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
459 } else {
460 # use the default look
461 $format = "{sb Mail: ".$new_messages." new, ".$total_messages." total}";
462 }
463
464 if($new_messages == 0) {
465 if(Irssi::settings_get_bool('mailcheck_imap_show_zero')) {
466 $format = $theme->format_expand("{sb_mailcheck_imap_zero $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
467
468 if (!$format) {
469 # use the default look
470 $format = "{sb Mail: None new, ".$total_messages." total}";
471 }
472 } else {
473 $format = "";
474 }
475 }
476
477 if (length($format) == 0) {
478 # nothing to print, so don't print at all
479 if ($get_size_only) {
480 $item->{min_size} = $item->{max_size} = 0;
481 }
482 } else {
483 $item->default_handler($get_size_only, $format, undef, 1);
484 }
485 }
486
487
488 ################################################################################
489 # Ensure that all required details are filled in:
490 # host, user, password
491 sub check_details {
492 my $host = Irssi::settings_get_str('mailcheck_imap_host');
493 my $user = Irssi::settings_get_str('mailcheck_imap_user');
494 my $password = Irssi::settings_get_str('mailcheck_imap_password');
495
496 if(!$host || !$user || !$password) {
497 show_help();
498 return 0;
499 }
500 return 1;
501 }
502
503
504 ################################################################################
505 # Immediately check for new mail (updates statusbar item too)
506
507 sub cmd_mailcheck_imap {
508 refresh_mailcheck_imap();
509 }
510
511
512 ################################################################################
513 # Kill the connection and stop the refresh.
514 sub cmd_mailcheck_imap_stop {
515 if ($refresh_tag) {
516 Irssi::timeout_remove($refresh_tag);
517 }
518 cleanup();
519 }
520
521 # Also close connection on script unload?
522 sub sig_command_script_unload ($$$) {
523 my ($script, $server, $witem) = @_;
524
525 if($script =~ /^mailcheck_imap\.pl$/ ||
526 $script =~ /^mailcheck_imap/) {
527 cleanup();
528 }
529 }
530
531 Irssi::signal_add_first('command script unload', \&sig_command_script_unload);
532
533
534 #######################################################################
535 # Adding stuff to irssi
536
537 Irssi::settings_add_int('mail', 'mailcheck_imap_interval', 120);
538 Irssi::settings_add_bool('mail', 'mailcheck_imap_use_ssl', 0);
539 Irssi::settings_add_bool('mail', 'mailcheck_imap_debug', 0);
540 Irssi::settings_add_bool('mail', 'mailcheck_imap_show_zero', 0);
541 Irssi::settings_add_bool('mail', 'mailcheck_imap_echo_new_in_window', 1);
542
543 Irssi::settings_add_str('mail', 'mailcheck_imap_host', '');
544 Irssi::settings_add_int('mail', 'mailcheck_imap_port', 143);
545 Irssi::settings_add_str('mail', 'mailcheck_imap_user', '');
546 Irssi::settings_add_str('mail', 'mailcheck_imap_password', '');
547
548
549 Irssi::statusbar_item_register('mailcheck_imap', '{sb $0-}', 'mailcheck_imap');
550
551 Irssi::command_bind('mailcheck_imap_help','cmd_mailcheck_imap_help');
552 Irssi::command_bind('mailcheck_imap','cmd_mailcheck_imap');
553 Irssi::command_bind('mailcheck_imap_stop','cmd_mailcheck_imap_stop');
554
555
556 #######################################################################
557 # Startup functions
558
559 # Check that everything is fiiiine and start checking if so
560 if(check_details()) {
561 # All is ok, so start running it
562 refresh_mailcheck_imap();
563 update_statusbar_item();
564 }
565
566
567 #######################################################################