Git-Code-Review

 view release on metacpan or  search on metacpan

lib/Git/Code/Review/Command/mailhandler.pm  view on Meta::CPAN

# ABSTRACT: Manage replies to the code review mailbox.
package Git::Code::Review::Command::mailhandler;
use strict;
use warnings;

use CLI::Helpers qw(
    debug
    debug_var
    output
    verbose
);
use Config::Auto;
use DateTime::Format::Mail;
use Email::Address;
use Email::Simple;
use Email::MIME;
use File::Spec;
use Git::Code::Review -command;
use Git::Code::Review::Notify;
use Git::Code::Review::Utilities qw(:all);
use Mail::IMAPClient;
use Mojo::DOM;
use Text::Wrap qw(fill);
use YAML;


my %METRICS = ();
my %CONFIG;
sub _init {
    return if scalar %CONFIG;
    %CONFIG = gcr_config();
    $CONFIG{ mailhandler } ||= {};
}


sub opt_spec {
    _init();
    my $mail_cfg = $CONFIG{ mailhandler };
    return (
        ['server:s',           "IMAP Server",                      {default=>exists $mail_cfg->{'global.server'}  ? $mail_cfg->{'global.server'} : 'localhost' }],
        ['port:i',             "IMAP Server Port",                 {default=>exists $mail_cfg->{'global.port'}    ? $mail_cfg->{'global.port'} : 993 }],
        ['folder:s',           "IMAP Folder to scan",              {default=>exists $mail_cfg->{'global.folder'}  ? $mail_cfg->{'global.folder'} : 'INBOX' }],
        ['ssl!',               "Use SSL. Use --no-ssl to turn off ssl", {default=>exists $mail_cfg->{'global.ssl'}  ? $mail_cfg->{'global.ssl'} : 1 }],
        ['credentials-file:s', "Location of the Credentials File", {default=>exists $mail_cfg->{'global.credentials-file'}  ? $mail_cfg->{'global.credentials-file'} : '/etc/code-review/mailhandler.config' }],
        ['dry-run',             "Test connection, show all configuration variables and exit",],
    );
}

sub description {
    my $DESC = <<"    EOH";
    SYNOPSIS

        git-code-review mailhandler [options]

    DESCRIPTION

        Manage replies to the code review mailbox.

    EXAMPLES

        git-code-review mailhandler

        git-code-review mailhandler --dry-run

        git-code-review mailhandler --dry-run --server mail.server.com --port 993 --credentials-file ~/mail.config

        git-code-review mailhandler --server mail.server.com --port 993 --credentials-file ~/mail.config

        git-code-review mailhandler --folder Replies

    OPTIONS
    EOH
    $DESC =~ s/^[ ]{4}//mg;
    return $DESC;
}

