#!/usr/bin/perl
# Spam Update
#
# Douglas Thrift
#
# $Id$

use strict;
use warnings;

use IMAP::Client;
use IPC::Open2;
use Mail::SpamAssassin;
use MIME::Base64;

my $debug = 0;

for my $arg (@ARGV)
{
	if ($arg eq "-debug")
	{
		$debug = 1;
	}
	else
	{
		print "Usage: $0 [-debug]\n";

		exit 1;
	}
}

my $negative = 'Spam.False Negative';
my $positive = 'Spam.False Positive';
my $spamc = '/usr/local/bin/spamc';

print "Information:
    If you receive spam in your Inbox mailbox, copy it to the $negative mailbox.

    If you receive mail that is not spam in your Spam mailbox, copy it to the $positive mailbox.
";

my $imap = new IMAP::Client;

$imap->onfail('ABORT');
$imap->debuglevel(1) if ($debug);
$imap->capability_checking(1);
$imap->connect(PeerAddr => 'mail.douglasthrift.net', ConnectMethod => 'STARTTLS');

{
	my $user = getpwuid $<;

	open PASS, "$ENV{HOME}/.SpamUpdate.pass" or die "$0: $!\n";

	my $pass = <PASS>;

	chomp $pass;
	close PASS;

	$pass =~ tr/A-Za-z/N-ZA-Mn-za-m/;

	$imap->authenticate($user, decode_base64($pass));
}

for my $job (new Job($negative), new Job($positive))
{
	$imap->select($job->mailbox);

	printf '
%s and %s from the %s mailbox:
', ucfirst $job->learning, $job->collabing, $job->mailbox;

	my $messages = $imap->uidsearch('UNDELETED');
	my $total = 0;
	my $learned = 0;
	my $collabed = 0;

	if (defined $messages)
	{
		my %fetch = $imap->uidfetch($messages, {});

		for my $message (values %fetch)
		{
			++$total;

			my $learn = open2(\*LEARN_OUT, \*LEARN_IN, $spamc, '-L', $job->learn);
			my $collab = open2(\*COLLAB_OUT, \*COLLAB_IN, $spamc, '-C', $job->collab);

			print LEARN_IN $message->{BODY}->{BODY};
			print COLLAB_IN $message->{BODY}->{BODY};
			close LEARN_IN;
			close COLLAB_IN;

			my $learn_out = <LEARN_OUT>;
			my $collab_out = <COLLAB_OUT>;

			close LEARN_OUT;
			close COLLAB_OUT;
			chomp($learn_out, $collab_out);

			print "learn_out = $learn_out\ncollab_out = $collab_out\n" if ($debug);

			++$learned if ($learn_out eq "Message successfully un/learned");
			++$collabed if ($collab_out eq "Message successfully reported/revoked");

			$imap->uidstore($message->{UID}, '+FLAGS.SILENT', '\Deleted');

			waitpid $learn, 0;
			waitpid $collab, 0;
		}
	}

	sub plural
	{
		my $number = shift;

		return $number != 1 ? 's' : '';
	}

	printf "    $learned message%s %s and $collabed message%s %s ($total message%s total).
", plural($learned), $job->learned, plural($collabed), $job->collabed, plural($total);
}

$imap->logout;

{
	package Job;

	sub new
	{
		my $class = shift;
		my $self = {};

		$self->{mailbox} = shift;
		$self->{negative} = $self->{mailbox} eq $negative;

		bless $self, $class;
	}

	sub learning
	{
		my $self = shift;

		return $self->_learn . 'ing';
	}

	sub collabing
	{
		my $self = shift;

		return $self->_collab . 'ing';
	}

	sub learned
	{
		my $self = shift;

		return $self->_learn . 'ed';
	}

	sub collabed
	{
		my $self = shift;

		return $self->_collab . 'ed';
	}

	sub learn
	{
		my $self = shift;

		return $self->{negative} ? 'spam' : 'ham';
	}

	sub collab
	{
		my $self = shift;

		return $self->{negative} ? 'report' : 'revoke';
	}

	sub mailbox
	{
		my $self = shift;

		return $self->{mailbox};
	}

	sub _learn
	{
		my $self = shift;

		return ($self->{negative} ? '' : 'un') . 'learn';
	}

	sub _collab
	{
		my $self = shift;

		return 're' . ($self->{negative} ? 'port' : 'vok');
	}
}
