#!/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') { s/\@\(\s*(\w+)\s*,\s*\w+\s*\)/$1/g; } else { s/\@\(\s*\w+\s*,\s*(\w+)\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]"); } 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})) { &massive_send_sms(\%$msg); } }, $cfg::user{"message pour un destinataire"} => sub { $outbox_sms_queue->enqueue([$1, $msg->{Content}]); }, $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} = ''; my @results = sql_request("SELECT * FROM " . $cfg::config{table} . " WHERE phone = '$msg->{Phone}'"); my $number_of_candidates = @results; $number_of_candidates == 0 and return; $msg->{PhoneOwner} = '('; foreach (@results) { $msg->{PhoneOwner} .= "$_->{firstname} $_->{lastname}"; --$number_of_candidates > 0 and $msg->{PhoneOwner} .= ' ou '; $msg->{Email} = $_->{email}; } $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"); } } }