Home

WeeChat scripts

Script: pop3_mail.pl

<< Back to scripts   |   Download Download
#
# Copyright (c) 2010 by Nils Görs <weechatter@arcor.de>
#
# checks POP3 server for mails and display mail headers 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Config:
# Add [mail] to your weechat.bar.status.items
#
# 0.1: initial version
# Thanks to Trashlord for the hint with hook_process()
#
# This script needs following perl modules:
# Tie::IxHash
# Mail::POP3Client
# IO::Socket::SSL
# MIME::Base64
# Crypt::Rijndael
# You can install them using "cpan"

# do not sort my hash
use Tie::IxHash;
my %pop3_accounts;
my %mail_counts;
# user@mail.de|pop3.server:port
tie %pop3_accounts, Tie::IxHash;

###
#use strict;
use Mail::POP3Client;
use MIME::Base64;
use Crypt::Rijndael;
use Encode;

#"✉"
my $prgname	= "pop3_mail";
my $version	= "0.1";
my $description	= "check POP3 server for mails and display mail header";
my $item_name	= "mail";


# -------------------------------[ config ]-------------------------------------
my $default_pop3list = "pop3list.txt";
my %default_options = ("refresh"			=> "10",		# interval in minutes to check pop3 accounts
                       "pop3_timeout"			=> "20",		# timeout for pop3_server (in seconds)
                       "show_header"			=> "From|Subject",
                       "passphrase"     	        => "enter passphrase",
                       "delete_passphrase_on_exit"	=> "on",
    );


# ------------------------------[ internal ]-----------------------------------
my %Hooks	= ();		# space for my hooks
my %mailcount	= ();		# how many mails for all accounts and in how many accounts?
my $bar_item	= "";
my $filename	= ();

# -------------------------------[ hook_process() command ]-------------------------------------
# arguments: user, password, server, port, timeout, (no)header
if ($#ARGV == 5 ) {		# six arguments given?
my $user = $ARGV[0];
my $password = $ARGV[1];
my $pop3server = $ARGV[2];
my $port = $ARGV[3];
my $timeout = $ARGV[4];
my $header = $ARGV[5];

if ($port eq "995") {
  $use_ssl = 1;
} else {
  $use_ssl = 0;
}
	  my $pop3 = new Mail::POP3Client( 
					    #DEBUG	=> 1,
					    USER	=> $user,
					    PASSWORD	=> $password,
					    HOST	=> $pop3server,
					    TIMEOUT	=> $timeout,
#					    PORT	=> $port,
					    USESSL	=> $use_ssl, 		# if true use port 995 otherwise port 110
					  );

	  my $num_mesg = $pop3->Count;						# how many messages are there?

if ($header ne "noheader"){

  for( $i = 1; $i <= $pop3->Count(); $i++ ) {
    foreach( $pop3->Head( $i ) ) {
      my $decoded = encode("utf-8",decode("MIME-Header",$_));

      /^($header):\s+/i && print "$decoded\n";	# decode MIME and encode to UTF8
    }
    print "\n";
  }
}else{
  print "$num_mesg\n";
}
$pop3->Close();

exit;
}