sub execute {
    my($cmd,$opt,$args) = @_;
    die "Not initialized, run git-code-review init!" unless gcr_is_initialized();
    die "Too many arguments: " . join( ' ', @$args ) if scalar @$args > 0;
    _init();
    $CONFIG{ mailhandler }{ 'global.auto-approve' } = 1 unless exists $CONFIG{ mailhandler }{ 'global.auto-approve' }; # not allowed to override via options
    my $auto_approve = $CONFIG{ mailhandler }{ 'global.auto-approve' };

    debug({color=>'cyan'},"Git::Code::Review Mailhandler Config");
    debug_var($opt);
    debug({clear => 1}, "Defaults");
    debug(Dump $CONFIG{mailhandler});

    my @missing_opts = grep { ! exists $opt->{ $_ } } qw( credentials_file server );    # ensure required options are provided
    if ( @missing_opts ) {
        output( {color=>'red',stderr=>1}, sprintf "Missing required options: %s", join( ', ', map { s/\_/-/g; } @missing_opts ) );
        exit 1;
    }
    if ( ! -f $opt->{ credentials_file } ) {
        output({color=>'red',stderr=>1}, sprintf "The credentials file %s does not exist. You can provide the right file with --credentials-file or configure it in global.credentials-file in mailhandler.config file", $opt->{ credentials_file } );
        exit 1;
    }

    # Parse the config file
    my $creds = Config::Auto::parse($opt->{credentials_file});

    # Try to grab the username/password
    my %mapping = (
        username => [qw(user userid username)],
        password => [qw(pass passwd password)],
    );
    my %credentials = ();
    foreach my $key (keys %mapping) {
        foreach my $try (@{ $mapping{$key} }) {
            next unless exists $creds->{$try};
            $credentials{$key} = $creds->{$try};
            last;
        }
        if(!exists $credentials{$key}) {
            output({color=>'red',stderr=>1}, "Unable to find the '$key' in $opt->{credentials_file}");
            exit 1;
        }
    }

    if ( $opt->{ dry_run } ) {
        # show config details for connection test
        output( 'Trying to connect to the mail server with the following settings.' );
        output( join( ": ", @$_ ) ) for (
            [ Server   => $opt->{ server } ],
            [ Port     => $opt->{ port } ],
            [ Ssl      => $opt->{ ssl } ],
            [ credentials_file  => $opt->{ credentials_file } ],
            [ User     => $credentials{ username } ],
            [ Password => '********' ],
            [ AutoApprove => ( $auto_approve ? '1' : '0' ) ],
        );
    }

    my $imap = Mail::IMAPClient->new(
        Server   => $opt->{server},
        Port     => $opt->{port},
        Ssl      => $opt->{ssl},
        User     => $credentials{username},
        Password => $credentials{password},
    ) or die "Unable to connect to $opt->{server}: $@";
    $METRICS{ connected } = 1;
    verbose({color=>'green'}, sprintf "Successfully connected to %s as %s.", $opt->{server}, $credentials{username});

    return if $opt->{ dry_run }; # we were just testing the connection to the mail server

    my @folders = $imap->folders;
    debug({indent=>1}, "+ Folders discovered: " . join(', ', @folders));

    $imap->select($opt->{folder});

    my @unseen = $imap->unseen();
    verbose({indent=>1}, sprintf "+ Found %d unread messages.", ( scalar @unseen ));
    $METRICS{ emails } = scalar @unseen ;

    # Reset these all the time
    my @EnvRelative = qw(
            GIT_AUTHOR_NAME
            GIT_AUTHOR_EMAIL
            GIT_AUTHOR_DATE
    );
    my %precedence;
    @precedence{qw(bulk junk auto_reply)} = ();

    my $refreshed = 0;
    foreach my $msg (@unseen) {
        # Reset key environment variables
        delete $ENV{$_} for @EnvRelative;
        debug({indent=>1}, "Processing $msg");
        # Basic Accessors
        my $email = Email::Simple->new($imap->message_string($msg));
        # For handling multipart messages without fucking everything up
        my $mime  = Email::MIME->new($imap->message_string($msg));

        my $body = undef;
        my @parts = $mime->subparts;
        if( @parts ) {
            foreach my $part (@parts) {
                debug({color=>'magenta'},sprintf "Subpart discovered is %s", $part->content_type);
                if ($part->content_type =~ m[text/plain]) {
                    $body = $part->body_str;
                    last;
                }
                elsif($part->content_type =~ m[text/html]) {
                    my $dom = Mojo::DOM->new($part->body);
                    $body = $dom->all_text;
                }
            }
        }
        $body ||= $mime->body_str;
        debug({color=>'magenta'}, $body);

        my %headers = $email->header_pairs;

        next if exists $headers{'X-Autoreply-Sent-To'};          # Out Of Office
        next if exists $headers{'X-Autorespond'};                # Out Of Office
        next if exists $headers{Precedence} and exists $precedence{lc $headers{Precedence}};  # Out of Office
        # Get Date
        my $received_dt = DateTime::Format::Mail->parse_datetime($headers{Date});
        next unless $received_dt;

        $ENV{GIT_AUTHOR_DATE} = $received_dt->datetime();



( run in 0.710 second using v1.01-cache-2.11-cpan-39bf76dae61 )