/// <summary>
        /// Admin command to end an existing vote.
        /// </summary>
        public void CMD_EndVote(string[] cmds, IUserMessage message)
        {
            if (!DemocracyBot.IsAdmin(message.Author))
            {
                return;
            }
            FDSSection topicSection = VotingCommands.GetVoteTopicSection(cmds, message, out string topicName);

            if (topicSection == null)
            {
                return;
            }
            topicName = topicName.ToLowerFast();
            Console.WriteLine($"Trying to end vote for {topicName} at {StringConversionHelper.DateTimeToString(DateTimeOffset.UtcNow, true)}");
            FDSSection choicesSection = topicSection.GetSection("Choices");

            Bot.ConfigFile.Set("old_topics." + StringConversionHelper.DateTimeToString(DateTimeOffset.UtcNow, true).Replace(".", "_") + "_topic_" + topicName.Replace(".", "_"), topicSection);
            string realKey = DemocracyBot.VoteTopicsSection.Data.Keys.First(s => s.ToLowerFast() == topicName);

            DemocracyBot.VoteTopicsSection.Remove(realKey);
            RefreshTopicData(topicName, topicSection, true);
            FDSSection userResultsSection = topicSection.GetSection("user_results");

            tallyVotes(topicSection, choicesSection, userResultsSection, message, topicName, true);
        }
Example #2
0
        /// <summary>
        /// User command to get help (shows a list of valid bot commands).
        /// </summary>
        public void CMD_Help(string[] cmds, IUserMessage message)
        {
            EmbedBuilder embed = new EmbedBuilder().WithTitle("Bot Command Help").WithFooter("Powered by Democracy!").WithColor(0, 255, 255);

            embed.AddField("**Available Informational Commands:**", "`!hello` for basic bot info, `!help` for usage info");
            embed.AddField("**Available Voting Commands:**", "`!ballot` to see what's current available to vote for, `!vote [topic] <choices ...>` to choose your votes, `!clearvote [topic]` to cancel your previous vote.");
            embed.AddField("**How To Vote:**", "1: Pull open the `!ballot`\n2: Pick the topic you'd like to cast your vote on (let's say for example, 'Topic A: Who's the new king?')"
                           + "\n3: Pick what choices you like (let's say you like '1: Bob' and will accept '3: Joe', but don't want '2: Steve.'.\n4: Enter your choices to the `!vote` command "
                           + "(for those examples, you would type `!vote A 1 3`. This would cast your vote for Topic A as preferring Bob most, and Joe as secondary, but not supporting Steve at all)."
                           + "\nNote that you can input 'none' as your choices input to indicate you don't have any preference but want the total number of members who voted to be 1 higher.");
            SendReply(message, embed.Build());
            if (DemocracyBot.IsAdmin(message.Author))
            {
                SendGenericPositiveMessageReply(message, "Admin Command Help", "`!callvote <topic ID> <topic text> | <choice> | (... additional choices)`, like `!callvote A Who's the new king? | Bob | Steve | Joe` to call a new vote, "
                                                + " `!endvote <topic ID>` to end a vote, `!restart` to restart the bot.");
            }
        }
