/// <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);
        }
Exemple #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.");
            }
        }
        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);
        }
        /// <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();
        }