Ticker2.pm


package Ticker2;

use strict;
use warnings;

use base 'Mail::SpamAssassin::Plugin';
use Mail::SpamAssassin::Logger qw(:DEFAULT log_message);

sub new {
    my ( $class, $mailsa ) = @_;

    $class = ref($class) || $class;
    my $self = $class->SUPER::new($mailsa);
    bless $self, $class;

    ### register the eval rule
    $self->register_eval_rule("check_stockspam");

    ### store configuration on $conf
    my $conf        = $self->{main}->{conf};
    my $ticker_conf = $conf->{Ticker2} ||= {};

    ### initialize list of symbols
    $ticker_conf->{symbols} = {};

    return $self;
}

my $stock_token_regex = qr{
        (?:\b|_)                            # word boundary, or _
        (?i:Inc|Corp(?:oration)?|Gr[0o]up)  # "inc" or something
        [.,]?                               # optional dot or comma
        \s*\n                               # match to end of line
        ^\s*(?i:Sym\w*\s*:\s*)?             # next line optional sym(bol):
        (                                   # start capture
                (?:[A-Z]\s{0,3}){3}             # match three letters, optional spaces
                [A-Z]                           # match last letter
        )                                   # end capture
        \s*$                                # match until end of line
}xsm;

sub parse_config {
    my ( $self, $opts ) = @_;

    my $key   = $opts->{key};
    my $value = $opts->{value};

    my $ticker_conf = $self->{main}->{conf}->{Ticker2};

    if ( $key eq "ticker_symbol" ) {
        ### ticker symbol can be list of words
        for my $w ( split ' ', $value ) {
            if ( $w !~ /^[A-Z]{4}$/ ) {
                log_message( "warn", "Ticker2: Invalid ticker symbol: $w" );
                next;
            }
            dbg("Ticker2: Adding ticker symbol $w");
            $ticker_conf->{symbols}{$w} = 1;
        }

        $self->inhibit_further_callbacks();
    }
}

sub check_stockspam {
    my ( $self, $per_msg_status, $body_ref ) = @_;

    my $ticker_conf = $self->{main}->{conf}->{Ticker2};
    my $symbol_ref  = $ticker_conf->{symbols};

    ### get the message object
    my $msg_obj = $per_msg_status->get_message();
    dbg("Ticker2: In check_stockspam with msg_obj=$msg_obj");

    my @text_attachments = $msg_obj->find_parts(qr{^text/}i);
    dbg( "Ticker2: found " . scalar(@text_attachments) . " attachments" );

    for my $att (@text_attachments) {
        my ( $type, $text ) = $att->rendered();

        dbg(      "Ticker2: Scanning attachment type $type ("
                . substr( $text, 0, 80 )
                . "...)" );

        return 1 if _check_text( $symbol_ref, $text );
    }
    return 0;
}

sub _check_text {
    my ( $symbol_ref, $msg_text ) = @_;

    while ( my ($possible_symbol) = $msg_text =~ /$stock_token_regex/ ) {
        ### trim all non-letters
        $possible_symbol =~ tr/A-Za-z//cd;
        dbg("Ticker2: testing possible symbol: $possible_symbol");
        return 1 if exists $symbol_ref->{$possible_symbol};
    }
    return 0;
}

1;