diff options
Diffstat (limited to 'scripts/procmaillog.pl')
| -rw-r--r-- | scripts/procmaillog.pl | 367 | 
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; + | 