# -------------------------------[ weechat program starts here ]-------------------------------------
# check out config settings
sub toggled_by_set{
	my ( $pointer, $option, $value ) = @_;
	my $plugin_name = "plugins.var.perl.$prgname.";
	if ($option eq $plugin_name."refresh"){
		$default_options{refresh} = $value;
	if (!weechat::config_is_set_plugin("refresh")){
	  $default_options{refresh} = 10;	#default 10 minutes
	  weechat::config_set_plugin("refresh", $default_options{refresh});
	}

	if ($default_options{refresh} ne "0"){
		if (defined $Hooks{timer}) {
			unhook_timer();
			hook_timer();
			return weechat::WEECHAT_RC_OK;
		}
	}
	if ($default_options{refresh} eq "0"){
		if (defined $Hooks{timer}) {
			unhook_timer();
		}
	}else{
		if (not defined $Hooks{timer}){
			weechat::config_set_plugin("refresh", "0") unless hook_timer();		# fall back to '0', if hook fails
		}
	}
	}elsif ($option eq $plugin_name."pop3_timeout"){
	  $default_options{pop3_timeout} = $value;
	}elsif ($option eq $plugin_name."show_header"){
	  $default_options{show_header} = $value;
	}elsif ($option eq $plugin_name."passphrase"){
	  return weechat::WEECHAT_RC_OK if (check_passphrase_length($value) eq 1);
	  $default_options{passphrase} = $value;
	}elsif ($option eq $plugin_name."delete_passphrase_on_exit"){
	  $default_options{delete_passphrase_on_exit} = $value;
	}

return weechat::WEECHAT_RC_OK;
}
sub hook_timer{
	count_messages();

	$Hooks{timer} = weechat::hook_timer($default_options{refresh} * 1000 * 60, 0, 0, "count_messages", "");	# period * millisec(1000) * second(60) * minutes(60)
		if ($Hooks{timer} eq '')
		{
			weechat::print("",weechat::prefix("error")."can't enable $prgname, hook failed.");
			return 0;
		}
#	$bar_item = weechat::bar_item_new($item_name, "show_mail","");
	weechat::bar_item_update($item_name);
	return 1;
}
sub unhook_timer{
	weechat::bar_item_remove($bar_item);
	$bar_item = "";
	if (defined $Hooks{timer}){
	  weechat::unhook($Hooks{timer});
	  delete $Hooks{timer};
	}
}

# user commands
sub user_cmd{
	my ($getargs) = ($_[2]);

	  return weechat::WEECHAT_RC_OK if (check_passphrase_length($default_options{passphrase}) eq 1);
	  if ($getargs eq ""){
	    weechat::command("","/help $prgname");
	    return weechat::WEECHAT_RC_OK;
	  }

	if ($getargs eq "list"){							# list all accounts
	  weechat::print("","POP3 accounts:");
	  my $i = 1;
	  my $x = keys %pop3_accounts;
	  if ($x eq 0){
	    	   weechat::print("","no accounts added yet");
	  }else{
	    while ( my($key,$password) = each %pop3_accounts) {
		  my ($user,$pop3server) = split (/\|/,$key);				# format : username|servername:port

#		  $password = decode_Rijndael($password);
		  $i = sprintf("%03d",$i);
		  my ($servername, $port) = split(/:/,$pop3server);
		  $port = $port . weechat::color("chat") . " (" . weechat::color("chat_delimiters") ."ssl" . weechat::color("chat") . ")" if ($port eq "995");
# get number of emails
		  my $account = $user.$servername;
		  my $num_mesg;
		  if (defined $mail_counts{$account}){
		    $num_mesg = $mail_counts{$account};
		  }else{
		    $num_mesg = 0;
		  }
		  $num_mesg = sprintf("% 3d",$num_mesg);
# print infos
		  weechat::print( "",
		  "  ["
		  . weechat::color("chat_delimiters")
		  . $i
		  . weechat::color("chat")
		  . "]"
		  . " mails: "
		  . weechat::color("chat_delimiters")
		  . $num_mesg
		  . weechat::color("chat")
		  . " user: "
		  . weechat::color("chat_delimiters")
		  . $user . weechat::color("chat")
		  . " server: "
		  . weechat::color("chat_delimiters")
		  . $servername
		  . weechat::color("chat")
		  . " port: "
		  . weechat::color("chat_delimiters")
		  . $port
		  . weechat::color("chat"));

#		  . " password: " . $password);
		  $i = $i + 1;
	    }
	  }
	return weechat::WEECHAT_RC_OK;
	}

	    count_messages() if ($getargs eq "check");

	    my ( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(.*?)/ );
	    return weechat::WEECHAT_RC_OK if (not defined $cmd);
	    
	    if ($cmd eq "list" and defined $arg){
	      ( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(\d.*)/ );

		if (not defined $cmd){
		  weechat::print("",weechat::prefix("error")."$prgname: wrong arguments");
		  return weechat::WEECHAT_RC_OK;
		}
	    if ($cmd eq "list"){							# display messages on server for account
		my $i = 1;

	      if ($arg !~ /^-?\d+(?:[\.,]\d+)?$/){					# only numbers for account?
		  weechat::print("",weechat::prefix("error")."$prgname: invalid account number");
		  return weechat::WEECHAT_RC_OK;
	      }

	      foreach my $key ( keys %pop3_accounts ) {
		  my $password = $pop3_accounts{$key};
		  if ($arg == $i){
		  get_message_header($key,$password);
		    return weechat::WEECHAT_RC_OK;
		  }
		  $i = $i + 1;
		  }
		    return weechat::WEECHAT_RC_OK;
		}
	    }

	    if ($cmd eq "del" and defined($arg)){					# check command and if argument exists
	      ( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(\d.*)/ );
		my $i = 1;
		foreach my $key ( keys %pop3_accounts ) {
		  if ($arg == $i){
		    delete $pop3_accounts{$key};					# delete user account
		    save_file();
		    last;
		  }
		  $i = $i + 1;
		}
	      unhook_timer();
	      hook_timer();
	    }

	    if ($cmd eq "add" and defined($arg)){
	      my ( $cmd, $user, $host ,$password ) = ( $getargs =~ /(.*?)\s(.*?)\s(.*?)\s(.*)/ );
	      if (not defined $password or not defined $host or index($host,":") eq -1){
		weechat::print("",weechat::prefix("error")."$prgname: wrong arguments given to add account: <username[\@host]> <servername:port> <password>");
		return weechat::WEECHAT_RC_OK;
	      }
	      $user = $user."|".$host;							# username@adress|host

	      $password = encode_Rijndael($password);
	      chomp($password);
	      $pop3_accounts{$user} = $password;					# add new user account with password
	      save_file();

	      unhook_timer();
	      hook_timer();
	    }

return weechat::WEECHAT_RC_OK;
}

