From 2cb992fbfe59b9f468e31ebf5f1206cb284aaedb Mon Sep 17 00:00:00 2001 From: Aorimn <aorimn@gmail.com> Date: Sat, 14 Nov 2015 15:37:42 +0100 Subject: [PATCH] Ajout d'une gestion des sms pour les reminds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seule l'API de Free est supportée actuellement --- Modules/Remind.pm | 515 +++++++++++++++++++++++++++------------------- Modules/SMS.pm | 279 +++++++++++++++++++++++++ 2 files changed, 586 insertions(+), 208 deletions(-) create mode 100644 Modules/SMS.pm diff --git a/Modules/Remind.pm b/Modules/Remind.pm index 3078a67..6cc2926 100644 --- a/Modules/Remind.pm +++ b/Modules/Remind.pm @@ -4,6 +4,7 @@ use strict; use warnings; use POSIX; +use Storable; use Modules::Common; @@ -29,7 +30,8 @@ our %scheduled_events = (); # |---> "during" -------> "string" --> ""|period (string) # | `---------> "int" --> 0|time (int) # |---> "in or at" -----> "in"|"at" -> N <period>|HH:MM:DD -# `---> "message" ------> message +# |---> "message" ------> message +# `---> "sms" ----------> { "disabled" => 0|1 } # basic: # !remind me [every [<nb>] <period> [during [<nb>] <period>] {in <nb> <period>|[<day>] at <X>h[<Y>]} :? reminding message @@ -55,7 +57,7 @@ my %periods = %Common::periods; # ### # remind_main # Sert à rappeler un message à quelqu'un -# +# # Usage : # !remind me [every [<nb>] <period> [during [<nb>] <period>] {in <nb> <period>|[<day>] at <X>h[<Y>]} :? reminding message # !remind {add|sub} [-]<nb> <period> [to] <id> @@ -66,15 +68,15 @@ my %periods = %Common::periods; sub remind_main { my ($conn, $event, $reply_to, $ref_params) = @_; - + # Regarder l'usage pour mieux comprendre if(defined($ref_params->[0])) { # Le pseudo de celui à qui on doit rappeler my $concerned_nick = $event->{"nick"}; - + my $me = shift @{$ref_params}; - + # Si on veut qu'on oublie un remind if($me eq "forget") { @@ -96,44 +98,50 @@ sub remind_main { return 1 if(search_remind($conn, $event, $reply_to, $ref_params)); } - - + elsif($me eq "sms") + { + return 1 if(sms_remind($conn, $event, $reply_to, $ref_params)); + } + + if($me eq "me" && defined($ref_params->[0])) { my $sched_infos = &parse_string(join(' ', @{$ref_params})); - + if(ref $sched_infos eq "HASH" && %{$sched_infos} && defined($sched_infos->{"sleep time"}) && $sched_infos->{"sleep time"} > 0) { $sched_infos->{"params"} = $ref_params; $sched_infos->{"reply to"} = $reply_to; $sched_infos->{"to"}{"nick"} = $concerned_nick; - + ($sched_infos->{"to"}{"user"}, $sched_infos->{"to"}{"host"}) = split /\@/, $event->{"userhost"}; - + my $id = &get_next_id($event->{"userhost"}); $scheduled_events{$id} = $sched_infos; - + $id =~ s/^.* //; $sched_infos->{"id number"} = $id; - + &add_scheduled_event($conn, $sched_infos); - + my $random_affirmation = $Config::affirmative[int(rand($#Config::affirmative + 1))]; - $conn->privmsg($reply_to, "$random_affirmation \x0304[\x0303id #$id\x0304]\x03"); - + my $sched_event = $conn->parent->schedulequeue->queue->{$sched_infos->{"sched_id"}}; + my $when = "Next event on " . localtime($sched_event->time); + $conn->privmsg($reply_to, "$random_affirmation \x0304[\x0303id #$id\x0304]\x0302 " . $when . "."); + return 1; } elsif(ref($sched_infos) eq "") { $sched_infos .= " See `".$Config::command_sign."help remind` for more info"; $conn->privmsg($reply_to, $sched_infos); - + return 1; } } } - + $ref_params->[0] = 'remind'; Help::help_main($conn, $event, $reply_to, $ref_params); } # Fin remind_main @@ -146,23 +154,23 @@ sub remind_main sub parse_string { my $string = shift; - + my %sched_infos = ( "during" => { "string" => "", "int" => 0 } ); - + my $re_during = '(?: during (?<tps_during>\d+)?\s*(?<during>\w+))?'; my $re_every = '(?:every (?<tps_period>\d+)?\s*(?<period>[^ ]+)' . $re_during . ',? ?)?'; - + my $re_in = 'in (?:(?<in_hour>\d{1,2})h(?<in_mins>\d\d)|(?<time_period>\d+)\s*(?<in_period>\w+))'; my $re_at = '(?<day>[^ ]+ )?at (?<hour>\d{1,2})[h\'](?<minutes>\d{2})?'; my $re_in_at = '(?<in_or_at>' . $re_in . '|' . $re_at . ')?'; - + my $re_colon = '(?:\s*:\s*)?'; - + if($string =~ /^$re_every$re_in_at$re_colon\s+(?<msg>.*)/i) { my $tps_period = $+{'tps_period'}; @@ -178,24 +186,24 @@ sub parse_string my $hour = $+{'hour'}; my $minutes = $+{'minutes'}; my $msg = $+{'msg'}; - + # Première gestion des erreurs return "You have to define a 'every <period>', a 'in <some time>' or a 'at <hour>h'." if(!defined $every && !defined $in_or_at); - + $sched_infos{"first epoch"} = time; my $has_one_day = 0; - + # Gestion du "every" if(defined $every && (is_period($every) || is_day($every))) { $sched_infos{"period"}{"string"} = ''; $sched_infos{"period"}{"string"} = $tps_period if(defined($tps_period)); $sched_infos{"period"}{"string"} .= $every; - + $tps_period = defined $tps_period && $tps_period ne "" ? int $tps_period : 1; - - + + if(is_period($every)) { $sched_infos{"period"}{"int"} = $tps_period * &period_to_int($every); @@ -208,15 +216,15 @@ sub parse_string $sched_infos{"every"} = 1; $has_one_day++; } - + $sched_infos{"sleep time"} = $sched_infos{"period"}{"int"}; - + # Gestion du "during" if(defined($period_during) && is_period($period_during)) { $sched_infos{"during"}{"int"} = &period_to_int($period_during); $sched_infos{"during"}{"int"} *= int $tps_during if(defined($tps_during) && int $tps_during > 0); - + $sched_infos{"during"}{"string"} = $tps_during." " if(defined($tps_during)); $sched_infos{"during"}{"string"} .= $period_during; } @@ -228,22 +236,22 @@ sub parse_string $sched_infos{"period"}{"string"} = ""; $sched_infos{"sleep time"} = 0; } - + # Gestion du "in" ou du "at" if(defined($in_or_at) && $in_or_at =~ /^in/i) { if(defined($time_period)) { $sched_infos{"in or at"}{"in"} = "$time_period $in_period"; - + return "Period not recognized: '$in_period'." unless is_period($in_period); - + $sched_infos{"sleep time"} = $time_period * &period_to_int($in_period); } elsif(defined($in_hour)) { $sched_infos{"in or at"}{"in"} = "${in_hour}h${in_mins}"; - + $sched_infos{"sleep time"} = $in_hour * &period_to_int("hour"); $sched_infos{"sleep time"} += $in_mins * &period_to_int("min"); } @@ -256,15 +264,15 @@ sub parse_string { $minutes = defined $minutes && $minutes =~ /^\d+$/ ? int $minutes : 0; $sched_infos{"in or at"}{"at"} = $hour.":".$minutes; - + # On récupère l'heure qu'il est avec le jour, le mois et l'année my (undef,$min_now,$hour_now,$mday,$mon,$year,$wday) = localtime($sched_infos{"first epoch"}); - + my $diff_day = 0; if((defined($day) && $day !~ /^\s*$/) || defined($every)) { $day =~ s/\s*$// if(defined $day); - + if(defined $day) { if(&is_day($day)) @@ -285,16 +293,16 @@ sub parse_string $sched_infos{"in or at"}{"at"} .= ":".$every; } } - + if(!$diff_day) { # Si le remind est pour demain $diff_day++ if($hour < $hour_now || ($hour == $hour_now && $minutes <= $min_now)); } - + # On calcule le timestamp unix du moment où on va envoyer le message my $unixtime = mktime(0, $minutes, $hour, $mday+$diff_day, $mon, $year); - + # Et on fait la différence avec le timestamp de maintenant $sched_infos{"sleep time"} = $unixtime - $sched_infos{"first epoch"}; } @@ -302,28 +310,28 @@ sub parse_string { my (undef,$min_now,$hour_now) = localtime($sched_infos{"first epoch"}); $sched_infos{"in or at"}{"in"} = $hour_now.":".$min_now; - + if(defined $every && &is_day($every)) { $sched_infos{"in or at"}{"in"} .= ":".$every; } } - + # Permet de bloquer le "... every wednesday monday at ..." (par exemple) return "Can't have both \"$every\" and \"$day\"." if($has_one_day > 1); - + return "No message defined." if(!defined $msg); - + $msg =~ s/^\s*//; - + return "No message found." if($msg eq ""); - + $sched_infos{"message"} = $msg; $sched_infos{"first epoch"} = $sched_infos{"first epoch"} + $sched_infos{"sleep time"}; - + return \%sched_infos; } - + return "Can't parse the total string, you may have forgotten a word or two."; } # Fin parse_string @@ -335,10 +343,10 @@ sub parse_string sub get_next_id { my $from = shift; - + my $cpt = 0; $cpt++ while(defined($scheduled_events{$from." ".$cpt})); - + return $from." ".$cpt; } # Fin get_next_id @@ -350,7 +358,7 @@ sub get_next_id sub is_period { my $to_test = shift; - + return grep {$to_test =~ /^${_}s?$/i} keys %periods; } # Fin is_period @@ -363,11 +371,11 @@ sub is_day { my $to_test = shift; $to_test = lc $to_test; - + return 1 if($to_test =~ /^tomorrow$/i); - + $to_test = $convert_days{$to_test} if(defined $convert_days{$to_test}); - + return grep {$to_test =~ /^${_}s?$/i} @days; } # Fin is_day @@ -379,12 +387,12 @@ sub is_day sub period_to_int { my $period = shift; - + $period = lc $period; $period =~ s/s$//; - + return $periods{$period} if(defined $periods{$period}); - + return; } # Fin period_to_int @@ -396,40 +404,40 @@ sub period_to_int sub send_remind { my ($conn, $sched_infos, $loading) = @_; - + # On récupère l'endroit où on doit envoyer le message my $reply_to = $sched_infos->{"reply to"}; my $repnick = $sched_infos->{"to"}{"nick"}; - + my $on_chan = &is_on_chan($repnick, $reply_to); - + my $is_chan = (substr($reply_to, 0, 1) eq "#"); - + # On regarde si le mec est sur le chan, on le query sinon # NOTE : lorsqu'on a $loading, on n'a pas encore les chans, donc on fait # comme si le mec était sur le chan, on verra au prochain coup quoi... if(!$loading && $is_chan && $on_chan != Chans::CHANS_OK) { $::logger->debug("changing 'reply_to' from $reply_to to $repnick"); - + $reply_to = $repnick; $is_chan = 0; } - + # On récupère et construit le message à envoyer - my $msg = $is_chan ? "$repnick: " : ""; - $msg .= $sched_infos->{"message"}; - + my $msg .= $sched_infos->{"message"}; + $msg .= " \x0304[\x0303id #". $sched_infos->{"id number"} ."\x0304]\x03" if($sched_infos->{"every"}); - + # Et on envoie le message - $::displayer->sendto($reply_to, $msg); - + $::displayer->sendto($reply_to, ($is_chan ? "$repnick: " : "") . $msg); + maybe_send_sms($conn, $reply_to, $repnick, $msg, $sched_infos); + # On regarde s'il faut reprogrammer le message pour un futur envoi # ou s'il faut l'éliminer sans laisser de trace if( - $sched_infos->{"every"} && + $sched_infos->{"every"} && ($sched_infos->{"during"}{"int"} == 0 || time <= $sched_infos->{"first epoch"} + $sched_infos->{"during"}{"int"} - $sched_infos->{"period"}{"int"}) ) @@ -439,14 +447,14 @@ sub send_remind { $::displayer->sendto($reply_to, $msg); } - + # On reschedule le message &add_scheduled_event($conn, $sched_infos); return; } - + # On supprime tout ce foutoir - &remove_scheduled_event($conn, + &remove_scheduled_event($conn, $sched_infos->{"to"}{"user"}."\@" .$sched_infos->{"to"}{"host"} ." ".$sched_infos->{"id number"}); @@ -460,7 +468,7 @@ sub send_remind sub is_on_chan { my ($nick, $chan) = @_; - + return Chans::is_on_chan($chan, $nick); } # Fin is_on_chan @@ -472,15 +480,15 @@ sub is_on_chan sub reevaluate_sleep_time { my ($conn, $sched_infos, $loading) = @_; - + # On calcul une correction d'erreur # Le do_one_loop utilisé ne s'exécute pas exactement toutes les secondes - # Donc quand on commence à avoir du retard (ou le contraire, mais j'ai + # Donc quand on commence à avoir du retard (ou le contraire, mais j'ai # jamais vu), on va corriger ce retard en touchant le temps de schedule my $current_epoch = time; - + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday); - + if(defined($loading)) { ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($loading); @@ -488,13 +496,13 @@ sub reevaluate_sleep_time elsif(defined($sched_infos->{"every"})) { ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($current_epoch); - - if(defined($sched_infos->{"period"}{"string"}) && + + if(defined($sched_infos->{"period"}{"string"}) && $sched_infos->{"period"}{"string"} =~ /^(\d+)?\s*([^ ]+)\s*$/i) { my $multiplier = defined $1 && $1 ne "" ? $1 : 1; my $period = $2; - + if(is_period($period)) { if($period =~ /^m(?:in(?:ute)?s?)?$/i) @@ -524,7 +532,7 @@ sub reevaluate_sleep_time return "Can't parse 'every ".$sched_infos->{"period"}{"string"}."', deleting."; } } - + if(defined($sched_infos->{"in or at"}{"at"})) { $sec = 0; @@ -532,17 +540,17 @@ sub reevaluate_sleep_time else { my $diff_epoch = $current_epoch - $sched_infos->{"first epoch"}; - + my $sec_diff = $diff_epoch % $sched_infos->{"period"}{"int"}; - + $sec -= $sec_diff if($sec_diff != 0); } } - + my $next_epoch = mktime($sec, $min, $hour, $mday, $mon, $year); - + $sched_infos->{"sleep time"} = $next_epoch - $current_epoch; - + return; } # Fin reevaluate_sleep_time @@ -554,12 +562,12 @@ sub reevaluate_sleep_time sub log_remind { my ($conn, $sched_infos) = @_; - + my $user_host = $sched_infos->{"to"}{"user"}."\@".$sched_infos->{"to"}{"host"}; - + my $is_every = $sched_infos->{"every"} ? "yes -- every '".$sched_infos->{"period"}{"string"}."'" : "no"; my $is_during = $sched_infos->{"during"}{"int"} ? "yes -- during '".$sched_infos->{"during"}{"string"}."'" : "no"; - + $::logger->debug("New remind (".$user_host." ".$sched_infos->{"id number"}."):"); $::logger->debug(" ID: ".$sched_infos->{"sched_id"}); $::logger->debug(" Sleeping time: ".$sched_infos->{"sleep time"}); @@ -576,15 +584,15 @@ sub log_remind sub add_scheduled_event { my ($conn, $sched_infos) = @_; - + my $sched_id = $conn->{"cmdtool"}->schedule( $sched_infos->{"sleep time"}, \&send_remind, $sched_infos ); - + $sched_infos->{"sched_id"} = $sched_id; - + &log_remind($conn, $sched_infos); } # Fin add_scheduled_event @@ -598,7 +606,7 @@ sub remove_scheduled_event my ($conn, $id) = @_; my $msg = "Removing '$id' "; $msg .= " (".$scheduled_events{$id}->{"sched_id"}.")" if(defined($scheduled_events{$id}->{"sched_id"})); - + $::logger->debug($msg); delete $scheduled_events{$id}; } # Fin remove_scheduled_event @@ -611,21 +619,21 @@ sub remove_scheduled_event sub forget_remind { my ($self, $event, $reply_to, $ref_params) = @_; - + return 0 if(!defined($ref_params->[0])); - + my $from = $event->{"userhost"}; - - + + # Ça c'est pour si jamais il y a un petit malin qui fait chier, # on laisse la possibilité à l'owner de supprimer ses remind... if($ref_params->[0] eq "-user" && $event->{"nick"} eq $Config::owner) { shift @{$ref_params}; return 0 if(!(defined($ref_params->[0]) && defined($ref_params->[1]))); - + $from = shift @{$ref_params}; - + # Vérifications des paramètres if($from !~ /^[^@]+\@[^@]+$/) { @@ -633,8 +641,8 @@ sub forget_remind return 1; } } - - + + # Pour tous les id listés... my @del_ids = (); my @non_exist_ids = (); @@ -652,17 +660,17 @@ sub forget_remind # Si tout est bon, on supprime ! my $schqueue = $self->parent->schedulequeue; my $entry = $schqueue->queue->{$scheduled_events{$from." ".$id}->{"sched_id"}}; - + $schqueue->dequeue($entry); remove_scheduled_event($self, $from." ".$id); - + push @del_ids, $id; } } } - + my $reply = ""; - + if(@del_ids) { if(defined($del_ids[1])) @@ -674,7 +682,7 @@ sub forget_remind $reply = "Remind #".$del_ids[0]." forgotten. "; } } - + if(@non_exist_ids) { if(defined($non_exist_ids[1])) @@ -686,14 +694,14 @@ sub forget_remind $reply = "Remind #".$non_exist_ids[0]." doesn't exist for $from."; } } - + if(!@del_ids && !@non_exist_ids) { $reply = "Nothing to do for $from."; } - + $self->privmsg($reply_to, $reply); - + return 1; } # Fin forget_remind @@ -705,22 +713,22 @@ sub forget_remind sub see_remind { my ($self, $event, $reply_to, $ref_params) = @_; - + # Pour pouvoir construire l'id, on a besoin de savoir qui a demandé ça my $from = $event->{"from"}; $from =~ s/^[^!]+!//; - + if(defined($ref_params->[0])) { my $real_id_nb = $ref_params->[0] =~ /^#(.*)$/ ? $1: $ref_params->[0]; - + # Si l'user veut voir un remind en particulier if(defined($scheduled_events{$from." ".$real_id_nb})) { my $sched_id = $scheduled_events{$from." ".$real_id_nb}->{"sched_id"}; my $event = $self->parent->schedulequeue->queue->{$sched_id}; my $sched_infos = $event->content->{"args"}->[3]; - + # Et là, ça dépend du type de remind... if($sched_infos->{"every"}) { @@ -729,16 +737,16 @@ sub see_remind .$sched_infos->{"message"} ." \x0303every " .$sched_infos->{"period"}{"string"}; - + if($sched_infos->{"during"}{"int"}) { $reply .= " during ".$sched_infos->{"during"}{"string"}; } - + $reply .= "\x03 -- \x0302Next will be sent on " .localtime($event->time) ."\x0307}\x03 "; - + $self->privmsg($reply_to, $reply); } else @@ -748,17 +756,17 @@ sub see_remind .$sched_infos->{"message"} ." \x0302will be sent on ".localtime($event->time) ."\x0307}\x03 "; - + $self->privmsg($reply_to, $reply); } - + $::logger->debug("Looking at remind ".$from." ".$real_id_nb.", id $sched_id"); } else { $self->privmsg($reply_to, "No message for the id #".$real_id_nb); } - + return 1; } else @@ -766,7 +774,7 @@ sub see_remind # On répond en query $reply_to = $event->{"nick"}; my @reply = (); - + # Pour toutes les reminds foreach my $key (keys %scheduled_events) { @@ -776,10 +784,10 @@ sub see_remind my $id = $1; my $sched_id = $scheduled_events{$key}->{"sched_id"}; $::logger->debug("Looking at remind '".$from." ".$id."', id $sched_id"); - + my $event = $self->parent->schedulequeue->queue->{$sched_id}; my $sched_infos = $event->content->{"args"}->[3]; - + # Et là, ça dépend du type de remind... if($sched_infos->{"every"}) { @@ -788,17 +796,17 @@ sub see_remind .$sched_infos->{"message"} ." \x0303every " .$sched_infos->{"period"}{"string"}; - + if($sched_infos->{"during"}{"int"}) { $reply .= " during ".$sched_infos->{"during"}{"string"}; } - - + + $reply .= "\x03 -- \x0302Next will be sent on " .localtime($event->time) ."\x0307}\x03"; - + push @reply, $reply; } else @@ -810,17 +818,34 @@ sub see_remind } } } - + if(defined $reply[0]) { - @reply = sort @reply; + # On ne peut pas utiliser sort ici, sinon on se retrouve avec + # l'ordre suivant : + # {1 ... {10 ... {11 ... {2 ... {3 ... + # Avec ce sort, on a l'ordre suivant (remarquez la position des 10 + # et 11) : + # {1 ... {2 ... {3 ... {10 ... {11 ... + # + # Merci http://www.perlmonks.org/?node_id=442285 pour le coup... + my @reply = @reply[ + map { unpack "N", substr($_,-4) } + sort + map { + my $key = $reply[$_]; + $key =~ s[(\d+)][ pack "N", $1 ]ge; + $key . pack "N", $_ + } 0..$#reply + ]; + $self->privmsg($reply_to, join(' ', @reply)); } else { $self->privmsg($reply_to, "No remind found for ".$from); } - + return 1; } @@ -835,26 +860,26 @@ sub see_remind sub add_remind { my ($conn, $event, $reply_to, $ref_params) = @_; - + return 0 if(!defined($ref_params->[0])); - + my $s_params = join(' ', @{$ref_params}); $::logger->debug("Trying to add time to a remind : '$s_params'"); - + # Format attendu : <nb> <period> to <id> - if($s_params =~ /^([-]*)\s*(\d+)\s*(\w+)\s+to\s+#?(\d+)$/i) + if($s_params =~ /^(-*)\s*(\d+)\s*(\w+)\s+to\s+#?(\d+)$/i) { my $modif = $1; my $nb = $2; my $period = $3; my $id_nb = $4; - + # On regarde si l'id existe my $userhost = $event->{"userhost"}; if(defined($scheduled_events{$userhost . ' ' . $id_nb})) { my $sched_id = $scheduled_events{$userhost.' '.$id_nb}->{"sched_id"}; - + $period =~ s/s$//; if(defined($periods{$period})) { @@ -864,22 +889,28 @@ sub add_remind $to_add = -$to_add; $modif = substr($modif, 1); } - + $::logger->debug("Dequeuing remind (id: $sched_id)"); my $sched_event = $conn->parent->schedulequeue->dequeue($sched_id); my $sched_infos = $sched_event->content->{"args"}->[3]; - + # Ajustement du temps nécessaire pour scheduler $sched_infos->{"sleep time"} = $sched_event->time - time() + $to_add; - + &add_scheduled_event($conn, $sched_infos); - + $::logger->debug("Added"); - + $s_params =~ s/(\d+)$/#$1/ if($s_params !~ /#\d+$/i); - my $msg = "I've added $s_params, as you said. \x0302Next will be sent on " - . localtime($sched_event->time + $to_add) . "."; - + + my $when = "on " . localtime($sched_event->time + $to_add); + if($sched_infos->{"sleep time"} <= 1) + { + $when = "right now"; + } + + my $msg = "I've added $s_params, as you said. \x0302Next will be sent " . $when . "."; + $conn->privmsg($reply_to, $msg); } else @@ -893,12 +924,12 @@ sub add_remind $conn->privmsg($reply_to, "This id (#$id_nb) doesn't exist for $userhost"); $::logger->debug("id doesn't exist, aborting."); } - + return 1; } - + $::logger->debug("Wrong pattern, aborting."); - + return 0; } # Fin add_remind @@ -910,9 +941,9 @@ sub add_remind sub sub_remind { my ($conn, $event, $reply_to, $ref_params) = @_; - + return 0 if(!defined($ref_params->[0])); - + $ref_params->[0] = '-'.$ref_params->[0]; return add_remind($conn, $event, $reply_to, $ref_params); } # Fin sub_remind @@ -925,34 +956,34 @@ sub sub_remind sub search_remind { my ($conn, $event, $reply_to, $ref_params) = @_; - + return 0 if(!defined($ref_params->[0])); - + my $userhost = $event->{"userhost"}; my @matches_id = (); my $text = join(' ', @{$ref_params}); local $@ = undef; - + # On vérifie que $text est une regex valide if(!eval { qr/$text/ }) { $conn->privmsg($reply_to, "$text isn't a valid regex"); return 1; } - + foreach my $id (keys %scheduled_events) { my @id_fields = split / /, $id; if($id_fields[0] eq $userhost) { my $sched_infos = $scheduled_events{$id}; - + my $message = $sched_infos->{"message"}; - + push @matches_id, $id_fields[1] if($message =~ /$text/i); } } - + my $reply = ""; if(@matches_id) { @@ -972,12 +1003,70 @@ sub search_remind { $reply = "No match for pattern '$text'"; } - + $conn->privmsg($reply_to, $reply); - + return 1; } # Fin search_remind + +# ### +# sms_remind +# Gestion de l'envoi d'un SMS lorsqu'on a un remind +# ### +sub sms_remind +{ + my ($conn, $event, $reply_to, $ref_params) = @_; + + return 0 if(!defined($ref_params->[0]) || !defined($ref_params->[1])); + + my $state = shift @{$ref_params}; + return 0 unless($state =~ /^(en|dis)able$/i); + + my $state_nb = ($1 eq 'en' ? 0 : 1); + + my $userhost = $event->{"userhost"}; + my @chg_state_ids = (); + + foreach my $id_nb (@{$ref_params}) + { + if(defined($scheduled_events{$userhost . ' ' . $id_nb})) + { + $scheduled_events{$userhost . ' ' . $id_nb}->{'sms'} = { + 'disabled' => $state_nb + }; + push @chg_state_ids, $id_nb; + } + } + + my $not = ''; + $not = ' not' if($state_nb == 1); + + my $reply = "SMS will$not be sent for these ids: ".join(', ', @chg_state_ids); + + $conn->privmsg($reply_to, $reply); + + return 1; +} # sms_remind + + +# ### +# maybe_send_sms +# Voit si le module SMS est chargé et tente d'envoyer un SMS à l'utilisateur +# ### +sub maybe_send_sms +{ + my ($conn, $reply_to, $nick, $msg, $sched_infos) = @_; + + return if(!defined($INC{'SMS.pm'})); + + return if(defined($sched_infos->{'sms'}) and $sched_infos->{'sms'}->{'disabled'} == 1); + + my $sms = SMS::instance($conn); + $sms->try_send($reply_to, $nick, $msg); +} + + # ### # remind_help # Affiche l'aide de la commande '!remind' @@ -985,7 +1074,7 @@ sub search_remind sub remind_help { my ($self, $event, $reply_to, $ref_params) = @_; - + $self->privmsg($reply_to, "`".$Config::command_sign."remind me [every [<nb>] <period> [during [<nb>] <period>]] " ."{in <nb> <period>|[<day>] at <X>h[<Y>]} :? reminding message`" ." => remind you what you want"); @@ -994,7 +1083,8 @@ sub remind_help $self->privmsg($reply_to, "`".$Config::command_sign."remind see [<id>]` => see reminds"); $self->privmsg($reply_to, "`".$Config::command_sign."remind {add|sub} [-]<nb> <period> to <id>` => add/substract a period to a specific remind"); $self->privmsg($reply_to, "`".$Config::command_sign."remind search <text>` => search for <text> in reminds"); - + $self->privmsg($reply_to, "`".$Config::command_sign."remind sms {en|dis}able <id>...` => enable/disable sending SMS for the listed reminds' ids (enabled by default)"); + return 1; } # Fin remind_help @@ -1006,22 +1096,24 @@ sub remind_help sub remind_save { my ($conn, $folder, $reloading) = @_; - + return 1 if($reloading); - + my $file_remind = "/remind".$Config::suffixe.".sav"; - + +# store \%scheduled_events, $file_remind; + open REMINDS, ">", $folder.$file_remind or return 0; - + # Les évènements prévus actuellement my $queue = $conn->parent->schedulequeue->queue; - + foreach my $remind_id (keys %scheduled_events) { my $entry = $queue->{$scheduled_events{$remind_id}->{"sched_id"}}; my $sched_infos = $entry->content->{"args"}->[3]; my $time = $entry->time; - + print REMINDS ";$remind_id;$time\n"; print REMINDS join(' ', @{$sched_infos->{"params"}})."\n"; print REMINDS $sched_infos->{"reply to"}."\n"; @@ -1035,7 +1127,7 @@ sub remind_save print REMINDS $sched_infos->{"during"}{"string"}."\n"; print REMINDS $sched_infos->{"during"}{"int"}."\n"; print REMINDS $sched_infos->{"sleep time"}."\n"; - + if(defined($sched_infos->{"in or at"}{"in"})) { print REMINDS "in:".$sched_infos->{"in or at"}{"in"}."\n"; @@ -1044,12 +1136,13 @@ sub remind_save { print REMINDS "at:".$sched_infos->{"in or at"}{"at"}."\n"; } - + print REMINDS $sched_infos->{"message"}."\n"; + print REMINDS 'sms:' . $sched_infos->{"sms"}->{"disabled"}."\n"; } - + close REMINDS; - + return 1; } # Fin remind_save @@ -1061,100 +1154,106 @@ sub remind_save sub remind_load { my ($conn, $folder, $reloading) = @_; - + if($reloading) { &load_from_schedulequeue($conn); return 1; } - + my $file_remind = "/remind".$Config::suffixe.".sav"; - + open REMINDS, "<", $folder.$file_remind or return 0; - + my $current_time = time; - + while(my $line = <REMINDS>) { chomp $line; - + my ($undefined, $remind_id, $time) = split /;/, $line; - + next if(defined $undefined && $undefined ne ""); - + my %sched_infos = (); - + $line = <REMINDS>; chomp $line; my @params = split / /, $line; $sched_infos{"params"} = \@params; - + $line = <REMINDS>; chomp $line; $sched_infos{"reply to"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"to"}{"nick"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"to"}{"user"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"to"}{"host"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"first epoch"} = int $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"every"} = int $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"period"}{"string"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"period"}{"int"} = int $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"during"}{"string"} = $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"during"}{"int"} = int $line; - + $line = <REMINDS>; chomp $line; $sched_infos{"sleep time"} = int $line; - + $line = <REMINDS>; chomp $line; my ($in_or_at, $value) = split /:/, $line; $sched_infos{"in or at"}{$in_or_at} = $value; - + $line = <REMINDS>; chomp $line; $sched_infos{"message"} = $line; next if(!defined($line) || $line =~ /^\s*$/); - - + + $line = <REMINDS>; + chomp $line; + $line =~ /^sms:(.*)$/; + $1 = 0 unless defined $1 and $1 ne ''; + $sched_infos{"sms"} = { "disabled" => int($1) }; + + $scheduled_events{$remind_id} = \%sched_infos; - + $remind_id =~ s/^.* //; $sched_infos{"id number"} = $remind_id; - + if(int($time) < $current_time) { # Si le remind devrait déjà être envoyé - + $time = int $time; - + if($sched_infos{"every"}) { while($time < $current_time) @@ -1162,21 +1261,21 @@ sub remind_load $time += $sched_infos{"period"}{"int"}; } } - + &send_remind($conn, \%sched_infos, $time); # NOTE ça va ralentir le démarrage... } else { # Si le remind doit être schedulé - + # On prend en compte le changement d'heure et le temps d'extinction du bot &reevaluate_sleep_time($conn, \%sched_infos, $time); - + &add_scheduled_event($conn, \%sched_infos); } } - + close REMINDS; } # Fin remind_load @@ -1188,24 +1287,24 @@ sub remind_load sub load_from_schedulequeue { my $conn = shift; - + my $queue = $conn->parent->schedulequeue->queue; - + foreach my $entry_id (keys %{$queue}) { my $entry = $queue->{$entry_id}; - + if(defined($entry->content->{"args"}->[3])) { # S'il y a un 3è argument, c'est peut-être un remind my $may_sched_infos = $entry->content->{"args"}->[3]; - + # On vérifie donc que c'en est bien un if(defined($may_sched_infos->{"every"}) && defined($may_sched_infos->{"first epoch"})) { my $id = $may_sched_infos->{"to"}{"user"}."\@".$may_sched_infos->{"to"}{"host"}; $id .= " ".$may_sched_infos->{"id number"}; - + $scheduled_events{$id} = $may_sched_infos; } } diff --git a/Modules/SMS.pm b/Modules/SMS.pm new file mode 100644 index 0000000..bf9ba9b --- /dev/null +++ b/Modules/SMS.pm @@ -0,0 +1,279 @@ +package SMS; # command = sms + +use strict; +use warnings; + +use URI::Escape; +use Common; +use Storable; + + + +our $sms_instance; + + +# ### +# sms_main +# Envoi des SMS +# +# Usage : +# !sms register <user> <pass> +# !sms forget +# ### +sub sms_main +{ + my ($conn, $event, $reply_to, $ref_params) = @_; + + # Regarder l'usage pour mieux comprendre + if(defined($ref_params->[0])) + { + # Le pseudo de celui qui vient d'invoquer cette commande + my $concerned_nick = $event->{"nick"}; + + my $cmd = shift @{$ref_params}; + + # Si on veut qu'on oublie un remind + if($cmd eq "forget") + { + my $sms = SMS::instance($conn); + $sms->forget($concerned_nick); + $::displayer->sendto( + $reply_to, + "Your information has been forgotten." + ); + return 1; + } + elsif($cmd eq "register") + { + if($reply_to =~ /^#/) + { + $::displayer->sendto( + $reply_to, + "I won't take this into account, please register in query." + ); + return 1; + } + + if(defined($ref_params->[1])) + { + my $sms = SMS::instance($conn); + $sms->register( + $concerned_nick, + $ref_params->[0], + $ref_params->[1] + ); + $::displayer->sendto( + $reply_to, + "You're now entitled to receive notifications through SMS." + ); + return 1; + } + } + } + + $ref_params->[0] = 'sms'; + Help::help_main($conn, $event, $reply_to, $ref_params); +} # Fin sample_main + + +sub instance +{ + return $sms_instance if defined($sms_instance); + $sms_instance = new SMS(@_); + return $sms_instance; +} + + +sub new +{ + my $class = shift; + my $self = { + _conn => shift, + _registered => {} + # format: + # nick1 -> 'user' -> 12345 + # `--> 'pass' -> 'Blah' + # ... + }; + + bless $self, $class; + return $self; +} + + +# ### +# register +# Enregistre un utilisateur pour l'utilisation des SMS +# ### +sub register +{ + my ($self, $nick, $user, $pass) = @_; + + $self->{'_registered'}->{lc $nick} = { + 'user' => $user, + 'pass' => $pass + }; +} + + +# ### +# forget +# Enlève les données relatives à l'utilisateur +# ### +sub forget +{ + my ($self, $nick) = @_; + if(defined($self->{'_registered'}->{lc $nick})) + { + delete $self->{'_registered'}->{lc $nick}; + } +} + + +# ### +# try_send +# Tente d'envoyer un SMS au nick +# ### +sub try_send +{ + my ($self, $reply_to, $to, $msg) = @_; + + $to = lc $to; + + return unless defined($self->{'_registered'}->{$to}); + + my $user = $self->{'_registered'}->{$to}->{'user'}; + my $pass = $self->{'_registered'}->{$to}->{'pass'}; + my $escm = uri_escape(Common::strip_colors($msg)); + + my $htmlreq = new HTMLRequests($self->{'_conn'}); + + my $url = "https://smsapi.free-mobile.fr/sendmsg?user=$user&pass=$pass&msg=$escm"; + my $method = "GET"; + my $raw_result = 0; + my $callback = \&api_response; + + my %params = ( + 'conn' => $self->{'_conn'}, + 'reply_to' => $reply_to, + 'msg' => $msg, + 'url' => $url + ); + + $htmlreq->wget($url, $method, undef, $raw_result, $callback, \%params); +} + + +# ### +# api_response +# Répond en fonction du retour de l'API. Actuellement, l'API est définie comme +# telle : +# """ +# Le code de retour HTTP indique le succès ou non de l'opération : +# 200 : Le SMS a été envoyé sur votre mobile. +# 400 : Un des paramètres obligatoires est manquant. +# 402 : Trop de SMS ont été envoyés en trop peu de temps. +# 403 : Le service n'est pas activé sur l'espace abonné, ou login / clé incorrect. +# 500 : Erreur côté serveur. Veuillez réessayer ultérieurement. +# """ +# ### +sub api_response +{ + my ($conn, $status, $buffer, $params) = @_; + + my $reply_to = $params->{'reply_to'}; + my $msg = $params->{'msg'}; + my $url = $params->{'url'}; + + if($status == 200) + { + $::displayer->sendto($reply_to, "SMS sent with message '$msg'"); + $::logger->debug("SMS sent using $url"); + } + elsif($status == 400) + { + $::displayer->sendto($reply_to, "SMS module error"); + $::logger->debug("Received status 400 by sending $url"); + } + elsif($status == 402) + { + $::displayer->sendto($reply_to, "Too many SMS sent"); + $::logger->debug("Received status 402 by sending $url"); + } + elsif($status == 403) + { + $::displayer->sendto($reply_to, "SMS notifications not active or wrong user/pass"); + $::logger->debug("Received status 403 by sending $url"); + } + elsif($status == 500) + { + $::displayer->sendto($reply_to, "SMS server error"); + $::logger->debug("Received status 500 by sending $url"); + } + else + { + $::displayer->sendto($reply_to, "SMS module error"); + $::logger->debug("Received status '$status' by sending $url"); + $::logger->debug("Content: '$buffer'"); + } +} + + +sub sms_help +{ + my ($conn, $event, $reply_to, $ref_params) = @_; + + shift @{$ref_params}; + + $conn->privmsg($reply_to, "`".$Config::command_sign."sms register <user> <pass>` =>". + " Register the current nick to receive SMS for each remind using the provided credentials"); + $conn->privmsg($reply_to, "`".$Config::command_sign."sms forget` =>". + " Forget the credentials for the current user"); + $conn->privmsg($reply_to, "This module currently uses the Free API (see https://mobile.free.fr/moncompte/index.php?page=options, 'Notifications par SMS')"); + + return 1; +} # Fin sms_help + + +sub sms_save +{ + my ($conn, $folder, $reloading) = @_; + + my $sms = SMS::instance($conn); + store $sms->{'_registered'}, $folder."/sms".$Config::suffixe.".sav"; + + return 1; +} # Fin sms_save + + +sub sms_load +{ + my ($conn, $folder, $reloading) = @_; + + my $sms_file = $folder."/sms".$Config::suffixe.".sav"; + if(-f $sms_file and not $sms_instance) + { + $sms_instance = SMS::instance($conn); + $sms_instance->{'_registered'} = retrieve $sms_file; + } + + return 1; +} # Fin sms_load + + + +sub print_debug +{ + my ($conn, $reply_to, $ref_params) = @_; + + print "]=====================[ Debug de SMS ]=====================[\n"; + my $sms = SMS::instance($conn); + require Data::Dumper; + print Data::Dumper->Dump([$sms->{'_registered'}], [qw(sms_registered)]); + print "]=====================[ SMS fin debug ]=====================[\n"; +} # Fin print_debug + + +1; + +__END__ -- GitLab