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