apps / sms / bot /
dbf2043 6 months ago
3 contributor
358 lines | 12.697kb
#!/usr/bin/perl

use strict;
use warnings;

use DBI;
use POSIX qw/ceil strftime/;
use HiPi::Huawei::E3531;
use HiPi::Huawei::Errors;

use open ':utf8';
use open ':std';
use utf8;

#use Data::Dumper;

use threads;
use threads::shared;
use Thread::Queue;
use Thread::Semaphore;

my $scriptconf = $0;
$scriptconf =~ s/\.pl$//;
$scriptconf =~ s/$/.conf/;
if (-r $scriptconf) {
    package cfg;
    unless (my $return = do $scriptconf) {
        warn "couldn't parse $scriptconf: $@" if $@;
        warn "couldn't do $scriptconf: $!"    unless defined $return;
        warn "couldn't run $scriptconf"       unless $return;
    }
}
else {
    print "pas de config\n";
    exit;
}

$SIG{INT}=\&terminate;

my $inbox_sms_queue = Thread::Queue->new;
my $outbox_sms_queue = Thread::Queue->new;
my $mail_queue = Thread::Queue->new;

my $mutex = Thread::Semaphore->new();
STDERR->autoflush();

my $mail = threads->new(\&send_mail);

my $inbox_sms = threads->new(\&inbox_sms_parse);

my $outbox_sms = threads->new(\&hilink_send_sms);

my $hlink = HiPi::Huawei::E3531->new();

sub log_bot {
    $mutex->down();
    print STDERR sprintf("%s\n", shift);
    $mutex->up();
}

sub terminate () {
    log_bot($mail_queue->pending() . " mails en attente d'envoi !") if ($mail_queue->pending() > 0);
    $mail->exit();
    log_bot($inbox_sms_queue->pending() . " SMS en attente de traitement !") if ($inbox_sms_queue->pending() > 0);
    $inbox_sms->exit();
    log_bot($outbox_sms_queue->pending() . " SMS en attente d'envoi !") if ($outbox_sms_queue->pending() > 0);
    $outbox_sms->exit();
    log_bot("arrêt");
    exit 0;
}

sub sql_request ($) {
    my $msg = shift;
    my @result;
    my $dbh = DBI->connect($cfg::config{db}->{driver}, $cfg::config{db}->{user}, $cfg::config{db}->{password}, {'RaiseError' => 1});
    $dbh->{'mysql_enable_utf8'} = 1;
    $dbh->do(qq{SET NAMES "utf8"});
    my $sth = $dbh->prepare($msg);
    $sth->execute();
    if (defined($sth->{NUM_OF_FIELDS})) {
        while (my $ref = $sth->fetchrow_hashref()) {
            push @result, $ref;
        }
    }
    $sth->finish();
    $dbh->disconnect();
    return @result;
}

sub massive_send_sms {
    my $msg = shift;
    if ($outbox_sms_queue->pending() > 0) {
        $outbox_sms_queue->insert(0, [$msg->{Phone}, "un envoi massif est déjà en cours, lancé par " . $cfg::config{last_sender} . ", reste " . $outbox_sms_queue->pending() . ", annulation"]);
        return;
    }
    $cfg::config{last_sender} = $msg->{PhoneOwner};
    my @results = sql_request("SELECT phone, firstname, gender FROM " . $cfg::config{table} . " WHERE " . $cfg::config{group_prefix} . $msg->{groupe} . " > '0'");
    my $qty = scalar(@results);
    my $start_msg = "envoi de " . $qty . " SMS (fin prévue entre ". strftime("%H:%M", localtime(time() + $qty * 60)) . " et " . strftime("%H:%M", localtime(time() + $qty * 90)) . ")";
    $outbox_sms_queue->enqueue([$msg->{Phone}, $start_msg]);
    foreach my $contact (@results) {
        $contact->{phone} =~ s/[\s\.]//g;
        $_ = $msg->{Content};
        s/\@prénom/$contact->{firstname}/g;
        if ($contact->{gender} eq 'F') { # @(Féminin,Masculin)
            s/@\(([^,]+),[^\)]+\)/$1/g;
        }
        else {
            s/@\([^,]+,([^\)]+)\)/$1/g;
        }
        $outbox_sms_queue->enqueue([$contact->{phone}, $_])
    }
    $outbox_sms_queue->enqueue([$msg->{Phone}, "envoi des SMS terminé !"]);
}

sub send_mail {
    while (1) {
        my $msg = $mail_queue->dequeue_timed(10, 1);
        next if ! defined $msg;
        defined($msg->{to}) or $msg->{to} = $cfg::config{mail};
        open(MAIL, "|msmtp $msg->{to}");
        print MAIL "Subject: $msg->{Subject}\n";
        print MAIL "Reply-To: $msg->{Email}\n" if ($msg->{Email} ne '');

        print MAIL "$msg->{Content}\n";
        close(MAIL);
        log_bot("mail envoyé à $msg->{to}");
    }
}

