3 contributor
#!/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");
}
}
}