]> Sergey Matveev's repositories - public-inbox.git/blob - script/public-inbox-learn
mda: hook up new filter functionality
[public-inbox.git] / script / public-inbox-learn
1 #!/usr/bin/perl -w
2 # Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
3 # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4 #
5 # Used for training spam (via SpamAssassin) and removing messages from a
6 # public-inbox
7 my $usage = "$0 (spam|ham) < /path/to/message";
8 use strict;
9 use warnings;
10 use PublicInbox::Config;
11 use PublicInbox::Git;
12 use PublicInbox::Import;
13 use Email::MIME;
14 use Email::MIME::ContentType;
15 $Email::MIME::ContentType::STRICT_PARAMS = 0; # user input is imperfect
16 use PublicInbox::Address;
17 use PublicInbox::Spawn qw(spawn);
18 my $train = shift or die "usage: $usage\n";
19 if ($train !~ /\A(?:ham|spam)\z/) {
20         die "`$train' not recognized.\nusage: $usage\n";
21 }
22
23 my $pi_config = PublicInbox::Config->new;
24 my $err;
25 my $mime = Email::MIME->new(eval {
26         local $/;
27         my $data = scalar <STDIN>;
28         $data =~ s/\AFrom [^\r\n]*\r?\n//s;
29         eval {
30                 my @cmd = (qw(spamc -L), $train);
31                 my ($r, $w);
32                 pipe($r, $w) or die "pipe failed: $!";
33                 open my $null, '>', '/dev/null' or
34                                         die "failed to open /dev/null: $!";
35                 my $nullfd = fileno($null);
36                 my %rdr = (0 => fileno($r), 1 => $nullfd, 2 => $nullfd);
37                 my $pid = spawn(\@cmd, undef, \%rdr);
38                 close $null;
39                 close $r or die "close \$r failed: $!";
40                 print $w $data or die "print \$w failed: $!";
41                 close $w or die "close \$w failed: $!";
42                 waitpid($pid, 0);
43                 die "spamc failed with: $?\n" if $?;
44         };
45         $err = $@;
46         $data
47 });
48
49 # get all recipients
50 my %dests;
51 foreach my $h (qw(Cc To)) {
52         my $val = $mime->header($h) or next;
53         foreach my $email (PublicInbox::Address::emails($val)) {
54                 $dests{lc($email)} = 1;
55         }
56 }
57
58 require PublicInbox::MDA if $train eq "ham";
59
60 # n.b. message may be cross-posted to multiple public-inboxes
61 foreach my $recipient (keys %dests) {
62         my $dst = $pi_config->lookup($recipient) or next;
63         my $git_dir = $dst->{mainrepo} or next;
64         my $git = PublicInbox::Git->new($git_dir);
65         # We do not touch GIT_COMMITTER_* env here so we can track
66         # who trained the message.
67         my $name = $ENV{GIT_COMMITTER_NAME} || $dst->{inbox};
68         my $email = $ENV{GIT_COMMITTER_EMAIL} || $recipient;
69         my $im = PublicInbox::Import->new($git, $name, $email);
70
71         if ($train eq "spam") {
72                 # This needs to be idempotent, as my inotify trainer
73                 # may train for each cross-posted message, and this
74                 # script already learns for every list in
75                 # ~/.public-inbox/config
76                 $im->remove($mime);
77         } else { # $train eq "ham"
78                 # no checking for spam here, we assume the message has
79                 # been reviewed by a human at this point:
80                 PublicInbox::MDA->set_list_headers($mime, $dst);
81
82                 # Ham messages are trained when they're marked into
83                 # a SEEN state, so this is idempotent:
84                 $im->add($mime);
85         }
86         $im->done;
87         eval {
88                 require PublicInbox::SearchIdx;
89                 PublicInbox::SearchIdx->new($git_dir, 2)->index_sync;
90         };
91 }
92
93 if ($err) {
94         warn $err;
95         exit 1;
96 }