Example #3
0
        /// <summary>
        /// User command to clear their vote.
        /// </summary>
        public void CMD_ClearVote(string[] cmds, IUserMessage message)
        {
            FDSSection topicSection = GetVoteTopicSection(cmds, message, out string topicName);

            if (topicSection == null)
            {
                return;
            }
            List <string> choices = topicSection.GetStringList($"user_results.{message.Author.Id}");

            if (choices == null)
            {
                SendGenericNegativeMessageReply(message, "Nothing To Clear", "You already do not have any vote cast to that voting topic.");
                return;
            }
            topicSection.Remove($"user_results.{message.Author.Id}");
            DemocracyBot.Save();
            SendGenericPositiveMessageReply(message, "Cleared", $"Your vote for topic `{topicName}` has been removed.\n\nFor your own reference, here is your original vote for that topic: `{string.Join(", ", choices)}`.");
        }
        public void CMD_VoteStatus(string[] cmds, IUserMessage message)
        {
            if (!DemocracyBot.IsAdmin(message.Author))
            {
                return;
            }
            FDSSection topicSection = VotingCommands.GetVoteTopicSection(cmds, message, out string topicName);

            if (topicSection == null)
            {
                return;
            }
            topicName = topicName.ToLowerFast();
            Console.WriteLine($"Trying get vote status for {topicName} at {StringConversionHelper.DateTimeToString(DateTimeOffset.UtcNow, true)}");
            FDSSection choicesSection = topicSection.GetSection("Choices");
            string     realKey        = DemocracyBot.VoteTopicsSection.Data.Keys.First(s => s.ToLowerFast() == topicName);

            RefreshTopicData(topicName, topicSection, false);
            FDSSection userResultsSection = topicSection.GetSection("user_results");

            tallyVotes(topicSection, choicesSection, userResultsSection, message, topicName, false);
        }
        /// <summary>
        /// Admin command to start a new vote.
        /// </summary>
        public void CMD_CallVote(string[] cmds, IUserMessage message)
        {
            if (!DemocracyBot.IsAdmin(message.Author))
            {
                return;
            }
            if (message.Channel is IPrivateChannel)
            {
                SendErrorMessageReply(message, "Wrong Location", "Votes cannot be called from a private message.");
                return;
            }
            if (cmds.Length < 4)
            {
                SendErrorMessageReply(message, "Invalid Input", "Input does not look like it can possibly be valid. Use `!help` for usage information.");
                return;
            }
            string topicId = cmds[0].Replace("`", "").Trim();

            if (DemocracyBot.VoteTopicsSection.HasRootKeyLowered(topicId))
            {
                SendErrorMessageReply(message, "Topic Already Present", "That voting topic ID already exists. Pick a new one!");
                return;
            }
            StringBuilder topicTitle = new StringBuilder(100);

            topicTitle.Append(cmds[1].Replace("`", ""));
            int argId;

            for (argId = 2; argId < cmds.Length; argId++)
            {
                string arg = cmds[argId].Replace("`", "");
                if (arg.Trim() == "|")
                {
                    break;
                }
                topicTitle.Append(" ").Append(cmds[argId]);
            }
            List <string> choices       = new List <string>(cmds.Length);
            StringBuilder currentChoice = new StringBuilder(100);

            for (argId++; argId < cmds.Length; argId++)
            {
                string arg = cmds[argId].Replace("`", "");
                if (arg.Trim() == "|")
                {
                    choices.Add(currentChoice.ToString().Trim());
                    currentChoice.Clear();
                    continue;
                }
                currentChoice.Append(" ").Append(cmds[argId]);
            }
            choices.Add(currentChoice.ToString().Trim());
            for (int i = 0; i < choices.Count; i++)
            {
                if (string.IsNullOrWhiteSpace(choices[i]))
                {
                    choices.RemoveAt(i--);
                }
            }
            if (choices.IsEmpty())
            {
                SendErrorMessageReply(message, "Invalid Input", "No choices found.");
                return;
            }
            if (choices.Count == 1)
            {
                SendErrorMessageReply(message, "Insufficient Democracy", "Only 1 choice detected. Need at least 2.");
                return;
            }
            FDSSection newTopicSection = new FDSSection();

            newTopicSection.SetRoot("Topic", topicTitle.ToString());
            FDSSection choiceSection = new FDSSection();

            for (int i = 0; i < choices.Count; i++)
            {
                choiceSection.Set((i + 1).ToString(), choices[i]);
            }
            IUserMessage sentMessage = message.Channel.SendMessageAsync(embed: GetGenericPositiveMessageEmbed("Vote In Progress", "New Vote... Data inbound, please wait!")).Result;

            newTopicSection.SetRoot("channel_id", sentMessage.Channel.Id);
            newTopicSection.SetRoot("post_id", sentMessage.Id);
            newTopicSection.SetRoot("Choices", choiceSection);
            newTopicSection.SetRoot("user_results", new FDSSection());
            DemocracyBot.VoteTopicsSection.SetRoot(topicId, newTopicSection);
            DemocracyBot.Save();
            RefreshTopicData(topicId, newTopicSection, false);
        }
        private void tallyVotes(FDSSection topicSection, FDSSection choicesSection, FDSSection userResultsSection, IUserMessage message, string topicName, Boolean commitChanges)
        {
            if (userResultsSection == null || userResultsSection.GetRootKeys().IsEmpty())
            {
                SendGenericNegativeMessageReply(message, $"Vote For Topic {topicName} Failed", "No votes were cast.");
                if (commitChanges)
                {
                    DemocracyBot.Save();
                }
                return;
            }
            List <List <string> > voteSets = new List <List <string> >(50);

            foreach (string userId in userResultsSection.GetRootKeys())
            {
                List <string> choices = userResultsSection.GetStringList(userId);
                if (choices != null && !choices.IsEmpty() && !(choices.Count == 1 && choices[0] == "none"))
                {
                    voteSets.Add(choices);
                }
            }
            if (voteSets.IsEmpty())
            {
                SendGenericNegativeMessageReply(message, $"Vote For Topic {topicName} Failed", "No votes were cast.");
                if (commitChanges)
                {
                    DemocracyBot.Save();
                }
                return;
            }
            int usersWhoVotedTotal = voteSets.Count;
            int discards           = 0;

            string gatherStats(string choice, string type, List <List <string> > placeVoteSets)
            {
                int numberHadFirst = 0;
                int positionTotal  = 0;
                int numberHadAtAll = 0;

                foreach (string userId in userResultsSection.GetRootKeys())
                {
                    List <string> choices = userResultsSection.GetStringList(userId);
                    if (choices != null && !choices.IsEmpty())
                    {
                        int index = choices.IndexOf(choice);
                        if (index != -1)
                        {
                            numberHadAtAll++;
                            positionTotal += index + 1;
                            if (index == 0)
                            {
                                numberHadFirst++;
                            }
                        }
                    }
                }
                return($"Options that were discarded due to low support: {discards}\n"
                       + $"Users whose votes were discarded due to supporting only unpopular options: {usersWhoVotedTotal - placeVoteSets.Count}\nUsers who listed the {type} first: {numberHadFirst}\n"
                       + $"Users who listed the {type} at all: {numberHadAtAll}\nAverage ranking of the {type}: {positionTotal / (float)numberHadAtAll:0.0}");
            }

            Tuple <string, string> firstTuple  = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));
            Tuple <string, string> secondTuple = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));
            Tuple <string, string> thirdTuple  = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));
            Tuple <string, string> fourthTuple = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));
            Tuple <string, string> fifthTuple  = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));
            Tuple <string, string> sixthTuple  = getRankWinner(voteSets.ConvertAll(new Converter <List <string>, List <string> >(x => x.ConvertAll(new Converter <string, string>(y => y.ToString())))));

            Tuple <string, string> getRankWinner(List <List <string> > placeVoteSets)
            {
                string topRank      = "";
                string topRankStats = "";
                bool   haveWinner   = false;

                Dictionary <string, int> votesTracker      = new Dictionary <string, int>(128);
                Dictionary <string, int> totalVotesTracker = new Dictionary <string, int>();

                discards = 0;

                foreach (List <string> voteSet in placeVoteSets)
                {
                    foreach (string singleVote in voteSet)
                    {
                        if (!totalVotesTracker.TryGetValue(singleVote, out int singleVoteCount))
                        {
                            singleVoteCount = 0;
                        }
                        totalVotesTracker[singleVote] = singleVoteCount + 1;
                    }
                }

                while (true)
                {
                    votesTracker.Clear();
                    foreach (List <string> voteSet in placeVoteSets)
                    {
                        string vote = voteSet[0]; //Only checking highest vote right now
                        if (!votesTracker.TryGetValue(vote, out int count))
                        {
                            count = 0;
                        }
                        votesTracker[vote] = count + 1;
                    }
                    if (votesTracker.Count == 0)
                    {
                        Console.WriteLine("Something went funky in vote counting... tracker is empty without a clear winner!");
                        break;
                    }
                    string        best = placeVoteSets[0][0];
                    List <string> worstList = new List <string>();// placeVoteSets[0][0];
                    int           bestCount = 0, worstCount = int.MaxValue;
                    foreach (KeyValuePair <string, int> voteResult in votesTracker)
                    {
                        if (voteResult.Value > bestCount)
                        {
                            best      = voteResult.Key;
                            bestCount = voteResult.Value;
                        }
                        if (voteResult.Value < worstCount)
                        {
                            worstList = new List <string>()
                            {
                                voteResult.Key
                            };
                            worstCount = voteResult.Value;
                        }
                        else if (voteResult.Value == worstCount)
                        {
                            worstList.Add(voteResult.Key);
                        }
                    }
                    if (bestCount * 2 > placeVoteSets.Count)
                    {
                        if (!haveWinner)
                        {
                            topRank      = best;
                            topRankStats = gatherStats(topRank, "winner", placeVoteSets);
                            haveWinner   = true;
                            for (int i = 0; i < voteSets.Count; i++)
                            {
                                if (voteSets[i].Contains(topRank))
                                {
                                    voteSets[i].Remove(topRank);
                                    if (voteSets[i].IsEmpty())
                                    {
                                        voteSets.RemoveAt(i--);
                                    }
                                }
                            }
                            break;
                        }
                    }
                    string worst = totalVotesTracker.Where(x => worstList.Contains(x.Key)).Aggregate((l, r) => l.Value < r.Value ? l : r).Key;
                    for (int i = 0; i < placeVoteSets.Count; i++)
                    {
                        for (int j = 0; j < placeVoteSets[i].Count; j++)
                        {
                            if (placeVoteSets[i][j] == worst)
                            {
                                placeVoteSets[i].RemoveAt(j);
                            }
                        }
                    }
                    for (int i = 0; i < placeVoteSets.Count; i++)
                    {
                        if (placeVoteSets[i].IsEmpty())
                        {
                            placeVoteSets.RemoveAt(i);
                            i--;
                        }
                    }
                    if (!haveWinner)
                    {
                        discards++;
                    }
                }
                return(new Tuple <string, string>(topRank, topRankStats));
            }

            SendGenericPositiveMessageReply(message, $"Vote Results For **{topicName}: {topicSection.GetString("Topic")}**", $"**__Winner__**: **{firstTuple.Item1}**: `{choicesSection.GetString(firstTuple.Item1)}`"
                                            + $"\n\n**Stats:**\nUsers who voted, in total: {usersWhoVotedTotal}\n{firstTuple.Item2}\n\n"
                                            + $"**__Runner Up__**: **{secondTuple.Item1}**: `{choicesSection.GetString(secondTuple.Item1)}`\n**Stats For Runner Up**:\n{secondTuple.Item2}\n\n"
                                            + $"**__Thrid Place__**: **{thirdTuple.Item1}**: `{choicesSection.GetString(thirdTuple.Item1)}`\n**Stats For Third Place**:\n{thirdTuple.Item2}\n\n"
                                            + $"**__Fourth Place__**: **{fourthTuple.Item1}**: `{choicesSection.GetString(fourthTuple.Item1)}`\n**Stats For Fourth Place**:\n{fourthTuple.Item2}\n\n"
                                            + $"**__Fifth Place__**: **{fifthTuple.Item1}**: `{choicesSection.GetString(fifthTuple.Item1)}`\n**Stats For Fifth Place**:\n{fifthTuple.Item2}\n\n"
                                            + $"**__Sixth Place__**: **{sixthTuple.Item1}**: `{choicesSection.GetString(sixthTuple.Item1)}`\n**Stats For Sixth Place**:\n{sixthTuple.Item2}"
                                            );
            if (commitChanges)
            {
                DemocracyBot.Save();
            }
        }
        /// <summary>
        /// Admin command to end an existing vote.
        /// </summary>
        public void CMD_EndVote(string[] cmds, IUserMessage message)
        {
            if (!DemocracyBot.IsAdmin(message.Author))
            {
                return;
            }
            FDSSection topicSection = VotingCommands.GetVoteTopicSection(cmds, message, out string topicName);

            if (topicSection == null)
            {
                return;
            }
            topicName = topicName.ToLowerFast();
            Console.WriteLine($"Trying to end vote for {topicName} at {StringConversionHelper.DateTimeToString(DateTimeOffset.UtcNow, true)}");
            FDSSection choicesSection = topicSection.GetSection("Choices");

            Bot.ConfigFile.Set("old_topics." + StringConversionHelper.DateTimeToString(DateTimeOffset.UtcNow, true).Replace(".", "_") + "_topic_" + topicName.Replace(".", "_"), topicSection);
            string realKey = DemocracyBot.VoteTopicsSection.Data.Keys.First(s => s.ToLowerFast() == topicName);

            DemocracyBot.VoteTopicsSection.Remove(realKey);
            RefreshTopicData(topicName, topicSection, true);
            FDSSection userResultsSection = topicSection.GetSection("user_results");

            if (userResultsSection == null || userResultsSection.GetRootKeys().IsEmpty())
            {
                SendGenericNegativeMessageReply(message, $"Vote For Topic {topicName} Failed", "No votes were cast.");
                DemocracyBot.Save();
                return;
            }
            List <List <string> > voteSets = new List <List <string> >(50);

            foreach (string userId in userResultsSection.GetRootKeys())
            {
                List <string> choices = userResultsSection.GetStringList(userId);
                if (choices != null && !choices.IsEmpty() && !(choices.Count == 1 && choices[0] == "none"))
                {
                    voteSets.Add(choices);
                }
            }
            if (voteSets.IsEmpty())
            {
                SendGenericNegativeMessageReply(message, $"Vote For Topic {topicName} Failed", "No votes were cast.");
                DemocracyBot.Save();
                return;
            }
            int usersWhoVotedTotal = voteSets.Count;
            Dictionary <string, int> votesTracker = new Dictionary <string, int>(128);
            string winner           = voteSets[0][0];
            string secondPlace      = voteSets[0][0];
            int    discards         = 0;
            bool   haveWinner       = false;
            string winnerStats      = "(error: stats missing)";
            string secondPlaceStats = "(error: second place missing)";

            string gatherStats(string choice, string type)
            {
                int numberHadFirst = 0;
                int positionTotal  = 0;
                int numberHadAtAll = 0;

                foreach (string userId in userResultsSection.GetRootKeys())
                {
                    List <string> choices = userResultsSection.GetStringList(userId);
                    if (choices != null && !choices.IsEmpty())
                    {
                        int index = choices.IndexOf(choice);
                        if (index != -1)
                        {
                            numberHadAtAll++;
                            positionTotal += index + 1;
                            if (index == 0)
                            {
                                numberHadFirst++;
                            }
                        }
                    }
                }
                return($"Options that were discarded due to low support: {discards}\n"
                       + $"Users whose votes were discarded due to supporting only unpopular options: {usersWhoVotedTotal - voteSets.Count}\nUsers who listed the {type} first: {numberHadFirst}\n"
                       + $"Users who listed the {type} at all: {numberHadAtAll}\nAverage ranking of the {type}: {positionTotal / (float)numberHadAtAll:0.0}");
            }

            while (true)
            {
                votesTracker.Clear();
                foreach (List <string> voteSet in voteSets)
                {
                    string vote = voteSet[0];
                    if (!votesTracker.TryGetValue(vote, out int count))
                    {
                        count = 0;
                    }
                    votesTracker[vote] = count + 1;
                }
                if (votesTracker.Count == 0)
                {
                    Console.WriteLine("Something went funky in vote counting... tracker is empty without a clear winner!");
                    break;
                }
                string best = voteSets[0][0], worst = voteSets[0][0];
                int    bestCount = 0, worstCount = int.MaxValue;
                foreach (KeyValuePair <string, int> voteResult in votesTracker)
                {
                    if (voteResult.Value > bestCount)
                    {
                        best      = voteResult.Key;
                        bestCount = voteResult.Value;
                    }
                    if (voteResult.Value < worstCount)
                    {
                        worst      = voteResult.Key;
                        worstCount = voteResult.Value;
                    }
                }
                if (bestCount * 2 > voteSets.Count)
                {
                    if (!haveWinner)
                    {
                        winner      = best;
                        winnerStats = gatherStats(winner, "winner");
                        haveWinner  = true;
                        for (int i = 0; i < voteSets.Count; i++)
                        {
                            if (voteSets[i].Contains(winner))
                            {
                                voteSets[i].Remove(winner);
                                if (voteSets[i].IsEmpty())
                                {
                                    voteSets.RemoveAt(i--);
                                }
                            }
                        }
                        worst = winner;
                    }
                    else
                    {
                        secondPlace      = best;
                        secondPlaceStats = gatherStats(secondPlace, "runner up");
                        break;
                    }
                }
                for (int i = 0; i < voteSets.Count; i++)
                {
                    if (voteSets[i][0] == worst)
                    {
                        voteSets[i].RemoveAt(0);
                        if (voteSets[i].IsEmpty())
                        {
                            voteSets.RemoveAt(i--);
                        }
                    }
                }
                if (!haveWinner)
                {
                    discards++;
                }
            }
            SendGenericPositiveMessageReply(message, $"Vote Results For **{topicName}: {topicSection.GetString("Topic")}**", $"**__Winner__**: **{winner}**: `{choicesSection.GetString(winner)}`"
                                            + $"\n\n**Stats:**\nUsers who voted, in total: {usersWhoVotedTotal}\n{winnerStats}\n\n"
                                            + $"**__Runner Up__**: **{secondPlace}**: `{choicesSection.GetString(secondPlace)}`\n**Stats For Runner Up**:\n{secondPlaceStats}");
            DemocracyBot.Save();
        }
