/// <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); }
/// <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(); }