summaryrefslogtreecommitdiffstats
path: root/scripts/procmaillog.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/procmaillog.pl')
-rw-r--r--scripts/procmaillog.pl367
1 files changed, 367 insertions, 0 deletions
diff --git a/scripts/procmaillog.pl b/scripts/procmaillog.pl
new file mode 100644
index 0000000..34fda32
--- /dev/null
+++ b/scripts/procmaillog.pl
@@ -0,0 +1,367 @@
+use strict;
+use Irssi;
+
+use Encode qw(decode);
+use IO::Handle;
+use Log::Procmail;
+use MIME::Words qw(decode_mimewords);
+use Time::HiRes qw(usleep);
+
+our $VERSION = '2.02';
+our %IRSSI = (
+ authors => 'Cyprien Debu',
+ contact => 'frey@notk.org',
+ name => 'procmaillog',
+ description => 'Gets new mails from procmail.log file',
+ license => 'Public Domain',
+ url => '',
+ changed => '06-2014'
+);
+
+my $sn = $IRSSI{name};
+
+Irssi::settings_add_level $sn, $sn.'_default_level', 'MSGS';
+Irssi::settings_add_int $sn, $sn.'_folder_pad', 15;
+Irssi::settings_add_str $sn, $sn.'_folders_color', '4,^error$';
+Irssi::settings_add_str $sn, $sn.'_folders_level', '';
+Irssi::settings_add_str $sn, $sn.'_folders_silent', 'spam';
+Irssi::settings_add_str $sn, $sn.'_logfile', '~/.procmail.log';
+Irssi::settings_add_int $sn, $sn.'_max_length', 90;
+Irssi::settings_add_str $sn, $sn.'_split_chars', ',;';
+Irssi::settings_add_str $sn, $sn.'_window', '(status)';
+
+Irssi::theme_register([
+ $sn.'_mail', '$0',
+ $sn.'_crap', '{line_start}{hilight '.$sn.':} $0'
+]);
+
+sub print_help
+{
+ print( <<EOF
+
+This script reads your procmail.log file and prints it in the form:
+| folder-name | subject
+
+Many options are available, see /set ${sn}:
+- default_level: default level of printed messages
+- folder_pad: padding added to the folder name if length(folder name) < folder_pad
+- folders_color: semicolon-separated list of pairs of <color, regex>: the folders that match the regex will be colorized following the codes listed here: http://irssi.org/documentation/formats (mIRC colors)
+ Example: 5,foo;8,bar -> colorize foo in red and bar in yellow
+- folders_level: same behaviour as folders_color but with levels instead of color numbers. NOTICES,foo will print folders matching foo with a NOTICES level
+- folders_silent: regex, folders you don't want to print
+- logfile: path to your procmail.log (default: ~/.procmail.log)
+- max_length: max length of the line
+- split_chars: ,; by default, split characters used in folders_color and folders_level strings
+ Change them if you use these characters in your folders names
+- window: the target window name
+
+Available subcommands: help, start, stop.
+
+The script may fail at first launch if it doesn't find your procmail.log file, just set the option and do /${sn} start.
+EOF
+ );
+}
+
+my $child;
+
+sub print_crap
+{
+ Irssi::printformat MSGLEVEL_CLIENTCRAP, $sn.'_crap', $_
+ foreach @_;
+}
+
+sub print_error
+{
+ print_crap "\x034Error:\x03 ".shift, @_;
+}
+
+# Utility function to parse folders_color and folders_level options.
+sub parse_option
+{
+ my $setting = shift;
+ my ($s2, $s1) = split '', Irssi::settings_get_str($sn.'_split_chars');
+ my %hash;
+
+ foreach (split $s1, Irssi::settings_get_str($setting)) {
+ my ($key, $rx) = split $s2;
+ $hash{$key} = $rx if $rx;
+ }
+
+ return %hash;
+}
+
+sub colorize_folder
+{
+ my $folder = shift;
+ my $border = "\x03";
+ my %folders = parse_option $sn.'_folders_color';
+
+ foreach (keys %folders) {
+ return $border.$_.$folder.$border if ($folder =~ /$folders{$_}/);
+ }
+
+ $border = "\x02";
+ return $border.$folder.$border;
+}
+
+sub format_folder
+{
+ my $folder = shift;
+ my $folder_pad = Irssi::settings_get_int $sn.'_folder_pad';
+ my $pad = $folder_pad - length $folder;
+ my $padding = $pad > 0 ? ' ' x $pad : '';
+ return colorize_folder($folder).$padding;
+}
+
+# Used in format_subject
+sub decode_mime
+{
+ my $str = shift;
+ my $decoded;
+
+ foreach (decode_mimewords $str) {
+ $decoded .= decode $_->[1] || 'US-ASCII', $_->[0];
+ }
+
+ return $decoded;
+}
+
+sub format_subject
+{
+ my $str = shift;
+
+ if (index($str, '=?') == -1)
+ { # If no MIME encoding, choose between utf8 and latin-1
+ my $utf8 = 0;
+
+ foreach (split '', $str) {
+ $utf8 = 1 if (ord == 0xc2 or ord == 0xc3);
+ }
+
+ $str = decode('ISO-8859-1', $str) unless $utf8;
+
+ return $str;
+ }
+
+ my $tmp = substr $str, rindex($str, '=?');
+
+ if (index($tmp, '?=') == -1)
+ {
+ if (not $tmp =~ /=\?[a-z0-9_-]+\?[bq]\?/i)
+ { # Encoding pattern not complete
+ $str = substr $str, 0, rindex($str, '=?');
+ }
+ elsif (my ($c) = ($tmp =~ /=\?\S+\?([bq])\?/i))
+ { # Encoding complete, lacks '?=' or just '='
+ if ($c =~ /q/i and index($str, '=', length($str)-2) != -1)
+ { # Remove trailing '=' (beginning of new special character)
+ $str = substr $str, 0, index($str, '=', length($str)-2)
+ }
+ $str .= ($str =~ /\?$/) ? '=' : '?=';
+ }
+ }
+
+ eval { $str = decode_mime $str };
+
+ if ($@) {
+ chomp $@;
+ print_error "Error while decoding subject: $@";
+ $str = "\x034(error)\x03 " . $str;
+ }
+
+ return $str;
+}
+
+# Get the print level from folder name
+sub get_level
+{
+ my $folder = shift;
+
+ my $level = Irssi::settings_get_level $sn.'_default_level';
+ return $level unless $folder;
+
+ my %levels = parse_option $sn.'_folders_level';
+
+ foreach (keys %levels) {
+ $level = Irssi::level2bits $_ if ($folder =~ /$levels{$_}/);
+ }
+
+ return $level;
+}
+
+# Find the right window, build and print the line
+sub printfmt
+{
+ my ($raw_folder, $raw_subject) = @_;
+
+ my $level = get_level $raw_folder;
+ my $folder = format_folder $raw_folder;
+ my $subject = format_subject $raw_subject;
+
+ my $line = "| $folder | $subject";
+ my $max_length = Irssi::settings_get_int $sn.'_max_length';
+ $line = substr($line, 0, $max_length) if ($max_length > 0);
+
+ my $win_name = Irssi::settings_get_str $sn.'_window';
+ my $window = Irssi::window_find_item $win_name;
+
+ unless ($window) {
+ print_error "Could not find window '$win_name'. Stopping.", "Please set ${sn}_window.";
+ do_stop();
+ return;
+ }
+
+ $window->printformat($level, $sn.'_mail', $line);
+}
+
+# Main loop
+sub read_log
+{
+ my $args = shift;
+ my ($log, $tagref) = @$args;
+
+ my $rec = $log->next;
+
+ unless ($rec) {
+ if (defined $child) {
+ # If $child is still running, we just got called too early
+ # (the record is not fully written)
+ return if (system("kill -0 $child &>/dev/null") == 0);
+
+ # Our child was killed by something external
+ print_error "Child killed. Stopping.";
+ undef $child;
+ }
+ # Child killed, close the pipe
+ Irssi::input_remove $$tagref;
+ return;
+ }
+
+ my $folders_silent = Irssi::settings_get_str $sn.'_folders_silent';
+
+ # We can get several mails in a row
+ # Double braces to use next in a do-while loop
+ do {{
+ unless (ref $rec) {
+ # If $rec is not a ref it is an error string
+ printfmt "error", $rec;
+ next;
+ }
+ next if ($folders_silent and $rec->folder =~ /$folders_silent/);
+ printfmt $rec->folder, $rec->subject;
+ }} while ($rec = $log->next);
+}
+
+sub do_start
+{
+ my $filename = Irssi::settings_get_str $sn.'_logfile';
+ my ($logfile, @rest) = glob $filename;
+
+ if ($#rest != -1) {
+ print_crap "I found several files with the given filename ($filename).",
+ "I will use $logfile.";
+ }
+
+ unless (-f $logfile and -r $logfile) {
+ print_error "Could not find $filename, or file not readable.",
+ "See /set ${sn}_logfile.";
+ return;
+ }
+
+ my $log = Log::Procmail->new;
+ my $wh = IO::Handle->new;
+
+ pipe $log->fh, $wh;
+
+ $log->errors(1);
+
+ $log->fh->blocking(0);
+ $wh->autoflush(1);
+
+ $child = fork;
+
+ if (not defined $child) {
+ print_error "Can't fork. Aborting.";
+ return;
+ }
+
+ if ($child > 0) { # parent
+ Irssi::pidwait_add $child;
+ my $tag;
+ my @args = ($log, \$tag);
+ $tag = Irssi::input_add fileno($log->fh), Irssi::INPUT_READ, \&read_log, \@args;
+ return $logfile;
+ } else { # child
+ open STDOUT, '>&', $wh;
+ open STDERR, '>&', $wh;
+ exec qw(tail -fn0), $logfile;
+ }
+}
+
+sub do_stop
+{
+ qx(kill $child);
+ undef $child;
+}
+
+sub cmd_start
+{
+ if (defined $child) {
+ print_crap "Already started, restarting...";
+ do_stop();
+ Irssi::timeout_add_once 200, \&cmd_start, undef;
+ return;
+ }
+
+ my $win_name = Irssi::settings_get_str $sn.'_window';
+ my $window = Irssi::window_find_item $win_name;
+
+ unless ($window) {
+ print_error "Could not find window '$win_name'. Aborting.", "Please set ${sn}_window.";
+ return;
+ }
+
+ if (my $file = do_start) {
+ print_crap "Started on window '$win_name' and file '$file'.";
+ }
+}
+
+sub cmd_stop
+{
+ unless (defined $child) {
+ print_crap "Not running.";
+ return;
+ }
+
+ do_stop();
+ print_crap "Stopped.";
+}
+
+sub UNLOAD
+{
+ do_stop() if $child;
+}
+
+# Subcommands handler
+Irssi::command_bind $sn, sub {
+ my ($data, $server, $item) = @_;
+ $data =~ s/\s+$//g;
+ Irssi::command_runsub $sn, $data, $server, $item;
+};
+
+# Subcommands
+Irssi::command_bind "$sn help", \&print_help;
+Irssi::command_bind "$sn start", \&cmd_start;
+Irssi::command_bind "$sn stop", \&cmd_stop;
+
+# Help command handler
+Irssi::command_bind 'help', sub {
+ $_[0] =~ s/\s+$//g;
+ return unless $_[0] eq $sn;
+ print_help;
+ Irssi::signal_stop;
+};
+
+# Timeout here to print our message after the loading notice
+Irssi::timeout_add_once 200, \&cmd_start, undef;
+