sub init{
	if ( weechat::config_get_plugin("pop3_list") eq "" ) {
		my $wd = weechat::info_get( "weechat_dir", "" );
		$wd =~ s/\/$//;
		weechat::config_set_plugin("pop3_list", $wd . "/" . $default_pop3list );
	}
	read_file();

# get absolute path of script
	my $infolist_pnt = weechat::infolist_get("perl_script","",$prgname);
	weechat::infolist_next($infolist_pnt);
	$filename = weechat::infolist_string($infolist_pnt,"filename");
	weechat::infolist_free($infolist_pnt);

#set default config
foreach my $option (keys %default_options) {
    if ( !weechat::config_is_set_plugin($option) ) {
        weechat::config_set_plugin($option, $default_options{$option});
    }else {
	  $default_options{$option} = weechat::config_get_plugin($option);
    }
}
    return if (check_passphrase_length($default_options{passphrase}) eq 1);
}

sub show_mail {
    if ( (keys %pop3_accounts) == 1 ){
      return sprintf ("✉: %d", $mailcount{mails_over_all});
    }else{
      return sprintf ("✉: %d (%d)", $mailcount{mails_over_all}, $mailcount{accounts_with_mails});
    }
}

# -------------------------------[ hook_process callback ]-------------------------------------
sub hook_process_cb{
my ($data, $command, $return_code, $out, $err) = @_;

return weechat::WEECHAT_RC_OK if ( $return_code > 0 );				# something went wrong!


my (undef, undef, $nick, undef, $server, undef) = split /\s+/, $command, 6;
my $account = $nick.$server;
chomp($out);									# kill LF

$mailcount{num_mesg} = $out;							# save number of messages
$mailcount{num_mesg} = 0 if ($out == -1);					# something wrong with account!
$mailcount{accounts_with_mails} = $mailcount{accounts_with_mails} + 1 if ($out > 0);	# how many accounts have mails?

$mail_counts{$account} = $mailcount{num_mesg};					# save number of mails for the account
$mailcount{mails_over_all} = $mailcount{mails_over_all} + $mailcount{num_mesg};	# all counted messages from all accounts

if ( $bar_item ne "" and $mailcount{mails_over_all} == 0 or not defined $mailcount{mails_over_all} ){
	weechat::bar_item_remove($bar_item);
	$bar_item = "";
}elsif ( ($mailcount{mails_over_all} > 0) and ($bar_item eq "") ){
	$bar_item = weechat::bar_item_new($item_name, "show_mail","");
}

weechat::bar_item_update($item_name);
return weechat::WEECHAT_RC_OK;
}

