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