Example #8
0
        /// <summary>
        /// User command to cast a vote.
        /// </summary>
        public void CMD_Vote(string[] cmds, IUserMessage message)
        {
            FDSSection topicSection = GetVoteTopicSection(cmds, message, out string topicName);

            if (topicSection == null)
            {
                return;
            }
            topicName = topicName.ToLowerFast();
            FDSSection    choicesSection = topicSection.GetSection("Choices");
            List <string> newChoices     = new List <string>();

            for (int i = 0; i < cmds.Length; i++)
            {
                string arg = cmds[i].Replace(",", "").Replace("`", "").Trim().ToLowerFast();
                if (arg == topicName && newChoices.IsEmpty())
                {
                    continue;
                }
                if (arg.ToLowerFast() == "none" && newChoices.IsEmpty())
                {
                    newChoices.Add("none");
                    break;
                }
                if (string.IsNullOrWhiteSpace(arg))
                {
                    continue;
                }
                if (choicesSection.GetRootDataLowered(arg) == null)
                {
                    SendErrorMessageReply(message, "Invalid Choice", $"Choice `{arg}` is not recognized. Did you format the command correctly?");
                    return;
                }
                if (newChoices.Contains(arg))
                {
                    SendErrorMessageReply(message, "Duplicate Choice", $"Choice `{arg}` has been sent twice. Check over your vote, you may have made a typo.");
                    return;
                }
                newChoices.Add(arg);
            }
            if (newChoices.IsEmpty())
            {
                SendErrorMessageReply(message, "Need To Choose", "You issued a vote command without any choices. You need to choose! If you're confused how to vote, use `!help`.");
                return;
            }
            List <string> originalChoices = topicSection.GetStringList($"user_results.{message.Author.Id}");

            topicSection.Set($"user_results.{message.Author.Id}", newChoices);
            DemocracyBot.Save();
            StringBuilder choicesText = new StringBuilder(100);

            foreach (string choice in newChoices)
            {
                choicesText.Append($"**{choice}**: `{choicesSection.GetString(choice)}`, ");
            }
            choicesText.Length -= 2;
            if (originalChoices == null)
            {
                SendGenericPositiveMessageReply(message, "Vote Cast", $"Your vote for topic `{topicName}` has been cast as: {choicesText}.");
            }
            else
            {
                SendGenericPositiveMessageReply(message, "Vote Cast", $"Your vote for topic `{topicName}` has been replaced to: {choicesText}.\n\nFor your own reference, here is your original vote for that topic: `{string.Join(", ", originalChoices)}`.");
            }
            AdminCommands.RefreshTopicData(topicName, topicSection, false);
        }