# mail header
sub hook_process_cb2{
my ($data, $command, $return_code, $out, $err) = @_;
return weechat::WEECHAT_RC_OK if ( $return_code > 0 );

my (undef, undef, $nick, undef, $server, undef) = split /\s+/, $command, 6;

my @array=split(/\n\n/,$out);
my $i = 1;
  weechat::print( "", "mails for \"$nick\" on server \"$server\":");

foreach (@array) {
  weechat::print( "", weechat::color("chat_delimiters")
		. "[" . weechat::color("chat_buffer")
		. " mail " . $i
		. weechat::color("chat_delimiters")
		. " ]" . weechat::color("chat")
		. "\n$_" );
$i++;

}
return weechat::WEECHAT_RC_OK;
}
# -------------------------------[ POP3 routines ]-------------------------------------
sub count_messages{
    $mailcount{num_mesg} = 0;
    $mailcount{mails_over_all} = 0;
    $mailcount{accounts_with_mails} = 0;


    foreach my $key ( keys %pop3_accounts ) {
      my ($user,$pop3server,$port) = split (/:|\|/,$key);					# format : username|servername:port
      my $password = decode_Rijndael($pop3_accounts{$key});
	  weechat::hook_process("perl $filename $user $password $pop3server $port $default_options{pop3_timeout} noheader", 1100 * $default_options{pop3_timeout},"hook_process_cb","");
    }

return weechat::WEECHAT_RC_OK;
}

sub get_message_header {
   my ($key,$password) = @_;
   my $i;
   my $use_ssl = 0;

    if ($default_options{show_header} eq "" or not defined $default_options{show_header}){
	weechat::print("",weechat::prefix("error")."$prgname: option \"plugins.var.perl.$prgname.show_header\" not set.");
	return;
    }

      my ($user,$pop3server,$port) = split (/:|\|/,$key);			# format : username|servername:port
      my $account = $user.$pop3server;
    if (not defined $mail_counts{$account} or $mail_counts{$account} eq 0){
	weechat::print("",weechat::prefix("error")."$prgname: no mails on server \"$pop3server\" for \"$user\"");
	return;
    }

      $password = decode_Rijndael($password);

	  weechat::hook_process("perl $filename $user $password $pop3server $port $default_options{pop3_timeout} \"$default_options{show_header}\"", 1100 * $default_options{pop3_timeout},"hook_process_cb2","");
}

# -------------------------------[ AES encode / decode: key must be 128, 192 or 256 bits long ]-------------------------------------
sub encode_Rijndael{
  my ( $plaintext ) = @_; # Your string to be encrypted

  my $base64 = "";

  my $rcipher = Crypt::Rijndael->new ($default_options{passphrase}, Crypt::Rijndael::MODE_CBC());
  $rcipher->set_iv($default_options{passphrase}); # You may wish this IV to be something different from the Secret Key


  if(length($plaintext) % 16 != 0 ) {
  $plaintext .= ' ' x (16 - (length($plaintext) % 16)); } 
  my $rencrypted = $rcipher->encrypt($plaintext);		# encrypt

  $base64 = encode_base64($rencrypted);				# base64 it
  return $base64;
}

sub decode_Rijndael{
  my ( $base64 ) = @_; # enctypted base64 string

  my $rcipher = Crypt::Rijndael->new ($default_options{passphrase}, Crypt::Rijndael::MODE_CBC());
  $rcipher->set_iv($default_options{passphrase}); # You may wish this IV to be something different from the Secret Key

  $base64 = decode_base64($base64);

#  if(length($plaintext) % 16 != 0 ) {
#  $plaintext .= ' ' x (16 - (length($plaintext) % 16)); } 
  my $rencrypted = $rcipher->decrypt($base64);
  $rencrypted =~ s/\s+\z//;					# remove space from end of string

  return $rencrypted;
}

sub check_passphrase_length{
  my ( $passphrase ) = @_;
	  my $len = length($passphrase);
	  if (! ($len == 16) or ($len == 24) or ($len == 32) ){
	  weechat::print("",weechat::prefix("error")."$prgname: wrong key length: passphrase must be 128, 192 or 256 bits long");
	  return 1; #false
	  }
return 0; #true
}