sub wait_open_time ($$) {
    my ($close_hour, $open_hour) = @_;
    my @lt = localtime;
    while ($lt[2] >= $close_hour or $lt[2] <= $open_hour) {
        sleep 1800;
    }
}

sub hilink_send_sms {
    my $sendbox = HiPi::Huawei::E3531->new();
    while (1) {
        my $sms = $outbox_sms_queue->dequeue_timed(10, 1);
        next if ! defined $sms;

        if ($sendbox->{code}) {
            log_bot('sendbox new: ' . HiPi::Huawei::Errors->get_error_message($sendbox->{code}));
        }
        else {
            my $reponse = $sendbox->send_sms($$sms[0], , $$sms[1]);
            if ($reponse->{code}) {
                log_bot('send_sms: ' . HiPi::Huawei::Errors->get_error_message($reponse->{code}));
            }

            log_bot("envoi à $$sms[0] : $$sms[1]");
        }
        if ($$sms[1] eq 'envoi des SMS terminé !') {
            unlink('/dev/shm/smsbot.envoi_en_cours');
        }
        sleep 60 + int(rand(30));
    }
    log_bot('fin du thread outbox_sms');
}

sub is_authorized {
    my ($msg) = @_;
    my @results = sql_request("SELECT * FROM $cfg::config{table} WHERE phone = '$msg->{Phone}'");
    if (scalar(@results) == 1) {
        my $_results = $results[0];
        foreach my $column (keys(%$_results)) {
            if ($column =~ /^$cfg::config{group_prefix}/) {
                if ($results[0]->{$column} == 2) {
                    $msg->{address} = $results[0]->{address};
                    return 1; # true
                }
            }
        }
        return 0; # false
    }
    else {
        return 0; # false
    }
}

sub authorized_on_table {
    my %msg = @_;
    my $table_name = $cfg::config{group_prefix} . $msg{groupe};
    my @results = sql_request("SELECT * FROM $cfg::config{table} WHERE phone = '$msg{id}'");
    if (scalar(@results) == 1) {
        if (! defined($results[0]->{$table_name})) {
            $msg{error} = "le groupe $msg{groupe} n'existe pas";
            $outbox_sms_queue->insert(0, [$msg{id}, $msg{error}]);
            return 0; # false
        }
    }
    @results = sql_request("SELECT $table_name FROM $cfg::config{table} WHERE phone = '$msg{id}'");
    if (scalar(@results) == 1) {
        if ($results[0]->{$table_name} != 2) {
            $msg{error} = "désolé, écrire au groupe $msg{groupe} n'est pas autorisé pour toi";
            $outbox_sms_queue->insert(0, [$msg{id}, $msg{error}]);
            return 0; # false
        }
        else {
            return 1; # true
        }
    }
}

sub react_on_message {
    my ($hashref , $msg) = @_;
    for my $regex (keys(%$hashref)) {
        if ($msg->{Content} =~ m/$regex/i) {
            $msg->{Content} =~ s/$regex//i;
            $hashref->{$regex}();
            return 1; # true
        }
    }
    return 0; # false
}

sub inbox_sms_parse {
    while (1) {
        my $msg = $inbox_sms_queue->dequeue_timed(10, 1);
        next if ! defined $msg;

        my %part_from_user = (
            $cfg::user{"message de groupe"} => sub {
                $msg->{groupe} = lc $1;
                if (authorized_on_table(groupe => $msg->{groupe}, id => $msg->{Phone})) {
                    open my $fh, '>', '/dev/shm/smsbot.envoi_en_cours';
                    close $fh;
                    &massive_send_sms(\%$msg);
                }
            },
            $cfg::user{"message pour un destinataire"} => sub {
                $outbox_sms_queue->enqueue([$1, $msg->{Content}]);
            },
            $cfg::user{"supervision"} => sub {
                if ($1 eq 'réception') {
                    $outbox_sms_queue->insert(0, [$msg->{Phone}, "supervision envoi"]);
                    open my $fh, '>', '/dev/shm/smsbot.reception.ok';
                    close $fh;
                }
                elsif ($1 eq 'envoi') {
                    open my $fh, '>', '/dev/shm/smsbot.envoi.ok';
                    close $fh;
                }
            },
            $cfg::user{"ping"} => sub {
                my $envoi_en_cours = '';
                if ($outbox_sms_queue->pending() > 0) {
#                    for (my $queue_id = $outbox_sms_queue->pending(); $queue_id >= 0; $queue_id--) {
#                        log_bot(Dumper($outbox_sms_queue->peek($queue_id)));
#                    }
                    $envoi_en_cours = "\nenvoi en cours de traitement (reste " . $outbox_sms_queue->pending() . ") ";
                    $envoi_en_cours .= "par $cfg::config{last_sender}" if defined($cfg::config{last_sender});
                }
                log_bot("envoi d'un pong à $msg->{Phone} $msg->{PhoneOwner} $envoi_en_cours");
                $outbox_sms_queue->insert(0, [$msg->{Phone}, "pong" . $envoi_en_cours]);
            },
        );

        $msg->{Phone} =~ s/^\+33/0/;
        $msg->{Email} = '';
        $msg->{PhoneOwner} = '';

        my @results = sql_request("SELECT * FROM " . $cfg::config{table} . " WHERE phone = '$msg->{Phone}'");
        my $number_of_candidates = @results;
        if ($number_of_candidates > 0) {
            foreach (@results) {
                $msg->{PhoneOwner} .= "$_->{firstname} $_->{lastname}";
                --$number_of_candidates > 0 and $msg->{PhoneOwner} .= ' ou ';
                $msg->{Email} = $_->{email};
            }
            $msg->{PhoneOwner} = ' (' . $msg->{PhoneOwner} . ')';
        }
        $msg->{Subject} = "SMS recu de $msg->{Phone}$msg->{PhoneOwner}";

        if (defined $msg->{Phone} and !(is_authorized(\%$msg) and react_on_message(\%part_from_user, \%$msg))) {
            log_bot("message de $msg->{Phone}$msg->{PhoneOwner}");
            $mail_queue->enqueue(\%$msg);
        }

        undef $msg;
    }
}

