From c954861baa8df0985bfe3c746714c753b3fbdd3e Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 16 Aug 2016 14:57:47 +0200 Subject: [PATCH] Games module file, poll command converted --- .../Games/Commands/BetrayGame.cs | 0 .../Games/Commands/Bomberman.cs | 0 .../Games/Commands/Leet.cs | 0 .../Games/Commands/PlantPick.cs | 0 .../Modules/Games/Commands/PollCommand.cs | 132 ++++++++++++ .../Modules/Games/Commands/SpeedTyping.cs | 197 ++++++++++++++++++ .../Games/Commands/Trivia/TriviaGame.cs | 0 .../Games/Commands/Trivia/TriviaQuestion.cs | 0 .../Commands/Trivia/TriviaQuestionPool.cs | 0 .../Modules/Games/Commands/TriviaCommand.cs | 76 +++++++ src/NadekoBot/Modules/Games/GamesModule.cs | 114 ++++++++++ .../Modules/Translator/TranslatorModule.cs | 1 + .../Translator/ValidLanguagesCommand.cs | 25 --- .../Modules/Utility/UtilityModule.cs | 1 + .../Services/Impl/BotConfiguration.cs | 2 + .../_Modules/Games/Commands/PollCommand.cs | 141 ------------- .../_Modules/Games/Commands/SpeedTyping.cs | 195 ----------------- .../_Modules/Games/Commands/TriviaCommand.cs | 73 ------- src/NadekoBot/_Modules/Games/GamesModule.cs | 140 ------------- 19 files changed, 523 insertions(+), 574 deletions(-) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/BetrayGame.cs (100%) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/Bomberman.cs (100%) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/Leet.cs (100%) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/PlantPick.cs (100%) create mode 100644 src/NadekoBot/Modules/Games/Commands/PollCommand.cs create mode 100644 src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs rename src/NadekoBot/{_Modules => Modules}/Games/Commands/Trivia/TriviaGame.cs (100%) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/Trivia/TriviaQuestion.cs (100%) rename src/NadekoBot/{_Modules => Modules}/Games/Commands/Trivia/TriviaQuestionPool.cs (100%) create mode 100644 src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs create mode 100644 src/NadekoBot/Modules/Games/GamesModule.cs delete mode 100644 src/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs delete mode 100644 src/NadekoBot/_Modules/Games/Commands/PollCommand.cs delete mode 100644 src/NadekoBot/_Modules/Games/Commands/SpeedTyping.cs delete mode 100644 src/NadekoBot/_Modules/Games/Commands/TriviaCommand.cs delete mode 100644 src/NadekoBot/_Modules/Games/GamesModule.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/BetrayGame.cs b/src/NadekoBot/Modules/Games/Commands/BetrayGame.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/BetrayGame.cs rename to src/NadekoBot/Modules/Games/Commands/BetrayGame.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/Bomberman.cs b/src/NadekoBot/Modules/Games/Commands/Bomberman.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/Bomberman.cs rename to src/NadekoBot/Modules/Games/Commands/Bomberman.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/Leet.cs b/src/NadekoBot/Modules/Games/Commands/Leet.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/Leet.cs rename to src/NadekoBot/Modules/Games/Commands/Leet.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/PlantPick.cs b/src/NadekoBot/Modules/Games/Commands/PlantPick.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/PlantPick.cs rename to src/NadekoBot/Modules/Games/Commands/PlantPick.cs diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommand.cs b/src/NadekoBot/Modules/Games/Commands/PollCommand.cs new file mode 100644 index 00000000..3684adc1 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/PollCommand.cs @@ -0,0 +1,132 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Classes; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Commands +{ + public partial class GamesModule + { + + //todo DB in the future + public static ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Poll(IMessage imsg, [Remainder] string arg) + { + var channel = imsg.Channel as IGuildChannel; + + if (!(imsg.Author as IGuildUser).GuildPermissions.ManageChannels) + return; + if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) + return; + var data = arg.Split(';'); + if (data.Length < 3) + return; + + var poll = new Poll(imsg, data[0], data.Skip(1)); + if (ActivePolls.TryAdd(channel.Guild, poll)) + { + await poll.StartPoll().ConfigureAwait(false); + } + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Pollend(IMessage imsg) + { + var channel = imsg.Channel as IGuildChannel; + + if (!(imsg.Author as IGuildUser).GuildPermissions.ManageChannels) + return; + Poll poll; + ActivePolls.TryGetValue(channel.Guild, out poll); + await poll.StopPoll(channel).ConfigureAwait(false); + } + } + + public class Poll + { + private readonly IMessage imsg; + private readonly string[] answers; + private ConcurrentDictionary participants = new ConcurrentDictionary(); + private readonly string question; + private DateTime started; + private CancellationTokenSource pollCancellationSource = new CancellationTokenSource(); + + public Poll(IMessage imsg, string question, IEnumerable enumerable) + { + this.imsg = imsg; + this.question = question; + this.answers = enumerable as string[] ?? enumerable.ToArray(); + } + + public async Task StartPoll() + { + started = DateTime.Now; + NadekoBot.Client.MessageReceived += Vote; + var msgToSend = $@"📃**{imsg.Author.Username}** has created a poll which requires your attention: + +**{question}**\n"; + var num = 1; + msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); + msgToSend += "\n**Private Message me with the corresponding number of the answer.**"; + await imsg.Channel.SendMessageAsync(msgToSend).ConfigureAwait(false); + } + + public async Task StopPoll(IGuildChannel ch) + { + NadekoBot.Client.MessageReceived -= Vote; + try + { + var results = participants.GroupBy(kvp => kvp.Value) + .ToDictionary(x => x.Key, x => x.Sum(kvp => 1)) + .OrderBy(kvp => kvp.Value); + + var totalVotesCast = results.Sum(kvp => kvp.Value); + if (totalVotesCast == 0) + { + await imsg.Channel.SendMessageAsync("📄 **No votes have been cast.**").ConfigureAwait(false); + return; + } + var closeMessage = $"--------------**POLL CLOSED**--------------\n" + + $"📄 , here are the results:\n"; + closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" + + $" has {kvp.Value} votes." + + $"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n"); + + await imsg.Channel.SendMessageAsync($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}").ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine($"Error in poll game {ex}"); + } + } + + private async Task Vote(IMessage msg) + { + try + { + IPrivateChannel ch; + if ((ch = msg.Channel as IPrivateChannel) == null) + return; + int vote; + if (!int.TryParse(msg.Content, out vote)) return; + if (vote < 1 || vote > answers.Length) + return; + if (participants.TryAdd(msg.Author, vote)) + { + await (ch as ITextChannel).SendMessageAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false); + } + } + catch { } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs b/src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs new file mode 100644 index 00000000..750fcbec --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs @@ -0,0 +1,197 @@ +//using Discord; +//using Discord.Commands; +//using NadekoBot.Classes; +//using NadekoBot.DataModels; +//using NadekoBot.Extensions; +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; +//using System.Diagnostics; +//using System.Linq; +//using System.Threading.Tasks; + +////todo DB +////todo rewrite? +//namespace NadekoBot.Modules.Games.Commands +//{ + +// public static class SentencesProvider +// { +// internal static string GetRandomSentence() +// { +// var data = DbHandler.Instance.GetAllRows(); +// try +// { +// return data.ToList()[new Random().Next(0, data.Count())].Text; +// } +// catch +// { +// return "Failed retrieving data from parse. Owner didn't add any articles to type using `typeadd`."; +// } +// } +// } + +// public class TypingGame +// { +// public const float WORD_VALUE = 4.5f; +// private readonly Channel channel; +// public string CurrentSentence; +// public bool IsActive; +// private readonly Stopwatch sw; +// private readonly List finishedUserIds; + +// public TypingGame(Channel channel) +// { +// this.channel = channel; +// IsActive = false; +// sw = new Stopwatch(); +// finishedUserIds = new List(); +// } + +// public Channel Channell { get; internal set; } + +// internal async Task Stop() +// { +// if (!IsActive) return false; +// NadekoBot.Client.MessageReceived -= AnswerReceived; +// finishedUserIds.Clear(); +// IsActive = false; +// sw.Stop(); +// sw.Reset(); +// await channel.Send("Typing contest stopped").ConfigureAwait(false); +// return true; +// } + +// internal async Task Start() +// { +// while (true) +// { +// if (IsActive) return; // can't start running game +// IsActive = true; +// CurrentSentence = SentencesProvider.GetRandomSentence(); +// var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f); +// await channel.SendMessage($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false); + + +// var msg = await channel.SendMessage("Starting new typing contest in **3**...").ConfigureAwait(false); +// await Task.Delay(1000).ConfigureAwait(false); +// await msg.Edit("Starting new typing contest in **2**...").ConfigureAwait(false); +// await Task.Delay(1000).ConfigureAwait(false); +// await msg.Edit("Starting new typing contest in **1**...").ConfigureAwait(false); +// await Task.Delay(1000).ConfigureAwait(false); +// await msg.Edit($":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:").ConfigureAwait(false); +// sw.Start(); +// HandleAnswers(); + +// while (i > 0) +// { +// await Task.Delay(1000).ConfigureAwait(false); +// i--; +// if (!IsActive) +// return; +// } + +// await Stop().ConfigureAwait(false); +// } +// } + +// private void HandleAnswers() +// { +// NadekoBot.Client.MessageReceived += AnswerReceived; +// } + +// private async void AnswerReceived(object sender, MessageEventArgs e) +// { +// try +// { +// if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + +// var guess = e.Message.RawText; + +// var distance = CurrentSentence.LevenshteinDistance(guess); +// var decision = Judge(distance, guess.Length); +// if (decision && !finishedUserIds.Contains(e.User.Id)) +// { +// finishedUserIds.Add(e.User.Id); +// await channel.Send($"{e.User.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false); +// if (finishedUserIds.Count % 2 == 0) +// { +// await imsg.Channel.SendMessageAsync($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:").ConfigureAwait(false); +// } +// } +// } +// catch { } +// } + +// private bool Judge(int errors, int textLength) => errors <= textLength / 25; + +// } + +// internal class SpeedTyping : DiscordCommand +// { + +// public static ConcurrentDictionary RunningContests; + +// public SpeedTyping(DiscordModule module) : base(module) +// { +// RunningContests = new ConcurrentDictionary(); +// } + +// public Func DoFunc() => +// async e => +// { +// var game = RunningContests.GetOrAdd(e.User.Server.Id, id => new TypingGame(e.Channel)); + +// if (game.IsActive) +// { +// await imsg.Channel.SendMessageAsync( +// $"Contest already running in " + +// $"{game.Channell.Mention} channel.") +// .ConfigureAwait(false); +// } +// else +// { +// await game.Start().ConfigureAwait(false); +// } +// }; + +// private Func QuitFunc() => +// async e => +// { +// TypingGame game; +// if (RunningContests.TryRemove(e.User.Server.Id, out game)) +// { +// await game.Stop().ConfigureAwait(false); +// return; +// } +// await imsg.Channel.SendMessageAsync("No contest to stop on this channel.").ConfigureAwait(false); +// }; + +// internal override void Init(CommandGroupBuilder cgb) +// { +// cgb.CreateCommand(Module.Prefix + "typestart") +// .Description($"Starts a typing contest. | `{Prefix}typestart`") +// .Do(DoFunc()); + +// cgb.CreateCommand(Module.Prefix + "typestop") +// .Description($"Stops a typing contest on the current channel. | `{Prefix}typestop`") +// .Do(QuitFunc()); + +// cgb.CreateCommand(Module.Prefix + "typeadd") +// .Description($"Adds a new article to the typing contest. Owner only. | `{Prefix}typeadd wordswords`") +// .Parameter("text", ParameterType.Unparsed) +// .Do(async e => +// { +// if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return; + +// DbHandler.Instance.Connection.Insert(new TypingArticle +// { +// Text = e.GetArg("text"), +// DateAdded = DateTime.Now +// }); + +// await imsg.Channel.SendMessageAsync("Added new article for typing game.").ConfigureAwait(false); +// }); +// } +// } +//} diff --git a/src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaGame.cs rename to src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaQuestion.cs rename to src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs diff --git a/src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs similarity index 100% rename from src/NadekoBot/_Modules/Games/Commands/Trivia/TriviaQuestionPool.cs rename to src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs b/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs new file mode 100644 index 00000000..ecbc0c4a --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs @@ -0,0 +1,76 @@ +//using Discord.Commands; +//using NadekoBot.Classes; +//using NadekoBot.Modules.Games.Commands.Trivia; +//using System; +//using System.Collections.Concurrent; +//using System.Linq; + +////todo DB +////todo Rewrite? + +//namespace NadekoBot.Modules.Games.Commands +//{ +// internal class TriviaCommands : DiscordCommand +// { +// public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); + +// public TriviaCommands(DiscordModule module) : base(module) +// { +// } + +// internal override void Init(CommandGroupBuilder cgb) +// { +// cgb.CreateCommand(Module.Prefix + "t") +// .Description($"Starts a game of trivia. You can add nohint to prevent hints." + +// "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + +// $" |`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") +// .Parameter("args", ParameterType.Multiple) +// .Do(async e => +// { +// TriviaGame trivia; +// if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) +// { +// var showHints = !e.Args.Contains("nohint"); +// var number = e.Args.Select(s => +// { +// int num; +// return new Tuple(int.TryParse(s, out num), num); +// }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); +// if (number < 0) +// return; +// var triviaGame = new TriviaGame(e, showHints, number == 0 ? 10 : number); +// if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) +// await imsg.Channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); +// else +// await triviaGame.StopGame().ConfigureAwait(false); +// } +// else +// await imsg.Channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); +// }); + +// cgb.CreateCommand(Module.Prefix + "tl") +// .Description($"Shows a current trivia leaderboard. | `{Prefix}tl`") +// .Do(async e => +// { +// TriviaGame trivia; +// if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) +// await imsg.Channel.SendMessageAsync(trivia.GetLeaderboard()).ConfigureAwait(false); +// else +// await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); +// }); + +// cgb.CreateCommand(Module.Prefix + "tq") +// .Description($"Quits current trivia after current question. | `{Prefix}tq`") +// .Do(async e => +// { +// TriviaGame trivia; +// if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) +// { +// await trivia.StopGame().ConfigureAwait(false); +// } +// else +// await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); +// }); +// } +// } +//} diff --git a/src/NadekoBot/Modules/Games/GamesModule.cs b/src/NadekoBot/Modules/Games/GamesModule.cs new file mode 100644 index 00000000..0cce65b0 --- /dev/null +++ b/src/NadekoBot/Modules/Games/GamesModule.cs @@ -0,0 +1,114 @@ +using Discord.Commands; +using Discord; +using NadekoBot.Services; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System; +using System.Linq; +using System.Collections.Generic; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.Games +{ + [Module(">", AppendSpace = false)] + public partial class GamesModule : DiscordModule + { + //todo DB + private IEnumerable _8BallResponses; + public GamesModule(ILocalization loc, CommandService cmds, IBotConfiguration config, IDiscordClient client) : base(loc, cmds, config, client) + { + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Choose(IMessage imsg, [Remainder] string list) + { + var channel = imsg.Channel as IGuildChannel; + if (string.IsNullOrWhiteSpace(list)) + return; + var listArr = list.Split(';'); + if (listArr.Count() < 2) + return; + var rng = new Random(); + await imsg.Channel.SendMessageAsync(listArr[rng.Next(0, listArr.Length)]).ConfigureAwait(false); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task _8Ball(IMessage imsg, [Remainder] string question) + { + var channel = imsg.Channel as IGuildChannel; + + if (string.IsNullOrWhiteSpace(question)) + return; + var rng = new Random(); + await imsg.Channel.SendMessageAsync($@":question: `Question` __**{question}**__ +🎱 `8Ball Answers` __**{_8BallResponses.Shuffle().FirstOrDefault()}**__").ConfigureAwait(false); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Rps(IMessage imsg, string input) + { + var channel = imsg.Channel as IGuildChannel; + + Func GetRPSPick = (p) => + { + if (p == 0) + return "rocket"; + else if (p == 1) + return "paperclip"; + else + return "scissors"; + }; + + int pick; + switch (input) + { + case "r": + case "rock": + case "rocket": + pick = 0; + break; + case "p": + case "paper": + case "paperclip": + pick = 1; + break; + case "scissors": + case "s": + pick = 2; + break; + default: + return; + } + var nadekoPick = new Random().Next(0, 3); + var msg = ""; + if (pick == nadekoPick) + msg = $"It's a draw! Both picked :{GetRPSPick(pick)}:"; + else if ((pick == 0 && nadekoPick == 1) || + (pick == 1 && nadekoPick == 2) || + (pick == 2 && nadekoPick == 0)) + msg = $"{(await NadekoBot.Client.GetCurrentUserAsync()).Mention} won! :{GetRPSPick(nadekoPick)}: beats :{GetRPSPick(pick)}:"; + else + msg = $"{imsg.Author.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(nadekoPick)}:"; + + await imsg.Channel.SendMessageAsync(msg).ConfigureAwait(false); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Linux(IMessage imsg, string guhnoo, string loonix) + { + var channel = imsg.Channel as IGuildChannel; + + await imsg.Channel.SendMessageAsync( +$@"I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. + +Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. + +There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}." + ).ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Translator/TranslatorModule.cs b/src/NadekoBot/Modules/Translator/TranslatorModule.cs index 29d72312..c51631fe 100644 --- a/src/NadekoBot/Modules/Translator/TranslatorModule.cs +++ b/src/NadekoBot/Modules/Translator/TranslatorModule.cs @@ -8,6 +8,7 @@ using NadekoBot.Services; namespace NadekoBot.Modules.Translator { + [Module("~", AppendSpace = false)] public class TranslatorModule : DiscordModule { public TranslatorModule(ILocalization loc, CommandService cmds, IBotConfiguration config, IDiscordClient client) : base(loc, cmds, config, client) diff --git a/src/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs b/src/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs deleted file mode 100644 index ad5c661e..00000000 --- a/src/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Translator.Helpers; -using System; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Translator -{ - class ValidLanguagesCommand : DiscordCommand - { - public ValidLanguagesCommand(DiscordModule module) : base(module) { } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "translangs") - .Description($"List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`") - .Parameter("search", ParameterType.Optional) - .Do(ListLanguagesFunc()); - } - private Func ListLanguagesFunc() => async e => - { - - }; - } -} diff --git a/src/NadekoBot/Modules/Utility/UtilityModule.cs b/src/NadekoBot/Modules/Utility/UtilityModule.cs index e8dfefa5..aeb5cd4f 100644 --- a/src/NadekoBot/Modules/Utility/UtilityModule.cs +++ b/src/NadekoBot/Modules/Utility/UtilityModule.cs @@ -128,6 +128,7 @@ namespace NadekoBot.Modules.Utility } } + //todo maybe split into 3/4 different commands with the same name [LocalizedCommand, LocalizedDescription, LocalizedSummary] [RequireContext(ContextType.Guild)] public async Task Prune(IMessage msg, [Remainder] string target = null) diff --git a/src/NadekoBot/Services/Impl/BotConfiguration.cs b/src/NadekoBot/Services/Impl/BotConfiguration.cs index 37ff011e..e25338c5 100644 --- a/src/NadekoBot/Services/Impl/BotConfiguration.cs +++ b/src/NadekoBot/Services/Impl/BotConfiguration.cs @@ -8,6 +8,8 @@ namespace NadekoBot.Services.Impl { public class BotConfiguration : IBotConfiguration { + internal Task _8BallResponses; + public HashSet BlacklistedChannels { get; set; } = new HashSet(); public HashSet BlacklistedServers { get; set; } = new HashSet(); diff --git a/src/NadekoBot/_Modules/Games/Commands/PollCommand.cs b/src/NadekoBot/_Modules/Games/Commands/PollCommand.cs deleted file mode 100644 index ac897f14..00000000 --- a/src/NadekoBot/_Modules/Games/Commands/PollCommand.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - internal class PollCommand : DiscordCommand - { - - public static ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "poll") - .Description($"Creates a poll, only person who has manage server permission can do it. | `{Prefix}poll Question?;Answer1;Answ 2;A_3`") - .Parameter("allargs", ParameterType.Unparsed) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - if (ActivePolls.ContainsKey(e.Server)) - return; - var arg = e.GetArg("allargs"); - if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) - return; - var data = arg.Split(';'); - if (data.Length < 3) - return; - - var poll = new Poll(e, data[0], data.Skip(1)); - if (PollCommand.ActivePolls.TryAdd(e.Server, poll)) - { - await poll.StartPoll().ConfigureAwait(false); - } - }).ConfigureAwait(false); - }); - cgb.CreateCommand(Module.Prefix + "pollend") - .Description($"Stops active poll on this server and prints the results in this channel. | `{Prefix}pollend`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - if (!ActivePolls.ContainsKey(e.Server)) - return; - await ActivePolls[e.Server].StopPoll(e.Channel).ConfigureAwait(false); - }); - } - - public PollCommand(DiscordModule module) : base(module) { } - } - - internal class Poll - { - private readonly CommandEventArgs e; - private readonly string[] answers; - private ConcurrentDictionary participants = new ConcurrentDictionary(); - private readonly string question; - private DateTime started; - private CancellationTokenSource pollCancellationSource = new CancellationTokenSource(); - - public Poll(CommandEventArgs e, string question, IEnumerable enumerable) - { - this.e = e; - this.question = question; - this.answers = enumerable as string[] ?? enumerable.ToArray(); - } - - public async Task StartPoll() - { - started = DateTime.Now; - NadekoBot.Client.MessageReceived += Vote; - var msgToSend = - $"📃**{e.User.Name}** from **{e.Server.Name}** server has created a poll which requires your attention:\n\n" + - $"**{question}**\n"; - var num = 1; - msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); - msgToSend += "\n**Private Message me with the corresponding number of the answer.**"; - await imsg.Channel.SendMessageAsync(msgToSend).ConfigureAwait(false); - } - - public async Task StopPoll(Channel ch) - { - NadekoBot.Client.MessageReceived -= Vote; - Poll throwaway; - PollCommand.ActivePolls.TryRemove(e.Server, out throwaway); - try - { - var results = participants.GroupBy(kvp => kvp.Value) - .ToDictionary(x => x.Key, x => x.Sum(kvp => 1)) - .OrderBy(kvp => kvp.Value); - - var totalVotesCast = results.Sum(kvp => kvp.Value); - if (totalVotesCast == 0) - { - await ch.SendMessage("📄 **No votes have been cast.**").ConfigureAwait(false); - return; - } - var closeMessage = $"--------------**POLL CLOSED**--------------\n" + - $"📄 , here are the results:\n"; - closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" + - $" has {kvp.Value} votes." + - $"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n"); - - await ch.SendMessage($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}").ConfigureAwait(false); - } - catch (Exception ex) - { - Console.WriteLine($"Error in poll game {ex}"); - } - } - - private async void Vote(object sender, MessageEventArgs e) - { - try - { - if (!e.Channel.IsPrivate) - return; - if (participants.ContainsKey(e.User)) - return; - - int vote; - if (!int.TryParse(e.Message.Text, out vote)) return; - if (vote < 1 || vote > answers.Length) - return; - if (participants.TryAdd(e.User, vote)) - { - await e.User.SendMessage($"Thanks for voting **{e.User.Name}**.").ConfigureAwait(false); - } - } - catch { } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/_Modules/Games/Commands/SpeedTyping.cs b/src/NadekoBot/_Modules/Games/Commands/SpeedTyping.cs deleted file mode 100644 index 8c8b9aa7..00000000 --- a/src/NadekoBot/_Modules/Games/Commands/SpeedTyping.cs +++ /dev/null @@ -1,195 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - - public static class SentencesProvider - { - internal static string GetRandomSentence() - { - var data = DbHandler.Instance.GetAllRows(); - try - { - return data.ToList()[new Random().Next(0, data.Count())].Text; - } - catch - { - return "Failed retrieving data from parse. Owner didn't add any articles to type using `typeadd`."; - } - } - } - - public class TypingGame - { - public const float WORD_VALUE = 4.5f; - private readonly Channel channel; - public string CurrentSentence; - public bool IsActive; - private readonly Stopwatch sw; - private readonly List finishedUserIds; - - public TypingGame(Channel channel) - { - this.channel = channel; - IsActive = false; - sw = new Stopwatch(); - finishedUserIds = new List(); - } - - public Channel Channell { get; internal set; } - - internal async Task Stop() - { - if (!IsActive) return false; - NadekoBot.Client.MessageReceived -= AnswerReceived; - finishedUserIds.Clear(); - IsActive = false; - sw.Stop(); - sw.Reset(); - await channel.Send("Typing contest stopped").ConfigureAwait(false); - return true; - } - - internal async Task Start() - { - while (true) - { - if (IsActive) return; // can't start running game - IsActive = true; - CurrentSentence = SentencesProvider.GetRandomSentence(); - var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f); - await channel.SendMessage($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false); - - - var msg = await channel.SendMessage("Starting new typing contest in **3**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit("Starting new typing contest in **2**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit("Starting new typing contest in **1**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit($":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:").ConfigureAwait(false); - sw.Start(); - HandleAnswers(); - - while (i > 0) - { - await Task.Delay(1000).ConfigureAwait(false); - i--; - if (!IsActive) - return; - } - - await Stop().ConfigureAwait(false); - } - } - - private void HandleAnswers() - { - NadekoBot.Client.MessageReceived += AnswerReceived; - } - - private async void AnswerReceived(object sender, MessageEventArgs e) - { - try - { - if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - - var guess = e.Message.RawText; - - var distance = CurrentSentence.LevenshteinDistance(guess); - var decision = Judge(distance, guess.Length); - if (decision && !finishedUserIds.Contains(e.User.Id)) - { - finishedUserIds.Add(e.User.Id); - await channel.Send($"{e.User.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false); - if (finishedUserIds.Count % 2 == 0) - { - await imsg.Channel.SendMessageAsync($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:").ConfigureAwait(false); - } - } - } - catch { } - } - - private bool Judge(int errors, int textLength) => errors <= textLength / 25; - - } - - internal class SpeedTyping : DiscordCommand - { - - public static ConcurrentDictionary RunningContests; - - public SpeedTyping(DiscordModule module) : base(module) - { - RunningContests = new ConcurrentDictionary(); - } - - public Func DoFunc() => - async e => - { - var game = RunningContests.GetOrAdd(e.User.Server.Id, id => new TypingGame(e.Channel)); - - if (game.IsActive) - { - await imsg.Channel.SendMessageAsync( - $"Contest already running in " + - $"{game.Channell.Mention} channel.") - .ConfigureAwait(false); - } - else - { - await game.Start().ConfigureAwait(false); - } - }; - - private Func QuitFunc() => - async e => - { - TypingGame game; - if (RunningContests.TryRemove(e.User.Server.Id, out game)) - { - await game.Stop().ConfigureAwait(false); - return; - } - await imsg.Channel.SendMessageAsync("No contest to stop on this channel.").ConfigureAwait(false); - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "typestart") - .Description($"Starts a typing contest. | `{Prefix}typestart`") - .Do(DoFunc()); - - cgb.CreateCommand(Module.Prefix + "typestop") - .Description($"Stops a typing contest on the current channel. | `{Prefix}typestop`") - .Do(QuitFunc()); - - cgb.CreateCommand(Module.Prefix + "typeadd") - .Description($"Adds a new article to the typing contest. Owner only. | `{Prefix}typeadd wordswords`") - .Parameter("text", ParameterType.Unparsed) - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return; - - DbHandler.Instance.Connection.Insert(new TypingArticle - { - Text = e.GetArg("text"), - DateAdded = DateTime.Now - }); - - await imsg.Channel.SendMessageAsync("Added new article for typing game.").ConfigureAwait(false); - }); - } - } -} diff --git a/src/NadekoBot/_Modules/Games/Commands/TriviaCommand.cs b/src/NadekoBot/_Modules/Games/Commands/TriviaCommand.cs deleted file mode 100644 index 6981597f..00000000 --- a/src/NadekoBot/_Modules/Games/Commands/TriviaCommand.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Games.Commands.Trivia; -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace NadekoBot.Modules.Games.Commands -{ - internal class TriviaCommands : DiscordCommand - { - public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); - - public TriviaCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "t") - .Description($"Starts a game of trivia. You can add nohint to prevent hints." + - "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + - $" |`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") - .Parameter("args", ParameterType.Multiple) - .Do(async e => - { - TriviaGame trivia; - if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - { - var showHints = !e.Args.Contains("nohint"); - var number = e.Args.Select(s => - { - int num; - return new Tuple(int.TryParse(s, out num), num); - }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); - if (number < 0) - return; - var triviaGame = new TriviaGame(e, showHints, number == 0 ? 10 : number); - if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) - await imsg.Channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); - else - await triviaGame.StopGame().ConfigureAwait(false); - } - else - await imsg.Channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "tl") - .Description($"Shows a current trivia leaderboard. | `{Prefix}tl`") - .Do(async e => - { - TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - await imsg.Channel.SendMessageAsync(trivia.GetLeaderboard()).ConfigureAwait(false); - else - await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "tq") - .Description($"Quits current trivia after current question. | `{Prefix}tq`") - .Do(async e => - { - TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - { - await trivia.StopGame().ConfigureAwait(false); - } - else - await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); - }); - } - } -} diff --git a/src/NadekoBot/_Modules/Games/GamesModule.cs b/src/NadekoBot/_Modules/Games/GamesModule.cs deleted file mode 100644 index 8c74a47f..00000000 --- a/src/NadekoBot/_Modules/Games/GamesModule.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; - -namespace NadekoBot.Modules.Games -{ - internal class GamesModule : DiscordModule - { - private readonly Random rng = new Random(); - - public GamesModule() - { - commands.Add(new TriviaCommands(this)); - commands.Add(new SpeedTyping(this)); - commands.Add(new PollCommand(this)); - commands.Add(new PlantPick(this)); - commands.Add(new Bomberman(this)); - commands.Add(new Leet(this)); - //commands.Add(new BetrayGame(this)); - - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Games; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "choose") - .Description($"Chooses a thing from a list of things | `{Prefix}choose Get up;Sleep;Sleep more`") - .Parameter("list", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("list"); - if (string.IsNullOrWhiteSpace(arg)) - return; - var list = arg.Split(';'); - if (list.Count() < 2) - return; - await imsg.Channel.SendMessageAsync(list[rng.Next(0, list.Length)]).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "8ball") - .Description($"Ask the 8ball a yes/no question. | `{Prefix}8ball should i do something`") - .Parameter("question", ParameterType.Unparsed) - .Do(async e => - { - var question = e.GetArg("question"); - if (string.IsNullOrWhiteSpace(question)) - return; - try - { - await imsg.Channel.SendMessageAsync( - $":question: `Question` __**{question}**__ \n🎱 `8Ball Answers` __**{NadekoBot.Config._8BallResponses[rng.Next(0, NadekoBot.Config._8BallResponses.Length)]}**__") - .ConfigureAwait(false); - } - catch { } - }); - - cgb.CreateCommand(Prefix + "rps") - .Description($"Play a game of rocket paperclip scissors with Nadeko. | `{Prefix}rps scissors`") - .Parameter("input", ParameterType.Required) - .Do(async e => - { - var input = e.GetArg("input").Trim(); - int pick; - switch (input) - { - case "r": - case "rock": - case "rocket": - pick = 0; - break; - case "p": - case "paper": - case "paperclip": - pick = 1; - break; - case "scissors": - case "s": - pick = 2; - break; - default: - return; - } - var nadekoPick = new Random().Next(0, 3); - var msg = ""; - if (pick == nadekoPick) - msg = $"It's a draw! Both picked :{GetRPSPick(pick)}:"; - else if ((pick == 0 && nadekoPick == 1) || - (pick == 1 && nadekoPick == 2) || - (pick == 2 && nadekoPick == 0)) - msg = $"{NadekoBot.BotMention} won! :{GetRPSPick(nadekoPick)}: beats :{GetRPSPick(pick)}:"; - else - msg = $"{e.User.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(nadekoPick)}:"; - - await imsg.Channel.SendMessageAsync(msg).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "linux") - .Description($"Prints a customizable Linux interjection | `{Prefix}linux Spyware Windows`") - .Parameter("gnu", ParameterType.Required) - .Parameter("linux", ParameterType.Required) - .Do(async e => - { - var guhnoo = e.Args[0]; - var loonix = e.Args[1]; - - await imsg.Channel.SendMessageAsync( -$@" -I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. - -Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. - -There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}. -").ConfigureAwait(false); - }); - }); - } - - private string GetRPSPick(int i) - { - if (i == 0) - return "rocket"; - else if (i == 1) - return "paperclip"; - else - return "scissors"; - } - } -}