From eb7cef7bb8f0053ea5e3fb3a886826d0133ec0ab Mon Sep 17 00:00:00 2001
From: TC <tc.arise@giboulees.net>
Date: Sun, 22 Jun 2014 15:51:21 +0000
Subject: [PATCH] Add vote module

New module to propose a yes-or-no vote.
This still needs a timer for memory cleaning purpose.
---
 Modules/Vote.pm | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 461 insertions(+)
 create mode 100644 Modules/Vote.pm

diff --git a/Modules/Vote.pm b/Modules/Vote.pm
new file mode 100644
index 0000000..801e227
--- /dev/null
+++ b/Modules/Vote.pm
@@ -0,0 +1,461 @@
+package Vote; # command = vote
+
+use strict;
+use warnings;
+
+use Storable;
+
+
+
+# Tableau des votes
+# On les stocke de telle manière :
+# "#chan id" --> "chan" --------> chan/reply name
+#         |----> "id" ----------> vote id
+#         |----> "added date" --> timestamp
+#         |----> "question" ----> yes/no question to answer
+#         |----> "voters" ------> [user@host voters]
+#         |----> "results" -----> ("yes" => nb, "no" => nb)
+#         `----> "author" ------> user@host
+# ...
+our %votes = ();
+
+# Tableau des derniers votes ajoutés
+# Format :
+# "chan1" --> dernier vote id
+# ...
+our %last_added = ();
+
+
+
+
+# ###
+#  vote_main
+# Permet de gérer des votes
+# 
+# Usage :
+#  !vote new <question>
+#  !vote del <id>
+#  !vote list [#chan]
+#  !vote see <id>
+#  !vote [<id>] {yes|no}
+# ###
+sub vote_main
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	# Don't use in query, only on chans
+	unless(substr($reply_to, 0, 1) eq '#')
+	{
+		$::displayer->sendto($reply_to, "Can't operate in query, sorry.");
+		return 1;
+	}
+	
+	if(defined($ref_params->[0]) && $ref_params->[0])
+	{
+		if($ref_params->[0] eq 'new')
+		{
+			return 1 if(new_vote($conn, $event, $reply_to, $ref_params));
+		}
+		elsif($ref_params->[0] =~ m'del(ete)?|remove')
+		{
+			return 1 if(del_vote($conn, $event, $reply_to, $ref_params));
+		}
+		elsif($ref_params->[0] eq 'list')
+		{
+			return 1 if(list_votes($conn, $event, $reply_to, $ref_params));
+		}
+		elsif($ref_params->[0] eq 'see')
+		{
+			return 1 if(see_vote($conn, $event, $reply_to, $ref_params));
+		}
+		else
+		{
+			return 1 if(vote($conn, $event, $reply_to, $ref_params));
+		}
+	}
+	
+	$ref_params->[0] = 'vote';
+	Help::help_main($conn, $event, $reply_to, $ref_params);
+} # Fin vote_main
+
+
+# ###
+#  new_vote
+# Enregistre un nouveau vote
+# ###
+sub new_vote
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	# On enlève le 'new'
+	shift @{$ref_params};
+	
+	unless(defined($ref_params->[0]) && $ref_params->[0])
+	{
+		$::displayer->sendto($reply_to, "Didn't find your question.");
+		return 0;
+	}
+	
+	my $lc_reply_to = lc $reply_to;
+	my $id = get_next_id($lc_reply_to);
+	my (undef, $vote_id) = split / +/, $id;
+	my $question = join(' ', @{$ref_params});
+	my $full_user = (lc $event->{'nick'}).'!'.(lc $event->{'userhost'});
+	
+	my %new_vote = (
+		'chan'       => $lc_reply_to,
+		'id'         => $vote_id,
+		'added date' => time(),
+		'question'   => $question,
+		'voters'     => [],
+		'results'    => {
+			'yes' => 0,
+			'no'  => 0
+		},
+		'author'     => $full_user
+	);
+	
+	$votes{$id} = \%new_vote;
+	$last_added{$lc_reply_to} = $vote_id;
+	
+	my $add_date = localtime($votes{$id}->{'added date'});
+	$::displayer->sendto($reply_to, "Vote \x0304[\x0303id #$vote_id\x0304]\x03 added on $add_date");
+	$::logger->info("New vote: '$question', id='$id', added by $full_user");
+	
+	return 1;
+} # Fin new_vote
+
+
+# ###
+#  del_vote
+# Supprime un vote
+# ###
+sub del_vote
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	# On enlève le 'del' ou autre
+	shift @{$ref_params};
+	
+	unless(defined($ref_params->[0]))
+	{
+		$::displayer->sendto($reply_to, "Didn't find the id.");
+		return 0;
+	}
+	
+	my $lc_reply_to = lc $reply_to;
+	my $vote_id = $ref_params->[0];
+	if($ref_params->[0] =~ /^#?(\d+)$/)
+	{
+		$vote_id = $1;
+	}
+	my $id = $lc_reply_to.' '.$vote_id;
+	my $full_user = (lc $event->{'nick'}).'!'.(lc $event->{'userhost'});
+	
+	# On vérifie que c'est bien l'auteur qui demande la suppression du vote
+	if(defined($votes{$id}) && $votes{$id}->{'author'} eq $full_user)
+	{
+		return real_del_vote($id, $lc_reply_to);
+	}
+	
+	$::displayer->sendto($reply_to, "Can't find this vote on this chan or you're not authorized.");
+	return 1;
+} # Fin del_vote
+
+
+# ###
+#  real_del_vote
+# S'occupe de vraiment supprimer le vote et d'émettre le résultat à l'utilisateur
+# ###
+sub real_del_vote
+{
+	my ($id, $reply_to) = @_;
+	
+	my $question = $votes{$id}->{'question'};
+	my $yes = $votes{$id}->{'results'}->{'yes'};
+	my $no  = $votes{$id}->{'results'}->{'no'};
+	
+	delete $votes{$id};
+	$::displayer->sendto($reply_to, "Vote for '$question' deleted");
+	$::displayer->sendto($reply_to, "Final results: yes = \x0303$yes\x03 - no = \x0304$no\x03");
+	$::logger->info("Vote removed: '$question', id='$id'. Results: yes=$yes, no=$no");
+	
+	return 1;
+} # Fin real_del_vote
+
+
+# ###
+#  list_votes
+# Liste les votes en cours sur le chan actuel
+# ###
+sub list_votes
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	shift @{$ref_params};
+	
+	my $to_check_chan = lc $reply_to;
+	if(defined($ref_params->[0]) && substr($ref_params->[0], 0, 1) eq '#')
+	{
+		$to_check_chan = lc $ref_params->[0];
+	}
+	
+	my %current_votes = ();
+	foreach my $id (sort keys %votes)
+	{
+		if($votes{$id}->{'chan'} eq $to_check_chan)
+		{
+			$current_votes{$id} = $votes{$id};
+		}
+	}
+	
+	my @curr_votes = sort keys %current_votes;
+	my $msg = "";
+	
+	if(@curr_votes)
+	{
+		foreach my $id (@curr_votes)
+		{
+			$msg .= "\x0307{#" . $votes{$id}->{'id'} . "\x03";
+			$msg .= " Added by \x0304" . $votes{$id}->{'author'};
+			$msg .= "on \x0302" . localtime($votes{$id}->{'added date'});
+			$msg .= "\x0307}\x03 ";
+		}
+	}
+	else
+	{
+		$msg = "There's currently no vote on $to_check_chan";
+	}
+	$::displayer->sendto($reply_to, $msg);
+	
+	return 1;
+} # Fin list_votes
+
+
+# ###
+#  see_vote
+# Affiche un vote particulier
+# ###
+sub see_vote
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	# On enlève le 'see'
+	shift @{$ref_params};
+	
+	unless(defined($ref_params->[0]))
+	{
+		$::displayer->sendto($reply_to, "Didn't find the id.");
+		return 0;
+	}
+	
+	my $lc_reply_to = lc $reply_to;
+	my $vote_id = $ref_params->[0];
+	my $id = $lc_reply_to." ".$vote_id;
+	
+	# On vérifie que le vote existe
+	if(defined($votes{$id}))
+	{
+		my $msg = "Vote \x0304[\x0303id #$vote_id\x0304]\x03: ";
+		$msg .= $votes{$id}->{'question'};
+		$msg .= " -- \x0302Added on " . localtime($votes{$id}->{'added date'});
+		
+		$::displayer->sendto($reply_to, $msg);
+		
+		# Public/private?
+# 		if(lc $event->{'nick'} eq $votes{$id}->{'author'})
+		{
+			my $yes = $votes{$id}->{'results'}->{'yes'};
+			my $no  = $votes{$id}->{'results'}->{'no'};
+			$msg = "Current results: yes = \x0303$yes\x03 - no = \x0304$no\x03";
+			
+			$::displayer->sendto($reply_to, $msg);
+		}
+	}
+	else
+	{
+		$::displayer->sendto($reply_to, "Can't find this vote. It has either expired or been deleted.");
+	}
+	
+	return 1;
+} # Fin see_vote
+
+
+# ###
+#  vote
+# Enregistre le vote d'un votant
+# ###
+sub vote
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	my $lc_reply_to = lc $reply_to;
+	my $vote_id = $last_added{$lc_reply_to};
+	my $voter = $event->{'userhost'};
+	
+	my $code = real_vote(join(' ', @{$ref_params}), $lc_reply_to, $vote_id, $voter);
+	
+	if($code == 0)
+	{
+		$::displayer->sendto($reply_to, "You've already answered to this vote \x0304[\x0303id #$vote_id\x0304]\x03");
+	}
+	elsif($code == 1)
+	{
+		$::displayer->sendto($reply_to, "Your vote has been added.");
+	}
+	else
+	{
+		$::displayer->sendto($reply_to, "Can't find your answer.");
+	}
+	
+	return 1;
+} # Fin vote
+
+
+# ###
+#  real_vote
+# Enregistre vraiment le vote d'un votant
+# ###
+sub real_vote
+{
+	my ($params, $chan, $vote_id, $voter) = @_;
+	
+	my $id = $chan." ".$vote_id;
+	print Data::Dumper->Dump([$votes{$id}, $params], [qw(vote_struct params)]);
+	
+	if($params =~ /^(?:y(?:es)?|oui)$/i)
+	{
+		if(can_vote($id, $voter))
+		{
+			$votes{$id}->{'results'}->{'yes'} += 1;
+			push @{$votes{$id}->{'voters'}}, $voter;
+			return 1;
+		}
+		
+		return 0;
+	}
+	elsif($params =~ /^n(?:o(?:n)?)?$/i)
+	{
+		if(can_vote($id, $voter))
+		{
+			$votes{$id}->{'results'}->{'no'} += 1;
+			push @{$votes{$id}->{'voters'}}, $voter;
+			return 1;
+		}
+		
+		return 0;
+	}
+	elsif($params =~ /^#?(\d+)\s+(.+)$/i)
+	{
+		$vote_id = $1;
+		my $answer = $2;
+		$id = $chan." ".$vote_id;
+		
+		if(can_vote($id, $voter))
+		{
+			return real_vote($answer, $chan, $vote_id, $voter);
+		}
+	}
+	
+	return -1;
+} # Fin real_vote
+
+
+# ###
+#  can_vote
+# Vérifie qu'un utilisateur n'a pas déjà voté
+# ###
+sub can_vote
+{
+	my ($id, $user) = @_;
+	
+	my $lc_user = lc $user;
+	
+	if(grep { $_ eq $lc_user } @{$votes{$id}->{'voters'}})
+	{
+		return 0;
+	}
+	
+	$::logger->info("$user can vote for $id");
+	return 1;
+}
+
+
+# ###
+#  get_next_id
+# Renvoie le prochain vote_id libre
+# ###
+sub get_next_id
+{
+	my $base = shift;
+	
+	my $cpt = 0;
+	$cpt++ while(defined($votes{$base." ".$cpt}));
+	
+	return $base." ".$cpt;
+} # Fin get_next_id
+
+
+# ###
+#  vote_help
+# Affiche l'aide de la commande '!vote'
+# ###
+sub vote_help
+{
+	my ($conn, $event, $reply_to, $ref_params) = @_;
+	
+	$conn->privmsg($reply_to, "`".$Config::command_sign."vote new <question>` => "
+												."suggest a new vote");
+	$conn->privmsg($reply_to, "`".$Config::command_sign."vote {del|see} <id>` => "
+												."remove or see the vote with id <id>");
+	$conn->privmsg($reply_to, "`".$Config::command_sign."vote list [<#chan>]` => "
+												."list votes on <#chan> or the current one by default");
+	$conn->privmsg($reply_to, "`".$Config::command_sign."vote [<id>] {yes|no}` => "
+												."vote for the last created vote or the one specified by its <id>");
+	
+	return 1;
+} # Fin vote_help
+
+
+# ###
+#  vote_save
+# Sauvegarde les postit
+# ###
+sub vote_save
+{
+	my (undef, $folder, $reloading) = @_;
+	my $file_votes = $folder."/vote".$Config::suffixe.".sav";
+	
+	store [\%votes, \%last_added], $file_votes;
+	
+	return 1;
+} # Fin vote_save
+
+
+# ###
+#  vote_load
+# Remplis %postits au démarrage
+# ###
+sub vote_load
+{
+	shift; # Pas besoin de la connexion au serveur
+	my $folder = shift;
+	my $file_votes = $folder."/vote".$Config::suffixe.".sav";
+	
+	my $file_content = retrieve $file_votes;
+	my ($ref_votes, $ref_last_added) = @{$file_content};
+	
+	%votes = %{$ref_votes};
+	%last_added = %{$ref_last_added};
+	
+	return 1;
+} # Fin vote_load
+
+
+
+1;
+
+
+
+__END__
+
-- 
GitLab