$inbox_sms->detach;    # gère la file des événements produits par GTalkSMS
log_bot("inbox thread ok");
$outbox_sms->detach;   # gère la file des envois de SMS par GTalkSMS
log_bot("outbox thread ok");
$mail->detach;         # gère la file des mails
log_bot("mail thread ok");

my $loop = 0;
log_bot("robot prêt");
while ( 1 ) {

    $loop++;

    my $notifications = $hlink->check_notifications();
    if ($notifications->{code}) {
        log_bot('check_notifications: ' . HiPi::Huawei::Errors->get_error_message($notifications->{code}));
    }
#TODO    elsif ($notifications->{OnlineUpdateStatus} != 10) {
#TODO        trouver les significations
#TODO    }
    elsif ($notifications->{UnreadMessage}) {
        my $inbox = $hlink->get_inbox();
        if ($inbox->{code}) {
            log_bot('get_inbox: ' . HiPi::Huawei::Errors->get_error_message($inbox->{code}));
        }
        elsif (defined $inbox->{Count} and $inbox->{Count} > 0) {
#            log_bot($inbox->{Count} . " messages dans inbox");
            for (my $i = 0; $i < $inbox->{Count}; $i++) {
#                log_bot("id " . $inbox->{Messages}[$i]->{Index} . ", status: " . $inbox->{Messages}[$i]->{Smstat});
                if ($inbox->{Messages}[$i]->{Smstat}) { # message lu
                    my $delete = $hlink->delete_sms($inbox->{Messages}[$i]->{Index});
                    if ($delete->{code}) {
                        log_bot('delete_sms: ' . HiPi::Huawei::Errors->get_error_message($delete->{code}));
                    }
#                    log_bot("id " . $inbox->{Messages}[$i]->{Index} . " deleted");
                }
                else {
                    $inbox_sms_queue->enqueue($inbox->{Messages}[$i]);
#                    log_bot("id " . $inbox->{Messages}[$i]->{Index} . " enqueued");
                    my $read = $hlink->set_sms_read($inbox->{Messages}[$i]->{Index});
                    if ($read->{code}) {
                        log_bot('set_sms_read: ' . HiPi::Huawei::Errors->get_error_message($read->{code}));
                    }
#                    log_bot("id " . $inbox->{Messages}[$i]->{Index} . " marked as read") if defined $inbox->{Messages}[$i]->{Index};
                }
            }
        }
    }

#on vérifie toutes les 30 secondes
    sleep 30;
    log_bot('outbox_sms not running') unless ($outbox_sms->is_running());
    next if ($loop % 20);
    $loop = 0;

# nettoyage des envoyés toutes les 10 minutes
    my $outbox = $hlink->get_outbox();
    if ($outbox->{code}) {
        log_bot('get_outbox: ' . HiPi::Huawei::Errors->get_error_message($outbox->{code}));
    }
    elsif (defined $outbox->{Count} and $outbox->{Count} > 1) {
#        log_bot($outbox->{Count} . " messages dans outbox");
# on conserve le dernier envoyé
        for (my $i = 1; $i < $outbox->{Count}; $i++) {
            my $delete = $hlink->delete_sms($outbox->{Messages}[$i]->{Index});
            if ($delete->{code}) {
                log_bot('delete_sms: ' . HiPi::Huawei::Errors->get_error_message($delete->{code}));
            }
#            log_bot("id " . $outbox->{Messages}[$i]->{Index} . " deleted");
        }
    }
}