# -------------------------------[ load, save, shutdown, debug routine ]-------------------------------------
sub save_file {
  my $x = keys %pop3_accounts;
  if ($x ne 0){							# entries in pop3_accounts?
	my $pop3list = weechat::config_get_plugin( "pop3_list" );
	open (WL, ">", $pop3list) || DEBUG("write pop3_list: $!");
	while ( my($user,$passwort) = each %pop3_accounts) {
			print WL "$user $passwort\n";
	}
	close WL;
  }else{
	my $pop3list = weechat::config_get_plugin( "pop3_list" );
      unlink($pop3list);
  }
}
sub read_file {
	my $pop3list = weechat::config_get_plugin("pop3_list");
	return unless -e $pop3list;
	open (WL, "<", $pop3list) || DEBUG("$pop3list: $!");
	while (<WL>) {
		chomp;								# kill LF
			my ( $user, $password ) = split / /;			# <user> <password>
			if (not defined $user){
				close WL;
				weechat::print("",weechat::prefix("error")."$prgname: $pop3list is not valid...");
				return;
			}
		$pop3_accounts{$user} = $password  if length $_;
	}
	close WL;
}

sub shutdown{
# remove my hooks
  if (defined $Hooks{timer}) {
      weechat::unhook($Hooks{timer});
  }
  if (defined $Hooks{command}) {
      weechat::unhook($Hooks{command});
  }
  if (defined $Hooks{config}) {
      weechat::unhook($Hooks{config});
  }
  if ($default_options{delete_passphrase_on_exit} eq "on"){
       weechat::config_unset_plugin("passphrase","");
  }

  return weechat::WEECHAT_RC_OK;
}
sub DEBUG {weechat::print('', "***\t" . $_[0]);}

# first function called by a WeeChat-script.
weechat::register($prgname, "Nils Görs <weechatter\@arcor.de>", $version,
                  "GPL3", $description, "shutdown", "");

init();

$Hooks{config}  = weechat::hook_config( "plugins.var.perl.$prgname.*", "toggled_by_set", "" );
$Hooks{command} = weechat::hook_command($prgname, $description,
		"[list <account>] | [add user(\@host) server:port password] | [del number] | check", 

		"list         : display account(s)\n".
		"list <number>: display mail header(s) for specified account\n".
		"add          : add new account (template: <username> <server:port> <password>)\n".
		"del <number> : delete an account\n".
		"check        : check POP3 account(s) manually\n".
		"\n".
		"This script is using Rijndael(AES) encryption to protect your pop3 password(s) in config file.\n".
		"Keep in mind that the passphrase is not encrypted. Use /rmodifier function to hide passphrase.\n".
		"\n".
		"Options:\n".
		"plugins.var.perl.$prgname.passphrase                   : to encrypt pop3 passwords in config file (default: empty)\n".
		"plugins.var.perl.$prgname.pop3_list                    : file to store account, server and password (default: ~/.weechat/pop3list.txt)\n".
		"plugins.var.perl.$prgname.pop3timeout                  : set a timeout value for socket operations (default: 20 seconds)\n".
		"plugins.var.perl.$prgname.refresh                      : checks pop3 account (default: 10 minutes)\n".
		"plugins.var.perl.$prgname.show_header                  : displays mail headers (default: From|Subject)\n".
		"plugins.var.perl.$prgname.delete_passphrase_on_exit    : delete passphrase on exit (default: on)\n".
		"You have to edit option \"delete_passphrase_on_exit\" manually each time when weechat or script (re)starts. Switching option to \"off\" will keep passphrase in weechat-config.\n".
		"\n".
		"Add item [mail] to your \"weechat.bar.status.items\"\n".
		"\n".
		"Examples:\n".
		"Add account with hostname and ssl/tls protocol to monitore:\n".
		"/$prgname add myuser\@host.de pop3.server.de:995 mypassword\n".
		"Add account without hostname and without ssl/tls protocol to monitore:\n".
		"/$prgname add myuser pop3.server.de:110 mypassword\n".
		"Delete account with number 001 from list:\n".
		"/$prgname del 1\n",
		"add|del|list|check", "user_cmd", "");


hook_timer() if ($default_options{refresh} ne "0");

weechat::bar_item_update($item_name);