Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
567 lines (467 sloc) 14.9 KB
# mailcheck_imap.pl
# Contains code from centericq.pl (public domain) and imapbiff (GPL) and
# hence this is also GPL'd.
use strict;
use vars qw($VERSION %IRSSI);
$VERSION = "0.5";
%IRSSI = (
authors => "David \"Legooolas\" Gardner",
contact => "irssi\@icmfp.com",
name => "mailcheck_imap",
description => "Staturbar item which indicates how many new emails you have in the specified IMAP[S] mailbox",
sbitems => "mailcheck_imap",
license => "GNU GPLv2",
url => "http://icmfp.com/irssi",
);
# TODO:
#
# - command to show status, so we can see if we are currently connected
# - add to statusbar item to say connected/not
#
# ? get user to type in password instead of storing it in a setting...
# - eg. /mailcheck_imap_pass <password>
#
# - settings
# - execute arbitrary command (with /exec?) on new mail?
# - for 'spoing' or something ;)
# - auto-reconnect on/off
#
#
# LATER:
# - show subject/sender/whatever of new mail (customizable)
# - multiple accounts?
# - multiple mailboxes?
# Known bugs: segfaults on exit of irssi when script loaded :/
use Irssi;
use Irssi::TextUI;
use IO::Socket;
# TODO : avoid requiring SSL when it's not in use?
#if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
# Irssi::print("Using SSL.") if $debug_msgs;
# $port = 993;
require IO::Socket::SSL;
# - you need the package libio-socket-ssl-perl on Debian
#}
#
# TODO : Set up signal handling for clean shutdown...
#
#$SIG{'ALRM'} = sub { die "socket timeout" };
#$SIG{'QUIT'} = 'cleanup';
#$SIG{'HUP'} = 'cleanup';
#$SIG{'INT'} = 'cleanup';
#$SIG{'KILL'} = 'cleanup';
#$SIG{'TERM'} = 'cleanup';
sub draw_box ($$$$) {
my ($title, $text, $footer, $colour) = @_;
my $box = '';
$box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
foreach (split(/\n/, $text)) {
$box .= '%R|%n '.$_."\n";
}
$box .= '%R`--<%n'.$footer.'%R>->%n';
$box =~ s/%.//g unless $colour;
return $box;
}
sub show_help() {
my $help = $IRSSI{name}." ".$VERSION."
/mailcheck_imap_help
Display this help.
/mailcheck_imap
Check for new mail immediately, opening the connection if required.
/mailcheck_imap_stop
Close connection to server and stop checking for new mail.
/set mailcheck_imap
Show all mailcheck_imap settings.
Note: You need to set at least host, user and password.
/statusbar <name> add mailcheck_imap
Add statusbar item for mailcheck.
Formats in theme for statusbar item:
(number of new mails in $0, total number of message in $1)
sb_mailcheck_imap = \"{sb Mail: $0 new, $1 total}\";
sb_mailcheck_imap_zero = \"{sb Mail: None new, $1 total}\";
Format in theme for 'new mail arrived' message in current window:
(number of new mails in $0, total number of message in $1)
mailcheck_imap_echo = \"You have $0 new message(s)!\";
Note: You have to set at least the mailcheck_imap_host, user,
and password settings.
IMPORTANT NOTE: As this stores the password in your irssi config
file, you should really set the mode of the file to 0600 so that
it's only readable by your user.
";
my $text = "";
foreach (split(/\n/, $help)) {
$_ =~ s/^\/(.*)$/%9\/$1%9/;
$text .= $_."\n";
}
print CLIENTCRAP draw_box($IRSSI{name}, $text, "Help", 1);
}
sub cmd_mailcheck_imap_help {
show_help();
}
#
# Global variables.
#
my $handle;
my ($logged_in, $sleep);
my ($last_refresh_time, $refresh_tag);
my ($new_messages, $old_new_messages);
my ($total_messages, $old_total_messages);
$handle = 0;
$logged_in = 0;
$old_new_messages = -1;
$old_total_messages = -1;
#
# Subroutine to update status, called every N seconds.
#
sub refresh_mailcheck_imap {
# For now, just print a message and return :)
Irssi::print("update hit.") if Irssi::settings_get_bool('mailcheck_imap_debug');
# ensure we have details for the login..
if(!check_details()) {
return 0;
}
if(!$handle) {
if(!setup_socket()) {
error("Couldn't setup socket to imap server!",0);
return 0;
}
}
Irssi::print("Socket is setup.") if Irssi::settings_get_bool('mailcheck_imap_debug');
if(!$logged_in) {
if(!login()) {
return 0;
}
}
$new_messages = check_imap("UNSEEN");
$total_messages = check_imap("MESSAGES");
$new_messages = 0 if (! $new_messages);
$total_messages = 0 if (! $total_messages);
if ($new_messages eq "-1" || $total_messages eq "-1") {
Irssi::print("check_imap returned an error, no updates.") if Irssi::settings_get_bool('mailcheck_imap_debug');
}
# update statusbar if changed rather than updating every the time...
if(($new_messages != $old_new_messages) ||
($total_messages != $old_total_messages)) {
update_statusbar_item();
}
# TODO : This doesn't work if you get a sequence such as:
# check -> arrive, delete, arrive -> check
# as it is just done on the number of unseen messages and won't know..
if(($new_messages > $old_new_messages) &&
(Irssi::settings_get_bool('mailcheck_imap_echo_new_in_window'))) {
# If set, echo to the current window...
my $theme = Irssi::current_theme();
my $format = $theme->format_expand("{mailcheck_imap_echo}");
if ($format) {
# use theme-specific look
$format = $theme->format_expand("{mailcheck_imap_echo $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
} else {
# use the default look
$format = "mailcheck_imap: You have ".$new_messages." new message(s).";
}
print CLIENTCRAP $format;
}
$old_new_messages = $new_messages;
$old_total_messages = $total_messages;
# Adding new timeout to make sure that this function will be called again
if ($refresh_tag) {
Irssi::timeout_remove($refresh_tag);
}
my $time = Irssi::settings_get_int('mailcheck_imap_interval');
$refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
return 1;
}
#
# Subroutine to setup socket handle.
#
sub setup_socket {
# Set an alarm in case we can not connect or get hung. Older versions
# the IO::Socket perl module caused errors with the alarm we set before
# setting up the socket. If this program dies in debug mode saying:
# "Alarm clock", then you can probably fix it by upgrading your perl
# IO module.
my ($host,$port);
$host = Irssi::settings_get_str('mailcheck_imap_host');
$port = Irssi::settings_get_int('mailcheck_imap_port');
# change port number if SSL enabled and original imap port unchanged
if($port == 143 && Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
$port = 993;
}
eval {
alarm 30;
Irssi::print("mailcheck_imap connecting to mail server...");
if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
Irssi::print("Using ssl...") if Irssi::settings_get_bool('mailcheck_imap_debug');
$handle = IO::Socket::SSL->new(Proto => "tcp",
SSL_verify_mode => 0x00,
PeerAddr => $host,
PeerPort => $port,
)
or error("Can't connect to port $port on $host: $!",0), return 0;
} else {
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port,
)
or error("Can't connect to port $port on $host: $!",0), return 0;
}
$handle->autoflush(1); # So output gets there right away.
Irssi::print("...done");
receive();
alarm 0;
};
if ($@) {
alarm 0;
if ($@ =~ /timeout/) {
alarm();
return 0;
} else {
error("$@",0);
return 0;
}
}
return 1;
}
#
# Subroutine to login to the mailbox.
#
sub login {
my ($response,$success);
my ($user,$password);
$user = Irssi::settings_get_str('mailcheck_imap_user');
$password = Irssi::settings_get_str('mailcheck_imap_password');
$logged_in = 0;
# Set an alarm in case we can not connect or get hung. Older versions
# the IO::Socket perl module caused errors with the alarm we set before
# setting up the socket. If this program dies in debug mode saying:
# "Alarm clock", then you can probably fix it by upgrading your perl
# IO module.
eval {
alarm 30;
send_data("A001 LOGIN \"$user\" \"$password\"","\"$user\"");
while (1) {
($success,$response) = receive();
if (! $success) {
return 0;
}
last if $response =~ /LOGIN|OK/;
}
if ($response =~ /fail|BAD/) {
return 0;
} else {
$logged_in = 1;
}
alarm 0;
};
if ($@) {
alarm 0;
if ($@ =~ /timeout/) {
alarm();
return 0;
} else {
error("$@",0);
return 0;
}
}
# Success! :D
return 1;
}
#
# Subroutine that does check of imap mailbox.
#
sub check_imap {
my ($type) = @_;
#my ($type) = ("MESSAGES");
my ($response,$success,$num_messages);
# Set an alarm in case we can not connect or get hung. Older versions
# the IO::Socket perl module caused errors with the alarm we set before
# setting up the socket. If this program dies in debug mode saying:
# "Alarm clock", then you can probably fix it by upgrading your perl
# IO module.
eval {
alarm 30;
send_data("A003 STATUS INBOX ($type)");
while (1) {
($success,$response) = receive();
if (! $success) {
return "-1";
}
last if $response =~ /STATUS\s+.*?\s+\($type/;
}
($num_messages) = $response =~ /\($type\s+(\d+)\)/;
alarm 0;
};
if ($@) {
alarm 0;
if ($@ =~ /timeout/) {
alarm();
return "-1";
} else {
error("$@",0);
return "-1";
}
}
return $num_messages;
}
#
# Subroutine to send a line to the imap server.
# Block everything after $block.
#
sub send_data {
my ($line,$block) = (@_);
print $handle "$line\r\n";
$line =~ s/(.*$block).*/$1 ----/ if ($block);
Irssi::print("sent: $line") if Irssi::settings_get_bool('mailcheck_imap_debug');
return 1;
}
#
# Subroutine to get a response from the imap server and print.
# that response if in debug mode.
#
sub receive {
my ($response,$success);
$response = "";
$success = 0;
chomp($response = <$handle>);
if ($response) {
Irssi::print("got: $response") if Irssi::settings_get_bool('mailcheck_imap_debug');
$success = 1;
} else {
Irssi::print("no response!") if Irssi::settings_get_bool('mailcheck_imap_debug');
}
return ($success,$response);
}
#
# Subroutine to display and error message in a text box.
#
sub error {
my ($error,$fatal) = (@_);
if ($fatal) {
# TODO : Print some useful message and die?
Irssi::print("mailcheck_imap FATAL : $error");
return 0;
} else {
Irssi::print("mailcheck_imap error : $error");
if ($refresh_tag) {
Irssi::timeout_remove($refresh_tag)
}
my $time = Irssi::settings_get_int('mailcheck_imap_interval');
$refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
$handle = 0;
return 0;
}
}
#
# Subroutine to call when alarm times out.
#
sub alarm {
Irssi::print("Alarm went off!") if Irssi::settings_get_bool('mailcheck_imap_debug');
return 1;
}
#
# Subroutine to clean up.
#
sub cleanup {
if ($handle) {
send_data("A999 LOGOUT");
$handle->close();
}
Irssi::print("mailcheck_imap logged out.");
}
#######################################################################
# Simply requests a statusbar item redraw.
sub update_statusbar_item {
Irssi::statusbar_items_redraw('mailcheck_imap');
}
#######################################################################
# This is the function called by irssi to obtain the statusbar item.
sub mailcheck_imap {
my ($item, $get_size_only) = @_;
my $theme = Irssi::current_theme();
my $format = $theme->format_expand("{sb_mailcheck_imap}");
if ($format) {
# use theme-specific look
$format = $theme->format_expand("{sb_mailcheck_imap $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
} else {
# use the default look
$format = "{sb Mail: ".$new_messages." new, ".$total_messages." total}";
}
if($new_messages == 0) {
if(Irssi::settings_get_bool('mailcheck_imap_show_zero')) {
$format = $theme->format_expand("{sb_mailcheck_imap_zero $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
if (!$format) {
# use the default look
$format = "{sb Mail: None new, ".$total_messages." total}";
}
} else {
$format = "";
}
}
if (length($format) == 0) {
# nothing to print, so don't print at all
if ($get_size_only) {
$item->{min_size} = $item->{max_size} = 0;
}
} else {
$item->default_handler($get_size_only, $format, undef, 1);
}
}
################################################################################
# Ensure that all required details are filled in:
# host, user, password
sub check_details {
my $host = Irssi::settings_get_str('mailcheck_imap_host');
my $user = Irssi::settings_get_str('mailcheck_imap_user');
my $password = Irssi::settings_get_str('mailcheck_imap_password');
if(!$host || !$user || !$password) {
show_help();
return 0;
}
return 1;
}
################################################################################
# Immediately check for new mail (updates statusbar item too)
sub cmd_mailcheck_imap {
refresh_mailcheck_imap();
}
################################################################################
# Kill the connection and stop the refresh.
sub cmd_mailcheck_imap_stop {
if ($refresh_tag) {
Irssi::timeout_remove($refresh_tag);
}
cleanup();
}
# Also close connection on script unload?
sub sig_command_script_unload ($$$) {
my ($script, $server, $witem) = @_;
if($script =~ /^mailcheck_imap\.pl$/ ||
$script =~ /^mailcheck_imap/) {
cleanup();
}
}
Irssi::signal_add_first('command script unload', \&sig_command_script_unload);
#######################################################################
# Adding stuff to irssi
Irssi::settings_add_int('mail', 'mailcheck_imap_interval', 120);
Irssi::settings_add_bool('mail', 'mailcheck_imap_use_ssl', 0);
Irssi::settings_add_bool('mail', 'mailcheck_imap_debug', 0);
Irssi::settings_add_bool('mail', 'mailcheck_imap_show_zero', 0);
Irssi::settings_add_bool('mail', 'mailcheck_imap_echo_new_in_window', 1);
Irssi::settings_add_str('mail', 'mailcheck_imap_host', '');
Irssi::settings_add_int('mail', 'mailcheck_imap_port', 143);
Irssi::settings_add_str('mail', 'mailcheck_imap_user', '');
Irssi::settings_add_str('mail', 'mailcheck_imap_password', '');
Irssi::statusbar_item_register('mailcheck_imap', '{sb $0-}', 'mailcheck_imap');
Irssi::command_bind('mailcheck_imap_help','cmd_mailcheck_imap_help');
Irssi::command_bind('mailcheck_imap','cmd_mailcheck_imap');
Irssi::command_bind('mailcheck_imap_stop','cmd_mailcheck_imap_stop');
#######################################################################
# Startup functions
# Check that everything is fiiiine and start checking if so
if(check_details()) {
# All is ok, so start running it
refresh_mailcheck_imap();
update_statusbar_item();
}
#######################################################################