diff --git a/.gitmodules b/.gitmodules index 1fa69c2d..3f0608a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "discord.net"] path = discord.net url = git://github.com/kwoth/discord.net.git +[submodule "ffmpeg-installer"] + path = ffmpeg-installer + url = https://github.com/kwoth/ffmpeg-installer diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index 41037d4a..1b2c3ea4 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -35,11 +35,9 @@ ________________________________________________________________________________ #### Setting Up NadekoBot For Music ###### Setting up `ffmpeg` with installer: 1) Google Account -2) Soundcloud Account (if you want soundcloud support) - -3) Download installer here: http://luxcaeli.de/owncloud/s/fIxSgh4Nde3Td6e/download - -4) Run the installer +2) Soundcloud Account (if you want soundcloud support) +3) Download installer here: https://goo.gl/lQZnsH (pick the one for your system, either 32 or 64bit and then click 'download') +4) Run the installer 5) Follow these steps on how to setup API keys: - Go to https://console.developers.google.com and log in. diff --git a/LinuxSetup.md b/LinuxSetup.md index 1295edc5..c29c8408 100644 --- a/LinuxSetup.md +++ b/LinuxSetup.md @@ -222,6 +222,8 @@ Type/ Copy and hit **Enter**. Now time to **move bot to background** and to do that, press **CTRL+B+D** (this will ditach the nadeko session using TMUX), and you can finally close PuTTY now. +Copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in this link: `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with it. Go to that link and you will be able to add your bot to your server. + **NOW YOU HAVE YOUR OWN NADEKO BOT** `Thanks to Kwoth <3` ######SOME MORE INFO (JUST TO KNOW): diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs index a4071ded..098e4732 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/NadekoBot/Classes/Extensions.cs @@ -138,7 +138,7 @@ namespace NadekoBot.Extensions /// /// /// - public static void Shuffle(this IList list) + public static IList Shuffle(this IList list) { // Thanks to @Joe4Evr for finding a bug in the old version of the shuffle @@ -160,6 +160,7 @@ namespace NadekoBot.Extensions list[k] = list[n]; list[n] = value; } + return list; } /// @@ -303,6 +304,15 @@ namespace NadekoBot.Extensions public static int GiB(this int value) => value.MiB() * 1024; public static int GB(this int value) => value.MB() * 1000; + public static ulong KiB(this ulong value) => value * 1024; + public static ulong KB(this ulong value) => value * 1000; + + public static ulong MiB(this ulong value) => value.KiB() * 1024; + public static ulong MB(this ulong value) => value.KB() * 1000; + + public static ulong GiB(this ulong value) => value.MiB() * 1024; + public static ulong GB(this ulong value) => value.MB() * 1000; + public static Stream ToStream(this Image img, System.Drawing.Imaging.ImageFormat format = null) { if (format == null) @@ -362,5 +372,7 @@ namespace NadekoBot.Extensions return sw.BaseStream; } + public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds; + } } diff --git a/NadekoBot/Classes/NadekoStats.cs b/NadekoBot/Classes/NadekoStats.cs index 66ef0984..3ccbc272 100644 --- a/NadekoBot/Classes/NadekoStats.cs +++ b/NadekoBot/Classes/NadekoStats.cs @@ -4,8 +4,10 @@ using NadekoBot.Extensions; using NadekoBot.Modules.Administration.Commands; using NadekoBot.Modules.Music; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; @@ -41,7 +43,23 @@ namespace NadekoBot var commandService = NadekoBot.Client.GetService(); statsStopwatch.Start(); + commandService.CommandExecuted += StatsCollector_RanCommand; + commandService.CommandFinished += CommandService_CommandFinished; + commandService.CommandErrored += (s, e) => + { + try + { + if (e.ErrorType == CommandErrorType.Exception) + File.AppendAllText("errors.txt", $@"Command: {e.Command} +{e.Exception} +------------------------------------- +"); + } + catch { + Console.WriteLine("Command errored errorring"); + } + }; Task.Run(StartCollecting); @@ -97,7 +115,7 @@ namespace NadekoBot if (e.Channel.IsPrivate) return; if (e.Channel.Type == ChannelType.Text) - VoiceChannelsCount++; + TextChannelsCount--; else if (e.Channel.Type == ChannelType.Voice) VoiceChannelsCount--; } @@ -106,6 +124,7 @@ namespace NadekoBot carbonStatusTimer.Elapsed += async (s, e) => await SendUpdateToCarbon().ConfigureAwait(false); carbonStatusTimer.Start(); } + HttpClient carbonClient = new HttpClient(); private async Task SendUpdateToCarbon() { @@ -176,9 +195,11 @@ namespace NadekoBot private async Task StartCollecting() { + var statsSw = new Stopwatch(); while (true) { await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false); + statsSw.Start(); try { var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); @@ -195,6 +216,13 @@ namespace NadekoBot ConnectedServers = connectedServers, DateAdded = DateTime.Now }); + + statsSw.Stop(); + var clr = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine($"--------------\nCollecting stats finished in {statsSw.Elapsed.TotalSeconds}s\n-------------"); + Console.ForegroundColor = clr; + statsSw.Reset(); } catch { @@ -204,9 +232,21 @@ namespace NadekoBot } } + private static ConcurrentDictionary commandTracker = new ConcurrentDictionary(); + + private void CommandService_CommandFinished(object sender, CommandEventArgs e) + { + DateTime dt; + if (!commandTracker.TryGetValue(e.Message.Id, out dt)) + return; + Console.WriteLine($">>COMMAND ENDED after *{(DateTime.UtcNow - dt).TotalSeconds}s*\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); + } + private async void StatsCollector_RanCommand(object sender, CommandEventArgs e) { - Console.WriteLine($">> Cmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); + commandTracker.TryAdd(e.Message.Id, DateTime.UtcNow); + Console.WriteLine($">>COMMAND STARTED\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); +#if !NADEKO_RELEASE await Task.Run(() => { try @@ -230,6 +270,7 @@ namespace NadekoBot Console.WriteLine(ex); } }).ConfigureAwait(false); +#endif } } -} \ No newline at end of file +} diff --git a/NadekoBot/Classes/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs index 43bfbe37..7f26e1da 100644 --- a/NadekoBot/Classes/SearchHelper.cs +++ b/NadekoBot/Classes/SearchHelper.cs @@ -364,9 +364,7 @@ namespace NadekoBot.Classes } var httpResponse = (await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) as HttpWebResponse; - if (httpResponse == null) return "HTTP_RESPONSE_ERROR"; var responseStream = httpResponse.GetResponseStream(); - if (responseStream == null) return "RESPONSE_STREAM ERROR"; using (var streamReader = new StreamReader(responseStream)) { var responseText = await streamReader.ReadToEndAsync().ConfigureAwait(false); @@ -375,6 +373,7 @@ namespace NadekoBot.Classes } catch (Exception ex) { + Console.WriteLine("Shortening of this url failed: " + url); Console.WriteLine(ex.ToString()); return url; } diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index ff42f0fd..9142e574 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/NadekoBot/Classes/ServerSpecificConfig.cs @@ -6,6 +6,8 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace NadekoBot.Classes { @@ -27,9 +29,12 @@ namespace NadekoBot.Classes { configs = JsonConvert .DeserializeObject>( - File.ReadAllText(filePath), new JsonSerializerSettings() { - Error = (s,e) => { - if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") { + File.ReadAllText(filePath), new JsonSerializerSettings() + { + Error = (s, e) => + { + if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") + { e.ErrorContext.Handled = true; } } @@ -52,14 +57,19 @@ namespace NadekoBot.Classes public ServerSpecificConfig Of(ulong id) => configs.GetOrAdd(id, _ => new ServerSpecificConfig()); - private readonly object saveLock = new object(); + private readonly SemaphoreSlim saveLock = new SemaphoreSlim(1, 1); - public void Save() + public async Task Save() { - lock (saveLock) + await saveLock.WaitAsync(); + try { File.WriteAllText(filePath, JsonConvert.SerializeObject(configs, Formatting.Indented)); } + finally + { + saveLock.Release(); + } } } @@ -245,7 +255,7 @@ namespace NadekoBot.Classes LogserverIgnoreChannels = new ObservableCollection(); } - public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; + public event PropertyChangedEventHandler PropertyChanged = async delegate { await SpecificConfigurations.Default.Save().ConfigureAwait(false); }; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index ab6dafae..036687aa 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -70,7 +70,7 @@ namespace NadekoBot.Modules.Administration { var conf = SpecificConfigurations.Default.Of(e.Server.Id); conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand; - Classes.JSONModels.ConfigHandler.SaveConfig(); + await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); if (conf.AutoDeleteMessagesOnCommand) await e.Channel.SendMessage("❗`Now automatically deleting successfull command invokations.`"); else @@ -90,7 +90,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "setrole").Alias(Prefix + "sr") - .Description("Sets a role for a given user. | .sr @User Guest") + .Description($"Sets a role for a given user. | `{Prefix}sr @User Guest`") .Parameter("user_name", ParameterType.Required) .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) @@ -133,7 +133,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "removerole").Alias(Prefix + "rr") - .Description("Removes a role from a given user. | .rr @User Admin") + .Description($"Removes a role from a given user. | `{Prefix}rr @User Admin`") .Parameter("user_name", ParameterType.Required) .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) @@ -204,7 +204,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "removeallroles").Alias(Prefix + "rar") - .Description("Removes all roles from a mentioned user. | .rar @User") + .Description($"Removes all roles from a mentioned user. | `{Prefix}rar @User`") .Parameter("user_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -230,7 +230,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "createrole").Alias(Prefix + "cr") - .Description("Creates a role with a given name.**Usage**: `.r Awesome Role`") + .Description($"Creates a role with a given name. | `{Prefix}cr Awesome Role`") .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -253,7 +253,7 @@ namespace NadekoBot.Modules.Administration .Parameter("r", ParameterType.Optional) .Parameter("g", ParameterType.Optional) .Parameter("b", ParameterType.Optional) - .Description("Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55`") + .Description($"Set a role's color to the hex or 0-255 rgb color value provided. | `{Prefix}rc Admin 255 200 100` or `{Prefix}rc Admin ffba55`") .Do(async e => { if (!e.User.ServerPermissions.ManageRoles) @@ -298,7 +298,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "ban").Alias(Prefix + "b") .Parameter("user", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) - .Description("Bans a user by id or name with an optional message. | .b \"@some Guy\" Your behaviour is toxic.") + .Description($"Bans a user by id or name with an optional message. | `{Prefix}b \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -333,7 +333,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb") .Parameter("user", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) - .Description("Bans and then unbans a user by id or name with an optional message. | .sb \"@some Guy\" Your behaviour is toxic.") + .Description($"Bans and then unbans a user by id or name with an optional message. | `{Prefix}sb \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -369,7 +369,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "kick").Alias(Prefix + "k") .Parameter("user") .Parameter("msg", ParameterType.Unparsed) - .Description("Kicks a mentioned user.") + .Description($"Kicks a mentioned user. | `{Prefix}k \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -400,7 +400,7 @@ namespace NadekoBot.Modules.Administration } }); cgb.CreateCommand(Prefix + "mute") - .Description("Mutes mentioned user or users.") + .Description($"Mutes mentioned user or users. | `{Prefix}mute \"@Someguy\"` or `{Prefix}mute \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -426,7 +426,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "unmute") - .Description("Unmutes mentioned user or users.") + .Description($"Unmutes mentioned user or users. | `{Prefix}unmute \"@Someguy\"` or `{Prefix}unmute \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -453,7 +453,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "deafen") .Alias(Prefix + "deaf") - .Description("Deafens mentioned user or users") + .Description($"Deafens mentioned user or users | `{Prefix}deaf \"@Someguy\"` or `{Prefix}deaf \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -480,7 +480,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "undeafen") .Alias(Prefix + "undef") - .Description("Undeafens mentioned user or users") + .Description($"Undeafens mentioned user or users | `{Prefix}undef \"@Someguy\"` or `{Prefix}undef \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -507,7 +507,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "delvoichanl") .Alias(Prefix + "dvch") - .Description("Deletes a voice channel with a given name.") + .Description($"Deletes a voice channel with a given name. | `{Prefix}dvch VoiceChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -530,7 +530,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "creatvoichanl") .Alias(Prefix + "cvch") - .Description("Creates a new voice channel with a given name.") + .Description($"Creates a new voice channel with a given name. | `{Prefix}cvch VoiceChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -550,7 +550,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "deltxtchanl") .Alias(Prefix + "dtch") - .Description("Deletes a text channel with a given name.") + .Description($"Deletes a text channel with a given name. | `{Prefix}dtch TextChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -572,7 +572,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "creatxtchanl") .Alias(Prefix + "ctch") - .Description("Creates a new text channel with a given name.") + .Description($"Creates a new text channel with a given name. | `{Prefix}ctch TextChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -604,7 +604,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "setchanlname") .Alias(Prefix + "schn") - .Description("Changed the name of the current channel.") + .Description($"Changed the name of the current channel.| `{Prefix}schn NewName`") .AddCheck(SimpleCheckers.ManageChannels()) .Parameter("name", ParameterType.Unparsed) .Do(async e => @@ -636,7 +636,8 @@ namespace NadekoBot.Modules.Administration Message[] msgs; if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear nadeko's messages, no permissions required { - msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray(); + msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false));//.Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray(); + msgs = msgs.Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray(); if (!msgs.Any()) return; await e.Channel.DeleteMessages(msgs).ConfigureAwait(false); @@ -684,7 +685,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "setname") .Alias(Prefix + "newnm") - .Description("Give the bot a new name. **Bot Owner Only!**") + .Description($"Give the bot a new name. **Bot Owner Only!** | {Prefix}newnm BotName") .Parameter("new_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -696,7 +697,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "newavatar") .Alias(Prefix + "setavatar") - .Description("Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`") + .Description($"Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `{Prefix}setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`") .Parameter("img", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -717,7 +718,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "setgame") - .Description("Sets the bots game. **Bot Owner Only!**") + .Description($"Sets the bots game. **Bot Owner Only!** | `{Prefix}setgame Playing with kwoth`") .Parameter("set_game", ParameterType.Unparsed) .Do(e => { @@ -727,7 +728,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "send") - .Description("Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`") + .Description($"Send a message to someone on a different server through the bot. **Bot Owner Only!** | `{Prefix}send serverid|u:user_id Send this to a user!` or `{Prefix}send serverid|c:channel_id Send this to a channel!`") .Parameter("ids", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) @@ -775,7 +776,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "mentionrole") .Alias(Prefix + "menro") - .Description("Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission.") + .Description($"Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `{Prefix}menro RoleName`") .Parameter("roles", ParameterType.Unparsed) .Do(async e => { @@ -828,7 +829,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "donadd") - .Description("Add a donator to the database.") + .Description($"Add a donator to the database. | `.donadd Donate Amount`") .Parameter("donator") .Parameter("amount") .AddCheck(SimpleCheckers.OwnerOnly()) @@ -854,7 +855,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "announce") - .Description($"Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | {Prefix}announce Useless spam") + .Description($"Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `{Prefix}announce Useless spam`") .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -868,7 +869,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "savechat") - .Description("Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150`") + .Description($"Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `{Prefix}savechat 150`") .Parameter("cnt", ParameterType.Required) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index fcd970a7..a01f9a8d 100644 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Administration.Commands NadekoBot.Config.CustomReactions[name].Add(message); else NadekoBot.Config.CustomReactions.Add(name, new System.Collections.Generic.List() { message }); - await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); + await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"Added {name} : {message}").ConfigureAwait(false); }); @@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration.Commands index = index - 1; NadekoBot.Config.CustomReactions[name][index] = msg; - await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); + await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false); }); @@ -183,7 +183,7 @@ namespace NadekoBot.Modules.Administration.Commands NadekoBot.Config.CustomReactions.Remove(name); message = $"Deleted custom reaction: `{name}`"; } - await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); + await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage(message).ConfigureAwait(false); }); } diff --git a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs index aa7570cb..2d22d870 100644 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Timers; +using Timer = System.Timers.Timer; namespace NadekoBot.Modules.Administration.Commands { @@ -36,7 +38,7 @@ namespace NadekoBot.Modules.Administration.Commands {"%trivia%", () => Games.Commands.TriviaCommands.RunningTrivias.Count.ToString()} }; - private readonly object playingPlaceholderLock = new object(); + private readonly SemaphoreSlim playingPlaceholderLock = new SemaphoreSlim(1,1); public PlayingRotate(DiscordModule module) : base(module) { @@ -47,7 +49,9 @@ namespace NadekoBot.Modules.Administration.Commands { i++; var status = ""; - lock (playingPlaceholderLock) + //wtf am i doing, just use a queue ffs + await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); + try { if (PlayingPlaceholders.Count == 0 || NadekoBot.Config.RotatingStatuses.Count == 0 @@ -59,6 +63,7 @@ namespace NadekoBot.Modules.Administration.Commands status = PlayingPlaceholders.Aggregate(status, (current, kvp) => current.Replace(kvp.Key, kvp.Value())); } + finally { playingPlaceholderLock.Release(); } if (string.IsNullOrWhiteSpace(status)) return; await Task.Run(() => { NadekoBot.Client.SetGame(status); }); @@ -71,14 +76,18 @@ namespace NadekoBot.Modules.Administration.Commands public Func DoFunc() => async e => { - lock (playingPlaceholderLock) + await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); + try { if (timer.Enabled) timer.Stop(); else timer.Start(); NadekoBot.Config.IsRotatingStatus = timer.Enabled; - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); + } + finally { + playingPlaceholderLock.Release(); } await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`").ConfigureAwait(false); }; @@ -102,10 +111,15 @@ namespace NadekoBot.Modules.Administration.Commands var arg = e.GetArg("text"); if (string.IsNullOrWhiteSpace(arg)) return; - lock (playingPlaceholderLock) + await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); + try { NadekoBot.Config.RotatingStatuses.Add(arg); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig(); + } + finally + { + playingPlaceholderLock.Release(); } await e.Channel.SendMessage("πŸ†— `Added a new playing string.`").ConfigureAwait(false); }); @@ -137,14 +151,15 @@ namespace NadekoBot.Modules.Administration.Commands var arg = e.GetArg("number"); int num; string str; - lock (playingPlaceholderLock) - { + await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); + try { if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count) return; str = NadekoBot.Config.RotatingStatuses[num - 1]; NadekoBot.Config.RotatingStatuses.RemoveAt(num - 1); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); } + finally { playingPlaceholderLock.Release(); } await e.Channel.SendMessage($"πŸ†— `Removed playing string #{num}`({str})").ConfigureAwait(false); }); } diff --git a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index 2ef54772..1890eb5b 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -76,7 +76,7 @@ namespace NadekoBot.Modules.Administration.Commands var config = SpecificConfigurations.Default.Of(e.Server.Id); var msg = new StringBuilder($"There are `{config.ListOfSelfAssignableRoles.Count}` self assignable roles:\n"); var toRemove = new HashSet(); - foreach (var roleId in config.ListOfSelfAssignableRoles.OrderBy(r=>r.ToString())) + foreach (var roleId in config.ListOfSelfAssignableRoles.OrderBy(r => r.ToString())) { var role = e.Server.GetRole(roleId); if (role == null) @@ -98,7 +98,7 @@ namespace NadekoBot.Modules.Administration.Commands - cgb.CreateCommand(Module.Prefix + "togglexclsar").Alias(Module.Prefix +"tesar") + cgb.CreateCommand(Module.Prefix + "togglexclsar").Alias(Module.Prefix + "tesar") .Description("toggle whether the self-assigned roles should be exclusive") .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -146,12 +146,13 @@ namespace NadekoBot.Modules.Administration.Commands { await e.User.AddRoles(role).ConfigureAwait(false); } - catch(HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError) + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError) { } - catch (Exception) + catch (Exception ex) { await e.Channel.SendMessage($":anger:`I am unable to add that role to you. I can't add roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); + return; } var msg = await e.Channel.SendMessage($":ok:You now have {role.Name} role.").ConfigureAwait(false); await Task.Delay(3000).ConfigureAwait(false); diff --git a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs b/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs deleted file mode 100644 index ae63a7db..00000000 --- a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs +++ /dev/null @@ -1,46 +0,0 @@ -ο»Ώusing Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class VoiceNotificationCommand : DiscordCommand - { - - //voicechannel/text channel - private readonly ConcurrentDictionary subscribers = new ConcurrentDictionary(); - - public Func DoFunc() => async e => - { - var arg = e.GetArg("voice_name"); - if (string.IsNullOrWhiteSpace("voice_name")) - return; - var voiceChannel = e.Server.FindChannels(arg, ChannelType.Voice).FirstOrDefault(); - if (voiceChannel == null) - return; - if (subscribers.ContainsKey(voiceChannel)) - { - await e.Channel.SendMessage("`Voice channel notifications disabled.`").ConfigureAwait(false); - return; - } - if (subscribers.TryAdd(voiceChannel, e.Channel)) - { - await e.Channel.SendMessage("`Voice channel notifications enabled.`").ConfigureAwait(false); - } - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "voicenotif") - .Description("Enables notifications on who joined/left the voice channel. |.voicenotif Karaoke club") - .Parameter("voice_name", ParameterType.Unparsed) - .Do(DoFunc()); - } - - public VoiceNotificationCommand(DiscordModule module) : base(module) { } - } -} diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs index 668afd73..1ae491ad 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -1,9 +1,8 @@ -ο»Ώusing Discord.Commands; +ο»Ώusing Newtonsoft.Json; using System; using System.Linq; using System.Text; -using System.Threading; -using System.Threading.Tasks; +//using Manatee.Json.Serialization; namespace NadekoBot.Classes.ClashOfClans { @@ -11,16 +10,22 @@ namespace NadekoBot.Classes.ClashOfClans { One, Two, Three } + public enum WarState + { + Started, Ended, Created + } + [System.Serializable] internal class Caller { - public string CallUser { get; } + public string CallUser { get; set; } - public DateTime TimeAdded { get; private set; } + public DateTime TimeAdded { get; set; } - public bool BaseDestroyed { get; internal set; } + public bool BaseDestroyed { get; set; } public int Stars { get; set; } = 3; + public Caller() { } public Caller(string callUser, DateTime timeAdded, bool baseDestroyed) { @@ -31,7 +36,7 @@ namespace NadekoBot.Classes.ClashOfClans public void ResetTime() { - TimeAdded = DateTime.Now; + TimeAdded = DateTime.UtcNow; } public void Destroy() @@ -44,97 +49,84 @@ namespace NadekoBot.Classes.ClashOfClans { private static TimeSpan callExpire => new TimeSpan(2, 0, 0); - public string EnemyClan { get; } - public int Size { get; } + public string EnemyClan { get; set; } + public int Size { get; set; } - private Caller[] bases { get; } - private CancellationTokenSource[] baseCancelTokens; - private CancellationTokenSource endTokenSource { get; } = new CancellationTokenSource(); - public event Action OnUserTimeExpired = delegate { }; - public event Action OnWarEnded = delegate { }; - public bool Started { get; set; } = false; + public Caller[] Bases { get; set; } + public WarState WarState { get; set; } = WarState.Created; + //public bool Started { get; set; } = false; + public DateTime StartedAt { get; set; } + //public bool Ended { get; private set; } = false; - public ClashWar(string enemyClan, int size, CommandEventArgs e) + public ulong ServerId { get; set; } + public ulong ChannelId { get; set; } + + [JsonIgnore] + public Discord.Channel Channel { get; internal set; } + + /// + /// This init is purely for the deserialization + /// + public ClashWar() { } + + public ClashWar(string enemyClan, int size, ulong serverId, ulong channelId) { this.EnemyClan = enemyClan; this.Size = size; - this.bases = new Caller[size]; - this.baseCancelTokens = new CancellationTokenSource[size]; + this.Bases = new Caller[size]; + this.ServerId = serverId; + this.ChannelId = channelId; + this.Channel = NadekoBot.Client.Servers.FirstOrDefault(s => s.Id == serverId)?.TextChannels.FirstOrDefault(c => c.Id == channelId); } internal void End() { - if (endTokenSource.Token.IsCancellationRequested) return; - endTokenSource.Cancel(); - OnWarEnded(); + //Ended = true; + WarState = WarState.Ended; } internal void Call(string u, int baseNumber) { - if (baseNumber < 0 || baseNumber >= bases.Length) + if (baseNumber < 0 || baseNumber >= Bases.Length) throw new ArgumentException("Invalid base number"); - if (bases[baseNumber] != null) + if (Bases[baseNumber] != null) throw new ArgumentException("That base is already claimed."); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.BaseDestroyed == false && bases[i]?.CallUser == u) - throw new ArgumentException($"@{u} You already claimed a base #{i + 1}. You can't claim a new one."); + if (Bases[i]?.BaseDestroyed == false && Bases[i]?.CallUser == u) + throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one."); } - bases[baseNumber] = new Caller(u.Trim(), DateTime.Now, false); + Bases[baseNumber] = new Caller(u.Trim(), DateTime.UtcNow, false); } - internal async Task Start() + internal void Start() { - if (Started) - throw new InvalidOperationException(); - try + if (WarState == WarState.Started) + throw new InvalidOperationException("War already started"); + //if (Started) + // throw new InvalidOperationException(); + //Started = true; + WarState = WarState.Started; + StartedAt = DateTime.UtcNow; + foreach (var b in Bases.Where(b => b != null)) { - Started = true; - foreach (var b in bases.Where(b => b != null)) - { - b.ResetTime(); - } -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => await ClearArray()).ConfigureAwait(false); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - await Task.Delay(new TimeSpan(24, 0, 0), endTokenSource.Token).ConfigureAwait(false); - } - catch { } - finally - { - End(); + b.ResetTime(); } } + internal int Uncall(string user) { user = user.Trim(); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.CallUser != user) continue; - bases[i] = null; + if (Bases[i]?.CallUser != user) continue; + Bases[i] = null; return i; } throw new InvalidOperationException("You are not participating in that war."); } - private async Task ClearArray() - { - while (!endTokenSource.IsCancellationRequested) - { - await Task.Delay(5000).ConfigureAwait(false); - for (var i = 0; i < bases.Length; i++) - { - if (bases[i] == null) continue; - if (!bases[i].BaseDestroyed && DateTime.Now - bases[i].TimeAdded >= callExpire) - { - OnUserTimeExpired(bases[i].CallUser); - bases[i] = null; - } - } - } - } - public string ShortPrint() => $"`{EnemyClan}` ({Size} v {Size})"; @@ -143,24 +135,24 @@ namespace NadekoBot.Classes.ClashOfClans var sb = new StringBuilder(); sb.AppendLine($"πŸ”°**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**"); - if (!Started) + if (WarState == WarState.Created) sb.AppendLine("`not started`"); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i] == null) + if (Bases[i] == null) { sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); } else { - if (bases[i].BaseDestroyed) + if (Bases[i].BaseDestroyed) { - sb.AppendLine($"`{i + 1}.` βœ… `{bases[i].CallUser}` {new string('⭐', bases[i].Stars)}"); + sb.AppendLine($"`{i + 1}.` βœ… `{Bases[i].CallUser}` {new string('⭐', Bases[i].Stars)}"); } else { - var left = Started ? callExpire - (DateTime.Now - bases[i].TimeAdded) : callExpire; - sb.AppendLine($"`{i + 1}.` βœ… `{bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); + var left = (WarState == WarState.Started) ? callExpire - (DateTime.UtcNow - Bases[i].TimeAdded) : callExpire; + sb.AppendLine($"`{i + 1}.` βœ… `{Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); } } @@ -171,11 +163,11 @@ namespace NadekoBot.Classes.ClashOfClans internal int FinishClaim(string user, int stars = 3) { user = user.Trim(); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.BaseDestroyed != false || bases[i]?.CallUser != user) continue; - bases[i].BaseDestroyed = true; - bases[i].Stars = stars; + if (Bases[i]?.BaseDestroyed != false || Bases[i]?.CallUser != user) continue; + Bases[i].BaseDestroyed = true; + Bases[i].Stars = stars; return i; } throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs index 9c383509..37443900 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs @@ -1,12 +1,15 @@ ο»Ώusing Discord.Commands; using Discord.Modules; using NadekoBot.Classes.ClashOfClans; +using NadekoBot.Modules.Permissions.Classes; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; -using NadekoBot.Modules.Permissions.Classes; namespace NadekoBot.Modules.ClashOfClans { @@ -14,69 +17,172 @@ namespace NadekoBot.Modules.ClashOfClans { public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; - public static ConcurrentDictionary> ClashWars { get; } = new ConcurrentDictionary>(); + public static ConcurrentDictionary> ClashWars { get; set; } = new ConcurrentDictionary>(); private readonly object writeLock = new object(); + public ClashOfClansModule() + { + NadekoBot.OnReady += () => Task.Run(async () => + { + if (File.Exists("data/clashofclans/wars.json")) + { + try + { + var content = File.ReadAllText("data/clashofclans/wars.json"); + + var dict = JsonConvert.DeserializeObject>>(content); + + foreach (var cw in dict) + { + cw.Value.ForEach(war => + { + war.Channel = NadekoBot.Client.GetServer(war.ServerId)?.GetChannel(war.ChannelId); + if (war.Channel == null) + { + cw.Value.Remove(war); + } + } + ); + } + //urgh + ClashWars = new ConcurrentDictionary>(dict); + } + catch (Exception e) + { + Console.WriteLine("Could not load coc wars: " + e.Message); + } + + + } + //Can't this be disabled if the modules is disabled too :) + var callExpire = new TimeSpan(2, 0, 0); + var warExpire = new TimeSpan(23, 0, 0); + while (true) + { + try + { + var hash = ClashWars.GetHashCode(); + foreach (var cw in ClashWars) + { + foreach (var war in cw.Value) + { + await CheckWar(callExpire, war); + } + List newVal = new List(); + foreach (var w in cw.Value) + { + //We add when A: the war is not ended + if (w.WarState != WarState.Ended) + { + //and B: the war has not expired + if ((w.WarState == WarState.Started && DateTime.UtcNow - w.StartedAt <= warExpire) || w.WarState == WarState.Created) + { + newVal.Add(w); + } + } + } + //var newVal = cw.Value.Where(w => !(w.Ended || DateTime.UtcNow - w.StartedAt >= warExpire)).ToList(); + foreach (var exWar in cw.Value.Except(newVal)) + { + await exWar.Channel.SendMessage($"War against {exWar.EnemyClan} ({exWar.Size}v{exWar.Size}) has ended"); + } + + if (newVal.Count == 0) + { + List obj; + ClashWars.TryRemove(cw.Key, out obj); + } + else + { + ClashWars.AddOrUpdate(cw.Key, newVal, (x, s) => newVal); + } + } + if (hash != ClashWars.GetHashCode()) //something changed + { + Save(); + } + + + } + catch { } + await Task.Delay(5000); + } + }); + } + + private static void Save() + { + try + { + Directory.CreateDirectory("data/clashofclans"); + File.WriteAllText("data/clashofclans/wars.json", JsonConvert.SerializeObject(ClashWars, Formatting.Indented)); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + + private static async Task CheckWar(TimeSpan callExpire, ClashWar war) + { + var Bases = war.Bases; + for (var i = 0; i < Bases.Length; i++) + { + if (Bases[i] == null) continue; + if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire) + { + await war.Channel.SendMessage($"β—πŸ”°**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); + Bases[i] = null; + } + } + } + + #region commands public override void Install(ModuleManager manager) { manager.CreateCommands("", cgb => { - cgb.AddCheck(PermissionChecker.Instance); + cgb.AddCheck(PermissionChecker.Instance); - cgb.CreateCommand(Prefix + "createwar") - .Alias(Prefix + "cw") - .Description($"Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. |{Prefix}cw 15 The Enemy Clan") - .Parameter("size") - .Parameter("enemy_clan", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - List wars; - if (!ClashWars.TryGetValue(e.Server.Id, out wars)) - { - wars = new List(); - if (!ClashWars.TryAdd(e.Server.Id, wars)) - return; - } - var enemyClan = e.GetArg("enemy_clan"); - if (string.IsNullOrWhiteSpace(enemyClan)) - { - return; - } - int size; - if (!int.TryParse(e.GetArg("size"), out size) || size < 10 || size > 50 || size % 5 != 0) - { - await e.Channel.SendMessage("πŸ’’πŸ”° Not a Valid war size").ConfigureAwait(false); - return; - } - var cw = new ClashWar(enemyClan, size, e); - //cw.Start(); - wars.Add(cw); - cw.OnUserTimeExpired += async (u) => - { - try - { - await - e.Channel.SendMessage( - $"β—πŸ”°**Claim from @{u} for a war against {cw.ShortPrint()} has expired.**") - .ConfigureAwait(false); - } - catch { } - }; - cw.OnWarEnded += async () => - { - try - { - await e.Channel.SendMessage($"β—πŸ”°**War against {cw.ShortPrint()} ended.**").ConfigureAwait(false); - } - catch { } - }; - await e.Channel.SendMessage($"β—πŸ”°**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); - //war with the index X started. - }); + cgb.CreateCommand(Prefix + "createwar") + .Alias(Prefix + "cw") + .Description($"Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. |{Prefix}cw 15 The Enemy Clan") + .Parameter("size") + .Parameter("enemy_clan", ParameterType.Unparsed) + .Do(async e => + { + if (!e.User.ServerPermissions.ManageChannels) + return; + var enemyClan = e.GetArg("enemy_clan"); + if (string.IsNullOrWhiteSpace(enemyClan)) + { + return; + } + int size; + if (!int.TryParse(e.GetArg("size"), out size) || size < 10 || size > 50 || size % 5 != 0) + { + await e.Channel.SendMessage("πŸ’’πŸ”° Not a Valid war size").ConfigureAwait(false); + return; + } + List wars; + if (!ClashWars.TryGetValue(e.Server.Id, out wars)) + { + wars = new List(); + if (!ClashWars.TryAdd(e.Server.Id, wars)) + return; + } + + + var cw = new ClashWar(enemyClan, size, e.Server.Id, e.Channel.Id); + //cw.Start(); + + wars.Add(cw); + await e.Channel.SendMessage($"β—πŸ”°**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); + Save(); + //war with the index X started. + }); cgb.CreateCommand(Prefix + "startwar") .Alias(Prefix + "sw") @@ -93,14 +199,14 @@ namespace NadekoBot.Modules.ClashOfClans var war = warsInfo.Item1[warsInfo.Item2]; try { - var startTask = war.Start(); + war.Start(); await e.Channel.SendMessage($"πŸ”°**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); - await startTask.ConfigureAwait(false); } catch { - await e.Channel.SendMessage($"πŸ”°**WAR AGAINST {war.ShortPrint()} IS ALREADY STARTED**").ConfigureAwait(false); + await e.Channel.SendMessage($"πŸ”°**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false); } + Save(); }); cgb.CreateCommand(Prefix + "listwar") @@ -132,6 +238,7 @@ namespace NadekoBot.Modules.ClashOfClans } await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); return; + } //if number is not null, print the war needed var warsInfo = GetInfo(e); @@ -173,6 +280,7 @@ namespace NadekoBot.Modules.ClashOfClans var war = warsInfo.Item1[warsInfo.Item2]; war.Call(usr, baseNum - 1); await e.Channel.SendMessage($"πŸ”°**{usr}** claimed a base #{baseNum} for a war against {war.ShortPrint()}").ConfigureAwait(false); + Save(); } catch (Exception ex) { @@ -206,7 +314,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "unclaim") .Alias(Prefix + "uncall") .Alias(Prefix + "uc") - .Description($"Removes your claim from a certain war. Optional second argument denotes a person in whos place to unclaim | {Prefix}uc [war_number] [optional_other_name]") + .Description($"Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | {Prefix}uc [war_number] [optional_other_name]") .Parameter("number", ParameterType.Required) .Parameter("other_name", ParameterType.Unparsed) .Do(async e => @@ -226,6 +334,7 @@ namespace NadekoBot.Modules.ClashOfClans var war = warsInfo.Item1[warsInfo.Item2]; var baseNumber = war.Uncall(usr); await e.Channel.SendMessage($"πŸ”° @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false); + Save(); } catch (Exception ex) { @@ -246,12 +355,17 @@ namespace NadekoBot.Modules.ClashOfClans return; } warsInfo.Item1[warsInfo.Item2].End(); + await e.Channel.SendMessage($"β—πŸ”°**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false); var size = warsInfo.Item1[warsInfo.Item2].Size; warsInfo.Item1.RemoveAt(warsInfo.Item2); + Save(); }); }); + } + #endregion + private async Task FinishClaim(CommandEventArgs e, int stars = 3) { @@ -271,6 +385,7 @@ namespace NadekoBot.Modules.ClashOfClans { var baseNum = war.FinishClaim(usr, stars); await e.Channel.SendMessage($"β—πŸ”°{e.User.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false); + Save(); } catch (Exception ex) { diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs index 328be562..7432803f 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/NadekoBot/Modules/Conversations/Conversations.cs @@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Conversations cgb.AddCheck(PermissionChecker.Instance); cgb.CreateCommand("..") - .Description("Adds a new quote with the specified name (single word) and message (no limit). | .. abc My message") + .Description("Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message`") .Parameter("keyword", ParameterType.Required) .Parameter("text", ParameterType.Unparsed) .Do(async e => @@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Conversations }); cgb.CreateCommand("...") - .Description("Shows a random quote with a specified name. | .. abc") + .Description("Shows a random quote with a specified name. | `... abc`") .Parameter("keyword", ParameterType.Required) .Do(async e => { diff --git a/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs new file mode 100644 index 00000000..ae6c6819 --- /dev/null +++ b/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs @@ -0,0 +1,289 @@ +ο»Ώusing NadekoBot.Classes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Discord.Commands; +using System.Collections.Concurrent; +using Discord; +using NadekoBot.Extensions; +using System.Threading; + +namespace NadekoBot.Modules.Gambling.Commands +{ + class AnimalRacing : DiscordCommand + { + public static ConcurrentDictionary AnimalRaces = new ConcurrentDictionary(); + + public AnimalRacing(DiscordModule module) : base(module) + { + } + + internal override void Init(CommandGroupBuilder cgb) + { + cgb.CreateCommand(Prefix + "race") + .Description("Starts a new animal race.") + .Do(e => { + var ar = new AnimalRace(e.Server.Id, e.Channel); + if (ar.Fail) + { + return; + } + }); + + + cgb.CreateCommand(Prefix + "joinrace") + .Alias(Prefix + "jr") + .Description("Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`") + .Parameter("amount", ParameterType.Optional) + .Do(async e => { + + int amount; + if (!int.TryParse(e.GetArg("amount"), out amount) || amount < 0) + amount = 0; + + var userFlowers = GamblingModule.GetUserFlowers(e.User.Id); + + if (userFlowers < amount) + { + await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {NadekoBot.Config.CurrencyName}s. You only have {userFlowers}{NadekoBot.Config.CurrencySign}.").ConfigureAwait(false); + return; + } + + if (amount > 0) + await FlowersHandler.RemoveFlowers(e.User, "BetRace", (int)amount, true).ConfigureAwait(false); + + AnimalRace ar; + if (!AnimalRaces.TryGetValue(e.Server.Id, out ar)) { + await e.Channel.SendMessage("No race exists on this server"); + } + await ar.JoinRace(e.User, amount); + + }); + } + + public class AnimalRace + { + + private ConcurrentQueue animals = new ConcurrentQueue(NadekoBot.Config.RaceAnimals.Shuffle()); + + public bool Fail { get; internal set; } + + public List participants = new List(); + private ulong serverId; + private int messagesSinceGameStarted = 0; + + public Channel raceChannel { get; set; } + public bool Started { get; private set; } = false; + + public AnimalRace(ulong serverId, Channel ch) + { + this.serverId = serverId; + this.raceChannel = ch; + if (!AnimalRaces.TryAdd(serverId, this)) + { + Fail = true; + return; + } + var cancelSource = new CancellationTokenSource(); + var token = cancelSource.Token; + var fullgame = CheckForFullGameAsync(token); + Task.Run(async () => + { + try + { + await raceChannel.SendMessage($"🏁`Race is starting in 20 seconds or when the room is full. Type $jr to join the race.`"); + var t = await Task.WhenAny(Task.Delay(20000, token), fullgame); + Started = true; + cancelSource.Cancel(); + if (t == fullgame) + { + await raceChannel.SendMessage("🏁`Race full, starting right now!`"); + } + else if (participants.Count > 1) + { + await raceChannel.SendMessage("🏁`Game starting with " + participants.Count + " praticipants.`"); + } + else + { + await raceChannel.SendMessage("🏁`Race failed to start since there was not enough participants.`"); + var p = participants.FirstOrDefault(); + if (p != null) + await FlowersHandler.AddFlowersAsync(p.User, "BetRace", p.AmountBet, true).ConfigureAwait(false); + End(); + return; + } + await Task.Run(StartRace); + End(); + } + catch { } + }); + } + + private void End() + { + AnimalRace throwaway; + AnimalRaces.TryRemove(serverId, out throwaway); + } + + private async Task StartRace() { + var rng = new Random(); + Participant winner = null; + Message msg = null; + int place = 1; + try + { + NadekoBot.Client.MessageReceived += Client_MessageReceived; + + while (!participants.All(p => p.Total >= 60)) + { + //update the state + participants.ForEach(p => + { + + p.Total += 1 + rng.Next(0, 10); + if (p.Total > 60) + { + p.Total = 60; + if (winner == null) + { + winner = p; + } + if (p.Place == 0) + p.Place = place++; + } + }); + + + //draw the state + + var text = $@"|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š| +{String.Join("\n", participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))} +|πŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸπŸ”š|"; + if (msg == null || messagesSinceGameStarted >= 10) // also resend the message if channel was spammed + { + if(msg != null) + try { await msg.Delete(); } catch { } + msg = await raceChannel.SendMessage(text); + messagesSinceGameStarted = 0; + } + else + await msg.Edit(text); + + await Task.Delay(2500); + } + } + finally + { + NadekoBot.Client.MessageReceived -= Client_MessageReceived; + } + + if (winner.AmountBet > 0) + { + var wonAmount = winner.AmountBet * (participants.Count - 1); + await FlowersHandler.AddFlowersAsync(winner.User, "Won a Race", wonAmount).ConfigureAwait(false); + await raceChannel.SendMessage($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{NadekoBot.Config.CurrencySign}!**"); + } + else + { + await raceChannel.SendMessage($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race!**"); + } + + } + + private void Client_MessageReceived(object sender, MessageEventArgs e) + { + if (e.Message.IsAuthor || e.Channel.IsPrivate || e.Channel != raceChannel) + return; + messagesSinceGameStarted++; + } + + private async Task CheckForFullGameAsync(CancellationToken cancelToken) { + while (animals.Count > 0) + { + await Task.Delay(100,cancelToken); + } + } + + public async Task JoinRace(User u, int amount = 0) + { + var animal = ""; + if (!animals.TryDequeue(out animal)) + { + await raceChannel.SendMessage($"{u.Mention} `There is no running race on this server.`"); + return false; + } + var p = new Participant(u, animal, amount); + if (participants.Contains(p)) + { + await raceChannel.SendMessage($"{u.Mention} `You already joined this race.`"); + return false; + } + if (Started) + { + await raceChannel.SendMessage($"{u.Mention} `Race is already started`"); + return false; + } + participants.Add(p); + await raceChannel.SendMessage($"{u.Mention} **joined the race as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {NadekoBot.Config.CurrencyName.SnPl(amount)}!**" : "**")); + return true; + } + } + + public class Participant + { + public User User { get; set; } + public string Animal { get; set; } + public int AmountBet { get; set; } + + public float Coeff { get; set; } + public int Total { get; set; } + + public int Place { get; set; } = 0; + + public Participant(User u, string a, int amount) + { + this.User = u; + this.Animal = a; + this.AmountBet = amount; + } + + public override int GetHashCode() + { + return User.GetHashCode(); + } + + public override bool Equals(object obj) + { + var p = obj as Participant; + return p == null? + false: + p.User == User; + } + + public override string ToString() + { + var str = new string('β€£', Total) + Animal; + if (Place == 0) + return str; + if (Place == 1) + { + return str + "πŸ†"; + } + else if (Place == 2) + { + return str + "`2nd`"; + } + else if (Place == 3) + { + return str + "`3rd`"; + } + else { + return str + $"`{Place}th`"; + } + + } + } + } +} diff --git a/NadekoBot/Modules/Gambling/GamblingModule.cs b/NadekoBot/Modules/Gambling/GamblingModule.cs index 579e1ed6..a338150c 100644 --- a/NadekoBot/Modules/Gambling/GamblingModule.cs +++ b/NadekoBot/Modules/Gambling/GamblingModule.cs @@ -4,6 +4,7 @@ using Discord.Modules; using NadekoBot.Classes; using NadekoBot.DataModels; using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling.Commands; using NadekoBot.Modules.Permissions.Classes; using System; using System.Linq; @@ -18,6 +19,7 @@ namespace NadekoBot.Modules.Gambling commands.Add(new DrawCommand(this)); commands.Add(new FlipCoinCommand(this)); commands.Add(new DiceRollCommand(this)); + commands.Add(new AnimalRacing(this)); } public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling; @@ -31,7 +33,7 @@ namespace NadekoBot.Modules.Gambling commands.ForEach(com => com.Init(cgb)); cgb.CreateCommand(Prefix + "raffle") - .Description("Prints a name and ID of a random user from the online list from the (optional) role.") + .Description($"Prints a name and ID of a random user from the online list from the (optional) role. | `{Prefix}raffle` or `{Prefix}raffle RoleName") .Parameter("role", ParameterType.Optional) .Do(async e => { @@ -61,7 +63,7 @@ namespace NadekoBot.Modules.Gambling }); cgb.CreateCommand(Prefix + "give") - .Description(string.Format("Give someone a certain amount of {0}s", NadekoBot.Config.CurrencyName)) + .Description(string.Format("Give someone a certain amount of {0}s", NadekoBot.Config.CurrencyName)+ $"|`{Prefix}give 1 \"@SomeGuy\"`") .Parameter("amount", ParameterType.Required) .Parameter("receiver", ParameterType.Unparsed) .Do(async e => @@ -93,7 +95,7 @@ namespace NadekoBot.Modules.Gambling }); cgb.CreateCommand(Prefix + "award") - .Description("Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person`") + .Description($"Gives someone a certain amount of flowers. **Bot Owner Only!** | `{Prefix}award 100 @person`") .AddCheck(SimpleCheckers.OwnerOnly()) .Parameter("amount", ParameterType.Required) .Parameter("receiver", ParameterType.Unparsed) @@ -115,7 +117,7 @@ namespace NadekoBot.Modules.Gambling }); cgb.CreateCommand(Prefix + "take") - .Description("Takes a certain amount of flowers from someone. **Bot Owner Only!**") + .Description($"Takes a certain amount of flowers from someone. **Bot Owner Only!** | `{Prefix}take 1 \"@someguy\"`") .AddCheck(SimpleCheckers.OwnerOnly()) .Parameter("amount", ParameterType.Required) .Parameter("rektperson", ParameterType.Unparsed) diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs index 6af69857..d2882eb2 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/NadekoBot/Modules/Games/Commands/PlantPick.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading; using System.Threading.Tasks; namespace NadekoBot.Modules.Games.Commands @@ -59,7 +60,7 @@ namespace NadekoBot.Modules.Games.Commands //channelid/messageid pair ConcurrentDictionary> plantedFlowerChannels = new ConcurrentDictionary>(); - private object locker = new object(); + private SemaphoreSlim locker = new SemaphoreSlim(1,1); internal override void Init(CommandGroupBuilder cgb) { @@ -78,38 +79,47 @@ namespace NadekoBot.Modules.Games.Commands await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true).ConfigureAwait(false); var msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!").ConfigureAwait(false); - await Task.Delay(10000).ConfigureAwait(false); - await msg.Delete().ConfigureAwait(false); + ThreadPool.QueueUserWorkItem(async (state) => + { + try + { + await Task.Delay(10000).ConfigureAwait(false); + await msg.Delete().ConfigureAwait(false); + } + catch { } + }); }); cgb.CreateCommand(Module.Prefix + "plant") .Description("Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)") - .Do(e => + .Do(async e => { - lock (locker) + await locker.WaitAsync().ConfigureAwait(false); + try { if (plantedFlowerChannels.ContainsKey(e.Channel.Id)) { - e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel."); + await e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel.").ConfigureAwait(false); return; } - var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).GetAwaiter().GetResult(); + var removed = await FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).ConfigureAwait(false); if (!removed) { - e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").Wait(); + await e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").ConfigureAwait(false); return; } var file = GetRandomCurrencyImagePath(); Message msg; if (file == null) - msg = e.Channel.SendMessage(NadekoBot.Config.CurrencySign).GetAwaiter().GetResult(); + msg = await e.Channel.SendMessage(NadekoBot.Config.CurrencySign).ConfigureAwait(false); else - msg = e.Channel.SendFile(file).GetAwaiter().GetResult(); + msg = await e.Channel.SendFile(file).ConfigureAwait(false); var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]); - var msg2 = e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick").GetAwaiter().GetResult(); + var msg2 = await e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick").ConfigureAwait(false); plantedFlowerChannels.TryAdd(e.Channel.Id, new[] { msg, msg2 }); } + finally { locker.Release(); } }); cgb.CreateCommand(Prefix + "gencurrency") @@ -129,12 +139,12 @@ namespace NadekoBot.Modules.Games.Commands int throwaway; if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway)) { - await e.Channel.SendMessage("`Currency generation disabled on this channel.`"); + await e.Channel.SendMessage("`Currency generation disabled on this channel.`").ConfigureAwait(false); } else { if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd)) - await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`"); + await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`").ConfigureAwait(false); } }); } diff --git a/NadekoBot/Modules/Games/Commands/PollCommand.cs b/NadekoBot/Modules/Games/Commands/PollCommand.cs index e1cd75e4..0e1bb81c 100644 --- a/NadekoBot/Modules/Games/Commands/PollCommand.cs +++ b/NadekoBot/Modules/Games/Commands/PollCommand.cs @@ -15,11 +15,6 @@ namespace NadekoBot.Modules.Games.Commands public static ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); - public Func DoFunc() - { - throw new NotImplementedException(); - } - internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "poll") diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index 683eb3d0..3f38529f 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -13,7 +13,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia { internal class TriviaGame { - private readonly object _guessLock = new object(); + private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1,1); private Server server { get; } private Channel channel { get; } @@ -113,7 +113,8 @@ namespace NadekoBot.Modules.Games.Commands.Trivia if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; var guess = false; - lock (_guessLock) + await _guessLock.WaitAsync().ConfigureAwait(false); + try { if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested) { @@ -122,6 +123,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia guess = true; } } + finally { _guessLock.Release(); } if (!guess) return; triviaCancelSource.Cancel(); await channel.SendMessage($"β˜‘οΈ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); diff --git a/NadekoBot/Modules/Games/GamesModule.cs b/NadekoBot/Modules/Games/GamesModule.cs index 29fea7f8..acdaf1b3 100644 --- a/NadekoBot/Modules/Games/GamesModule.cs +++ b/NadekoBot/Modules/Games/GamesModule.cs @@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Games commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "choose") - .Description("Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more") + .Description($"Chooses a thing from a list of things | `{Prefix}choose Get up;Sleep;Sleep more`") .Parameter("list", ParameterType.Unparsed) .Do(async e => { @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Games }); cgb.CreateCommand(Prefix + "8ball") - .Description("Ask the 8ball a yes/no question.") + .Description($"Ask the 8ball a yes/no question. | `{Prefix}8ball should i do something`") .Parameter("question", ParameterType.Unparsed) .Do(async e => { @@ -60,14 +60,14 @@ namespace NadekoBot.Modules.Games try { await e.Channel.SendMessage( - $":question: **Question**: `{question}` \n🎱 **8Ball Answers**: `{NadekoBot.Config._8BallResponses[rng.Next(0, NadekoBot.Config._8BallResponses.Length)]}`") + $":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. | >rps scissors") + .Description($"Play a game of rocket paperclip scissors with Nadeko. | `{Prefix}rps scissors`") .Parameter("input", ParameterType.Required) .Do(async e => { diff --git a/NadekoBot/Modules/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs index 4d3fa441..d0e84b5f 100644 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ b/NadekoBot/Modules/Help/Commands/HelpCommand.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Classes.Help.Commands if (alias != null) str = $" / `{ com.Aliases.FirstOrDefault()}`"; if (com != null) - await e.Channel.SendMessage($@"**__Help for:__ `{com.Text}`**" + str + $"\n**Desc:** {new Regex(@"\|").Replace(com.Description, "\n**Usage:**",1)}").ConfigureAwait(false); + await e.Channel.SendMessage($@"**__Help for:__ `{com.Text}`**" + str + $"\n**Desc:** {new Regex(@"\|").Replace(com.Description, "\n**Usage:**", 1)}").ConfigureAwait(false); }).ConfigureAwait(false); }; public static string HelpString { @@ -49,7 +49,8 @@ namespace NadekoBot.Classes.Help.Commands { string helpstr = $@"######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/** -######You can donate on paypal: `nadekodiscordbot@gmail.com` +######You can donate on patreon: `https://patreon.com/nadekobot` +######or paypal: `nadekodiscordbot@gmail.com` #NadekoBot List Of Commands Version: `{NadekoStats.Instance.BotVersion}`"; diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index bcd661f8..847070eb 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -113,17 +113,10 @@ namespace NadekoBot.Modules.Music.Classes if (CurrentSong == null) continue; - try - { - OnStarted(this, CurrentSong); - await CurrentSong.Play(audioClient, cancelToken); - } - catch (OperationCanceledException) - { - Console.WriteLine("Song canceled"); - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; - } + + OnStarted(this, CurrentSong); + await CurrentSong.Play(audioClient, cancelToken); + OnCompleted(this, CurrentSong); if (RepeatPlaylist) @@ -135,8 +128,14 @@ namespace NadekoBot.Modules.Music.Classes } finally { - await Task.Delay(300).ConfigureAwait(false); + if (!cancelToken.IsCancellationRequested) + { + SongCancelSource.Cancel(); + } + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; CurrentSong = null; + await Task.Delay(300).ConfigureAwait(false); } } } diff --git a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs b/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs index 15dc4977..c4a95daa 100644 --- a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs +++ b/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs @@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Music.Classes public int BufferSize { get; } - private readonly object readWriteLock = new object(); + private readonly SemaphoreSlim readWriteLock = new SemaphoreSlim(1, 1); public PoopyBuffer(int size) { @@ -32,51 +32,57 @@ namespace NadekoBot.Modules.Music.Classes ringBuffer = new byte[size]; } - public int Read(byte[] buffer, int count) + public Task ReadAsync(byte[] buffer, int count) { - if (buffer.Length < count) - throw new ArgumentException(); - //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); - lock (readWriteLock) + return Task.Run(async () => { - //read as much as you can if you're reading too much - if (count > ContentLength) - count = ContentLength; - //if nothing to read, return 0 - if (WritePosition == ReadPosition) - return 0; - // if buffer is in the "normal" state, just read - if (WritePosition > ReadPosition) + if (buffer.Length < count) + throw new ArgumentException(); + //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); + await readWriteLock.WaitAsync().ConfigureAwait(false); + try { - Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); - ReadPosition += count; - //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); + //read as much as you can if you're reading too much + if (count > ContentLength) + count = ContentLength; + //if nothing to read, return 0 + if (WritePosition == ReadPosition) + return 0; + // if buffer is in the "normal" state, just read + if (WritePosition > ReadPosition) + { + Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); + ReadPosition += count; + //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); + return count; + } + //else ReadPos { - // if i can just write without hitting buffer.length, do it - if (WritePosition + count < BufferSize) + //the while above assures that i cannot write past readposition with my write, so i don't have to check + // *unless its multithreaded or task is not awaited + await readWriteLock.WaitAsync().ConfigureAwait(false); + try { - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); - WritePosition += count; - //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); - return; + // if i can just write without hitting buffer.length, do it + if (WritePosition + count < BufferSize) + { + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); + WritePosition += count; + //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); + return; + } + // otherwise, i have to write to the end, then write the rest from the start + + var wroteNormaly = BufferSize - WritePosition; + Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); + + //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); + + var wroteFromStart = count - wroteNormaly; + Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); + + //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); + + WritePosition = wroteFromStart; } - // otherwise, i have to write to the end, then write the rest from the start - - var wroteNormaly = BufferSize - WritePosition; - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); - - //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); - - var wroteFromStart = count - wroteNormaly; - Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); - - //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); - - WritePosition = wroteFromStart; - } + finally { readWriteLock.Release(); } + }); } } } diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/NadekoBot/Modules/Music/Classes/Song.cs index 025863e0..08be13b8 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/NadekoBot/Modules/Music/Classes/Song.cs @@ -3,6 +3,7 @@ using NadekoBot.Classes; using NadekoBot.Extensions; using System; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -31,9 +32,7 @@ namespace NadekoBot.Modules.Music.Classes public SongInfo SongInfo { get; } public string QueuerName { get; set; } - private PoopyBuffer songBuffer { get; set; } - - private bool prebufferingComplete { get; set; } = false; + private bool bufferingCompleted { get; set; } = false; public MusicPlayer MusicPlayer { get; set; } public string PrettyCurrentTime() @@ -74,7 +73,7 @@ namespace NadekoBot.Modules.Music.Classes return this; } - private Task BufferSong(CancellationToken cancelToken) => + private Task BufferSong(string filename, CancellationToken cancelToken) => Task.Factory.StartNew(async () => { Process p = null; @@ -89,40 +88,38 @@ namespace NadekoBot.Modules.Music.Classes RedirectStandardError = false, CreateNoWindow = true, }); - const int blockSize = 3840; - var buffer = new byte[blockSize]; - var attempt = 0; - while (!cancelToken.IsCancellationRequested) + var prebufferSize = 100ul.MiB(); + using (var outStream = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) { - var read = 0; - try + byte[] buffer = new byte[81920]; + int bytesRead; + while ((bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) != 0) { - read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken) - .ConfigureAwait(false); + await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); + while ((ulong)outStream.Length - bytesSent > prebufferSize) + await Task.Delay(100, cancelToken); } - catch - { - return; - } - if (read == 0) - if (attempt++ == 50) - break; - else - await Task.Delay(100, cancelToken).ConfigureAwait(false); - else - attempt = 0; - await songBuffer.WriteAsync(buffer, read, cancelToken).ConfigureAwait(false); - if (songBuffer.ContentLength > 2.MB()) - prebufferingComplete = true; } + + bufferingCompleted = true; + } + catch (System.ComponentModel.Win32Exception) { + var oldclr = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(@"You have not properly installed or configured FFMPEG. +Please install and configure FFMPEG to play music. +Check the guides for your platform on how to setup ffmpeg correctly: + Windows Guide: https://goo.gl/SCv72y + Linux Guide: https://goo.gl/rRhjCp"); + Console.ForegroundColor = oldclr; } catch (Exception ex) { - Console.WriteLine($"Buffering errored: {ex.Message}"); + Console.WriteLine($"Buffering stopped: {ex.Message}"); } finally { - Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]"); + Console.WriteLine($"Buffering done."); if (p != null) { try @@ -137,53 +134,82 @@ namespace NadekoBot.Modules.Music.Classes internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { - // initialize the buffer here because if this song was playing before (requeued), we must delete old buffer data - songBuffer = new PoopyBuffer(NadekoBot.Config.BufferSize); + var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); - var bufferTask = BufferSong(cancelToken).ConfigureAwait(false); - var bufferAttempts = 0; - const int waitPerAttempt = 500; - var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9; - while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) + var bufferTask = BufferSong(filename, cancelToken).ConfigureAwait(false); + + var inStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); + + bytesSent = 0; + + try { - await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false); - } - Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); - const int blockSize = 3840; - var attempt = 0; - while (!cancelToken.IsCancellationRequested) - { - //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); - byte[] buffer = new byte[blockSize]; - var read = songBuffer.Read(buffer, blockSize); - unchecked + var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken); + var sw = new Stopwatch(); + sw.Start(); + var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken)); + if (t != prebufferingTask) { - bytesSent += (ulong)read; + Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream."); + return; } - if (read == 0) - if (attempt++ == 20) - { - voiceClient.Wait(); - Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]"); - break; - } - else - await Task.Delay(100, cancelToken).ConfigureAwait(false); - else - attempt = 0; + else if(prebufferingTask.IsCanceled) + { + Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream."); + return; + } + sw.Stop(); + Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed); - while (this.MusicPlayer.Paused) - await Task.Delay(200, cancelToken).ConfigureAwait(false); - buffer = AdjustVolume(buffer, MusicPlayer.Volume); - voiceClient.Send(buffer, 0, read); + const int blockSize = 3840; + var attempt = 0; + byte[] buffer = new byte[blockSize]; + while (!cancelToken.IsCancellationRequested) + { + //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); + var read = inStream.Read(buffer, 0, buffer.Length); + //await inStream.CopyToAsync(voiceClient.OutputStream); + unchecked + { + bytesSent += (ulong)read; + } + if (read == 0) + if (attempt++ == 20) + { + voiceClient.Wait(); + break; + } + else + await Task.Delay(100, cancelToken).ConfigureAwait(false); + else + attempt = 0; + + while (this.MusicPlayer.Paused) + await Task.Delay(200, cancelToken).ConfigureAwait(false); + + buffer = AdjustVolume(buffer, MusicPlayer.Volume); + voiceClient.Send(buffer, 0, read); + } + } + finally + { + await bufferTask; + await Task.Run(() => voiceClient.Clear()); + inStream.Dispose(); + try { File.Delete(filename); } catch { } } - Console.WriteLine("Awiting buffer task"); - await bufferTask; - Console.WriteLine("Buffer task done."); - voiceClient.Clear(); - cancelToken.ThrowIfCancellationRequested(); } + private async Task CheckPrebufferingAsync(Stream inStream, CancellationToken cancelToken) + { + while (!bufferingCompleted && inStream.Length < 2.MiB()) + { + await Task.Delay(100, cancelToken); + } + Console.WriteLine("Buffering successfull"); + } + + /* //stackoverflow ftw private static byte[] AdjustVolume(byte[] audioSamples, float volume) { @@ -210,6 +236,33 @@ namespace NadekoBot.Modules.Music.Classes } return array; } + */ + + //aidiakapi ftw + public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume) + { + Contract.Requires(audioSamples != null); + Contract.Requires(audioSamples.Length % 2 == 0); + Contract.Requires(volume >= 0f && volume <= 1f); + Contract.Assert(BitConverter.IsLittleEndian); + + if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples; + + // 16-bit precision for the multiplication + int volumeFixed = (int)Math.Round(volume * 65536d); + + int count = audioSamples.Length / 2; + + fixed (byte* srcBytes = audioSamples) + { + short* src = (short*)srcBytes; + + for (int i = count; i != 0; i--, src++) + *src = (short)(((*src) * volumeFixed) >> 16); + } + + return audioSamples; + } public static async Task ResolveSong(string query, MusicType musicType = MusicType.Normal) { diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index ba04d598..d85d550e 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -18,11 +18,16 @@ namespace NadekoBot.Modules.Music { internal class MusicModule : DiscordModule { - public static ConcurrentDictionary MusicPlayers = new ConcurrentDictionary(); + public const string MusicDataPath = "data/musicdata"; + public MusicModule() { + //it can fail if its currenctly opened or doesn't exist. Either way i don't care + try { Directory.Delete(MusicDataPath, true); } catch { } + + Directory.CreateDirectory(MusicDataPath); } public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Music; @@ -713,7 +718,7 @@ namespace NadekoBot.Modules.Music }); cgb.CreateCommand(Prefix + "goto") - .Description("Goes to a specific time in seconds in a song.") + .Description($"Goes to a specific time in seconds in a song. | `{Prefix}goto 30`") .Parameter("time") .Do(async e => { @@ -856,7 +861,7 @@ namespace NadekoBot.Modules.Music } if (!silent) { - var queuedMessage = await textCh.SendMessage($"🎡`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count}`").ConfigureAwait(false); + var queuedMessage = await textCh.SendMessage($"🎡`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false); #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Run(async () => { diff --git a/NadekoBot/Modules/NSFW/NSFWModule.cs b/NadekoBot/Modules/NSFW/NSFWModule.cs index e54790e2..a94ab572 100644 --- a/NadekoBot/Modules/NSFW/NSFWModule.cs +++ b/NadekoBot/Modules/NSFW/NSFWModule.cs @@ -22,7 +22,7 @@ namespace NadekoBot.Modules.NSFW cgb.AddCheck(PermissionChecker.Instance); cgb.CreateCommand(Prefix + "hentai") - .Description("Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~hentai yuri+kissing") + .Description($"Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}hentai yuri+kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -39,7 +39,7 @@ namespace NadekoBot.Modules.NSFW await e.Channel.SendMessage("`No results.`"); }); cgb.CreateCommand(Prefix + "danbooru") - .Description("Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~danbooru yuri+kissing") + .Description($"Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}danbooru yuri+kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -51,7 +51,7 @@ namespace NadekoBot.Modules.NSFW await e.Channel.SendMessage(link).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "gelbooru") - .Description("Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~gelbooru yuri+kissing") + .Description($"Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}gelbooru yuri+kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -64,7 +64,7 @@ namespace NadekoBot.Modules.NSFW }); cgb.CreateCommand(Prefix + "rule34") - .Description("Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~rule34 yuri+kissing") + .Description($"Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}rule34 yuri+kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -76,7 +76,7 @@ namespace NadekoBot.Modules.NSFW await e.Channel.SendMessage(link).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "e621") - .Description("Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | ~e621 yuri kissing") + .Description($"Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `{Prefix}e621 yuri kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs b/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs index c5d2e64c..d7c9e784 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs +++ b/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs @@ -150,21 +150,21 @@ namespace NadekoBot.Modules.Permissions.Classes return PermissionBanType.None; } - private static void WriteServerToJson(ServerPermissions serverPerms) + private static Task WriteServerToJson(ServerPermissions serverPerms) => Task.Run(() => { string pathToFile = $"data/permissions/{serverPerms.Id}.json"; File.WriteAllText(pathToFile, Newtonsoft.Json.JsonConvert.SerializeObject(serverPerms, Newtonsoft.Json.Formatting.Indented)); - } + }); - public static void WriteToJson() + public static Task WriteToJson() => Task.Run(() => { Directory.CreateDirectory("data/permissions/"); foreach (var kvp in PermissionsDict) { WriteServerToJson(kvp.Value); } - } + }); public static string GetServerPermissionsRoleName(Server server) { @@ -174,25 +174,25 @@ namespace NadekoBot.Modules.Permissions.Classes return serverPerms.PermissionsControllerRole; } - internal static void SetPermissionsRole(Server server, string roleName) + internal static async Task SetPermissionsRole(Server server, string roleName) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); serverPerms.PermissionsControllerRole = roleName; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - internal static void SetVerbosity(Server server, bool val) + internal static async Task SetVerbosity(Server server, bool val) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); serverPerms.Verbose = val; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - internal static void CopyRolePermissions(Role fromRole, Role toRole) + internal static async Task CopyRolePermissions(Role fromRole, Role toRole) { var server = fromRole.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -207,10 +207,10 @@ namespace NadekoBot.Modules.Permissions.Classes to.CopyFrom(from); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - internal static void CopyChannelPermissions(Channel fromChannel, Channel toChannel) + internal static async Task CopyChannelPermissions(Channel fromChannel, Channel toChannel) { var server = fromChannel.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -225,10 +225,10 @@ namespace NadekoBot.Modules.Permissions.Classes to.CopyFrom(from); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - internal static void CopyUserPermissions(User fromUser, User toUser) + internal static async Task CopyUserPermissions(User fromUser, User toUser) { var server = fromUser.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -243,10 +243,10 @@ namespace NadekoBot.Modules.Permissions.Classes to.CopyFrom(from); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetServerModulePermission(Server server, string moduleName, bool value) + public static async Task SetServerModulePermission(Server server, string moduleName, bool value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); @@ -256,10 +256,10 @@ namespace NadekoBot.Modules.Permissions.Classes modules[moduleName] = value; else modules.TryAdd(moduleName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetServerCommandPermission(Server server, string commandName, bool value) + public static async Task SetServerCommandPermission(Server server, string commandName, bool value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); @@ -269,10 +269,10 @@ namespace NadekoBot.Modules.Permissions.Classes commands[commandName] = value; else commands.TryAdd(commandName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetChannelModulePermission(Channel channel, string moduleName, bool value) + public static async Task SetChannelModulePermission(Channel channel, string moduleName, bool value) { var server = channel.Server; @@ -288,10 +288,10 @@ namespace NadekoBot.Modules.Permissions.Classes modules[moduleName] = value; else modules.TryAdd(moduleName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetChannelCommandPermission(Channel channel, string commandName, bool value) + public static async Task SetChannelCommandPermission(Channel channel, string commandName, bool value) { var server = channel.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -306,10 +306,10 @@ namespace NadekoBot.Modules.Permissions.Classes commands[commandName] = value; else commands.TryAdd(commandName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetRoleModulePermission(Role role, string moduleName, bool value) + public static async Task SetRoleModulePermission(Role role, string moduleName, bool value) { var server = role.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -324,10 +324,10 @@ namespace NadekoBot.Modules.Permissions.Classes modules[moduleName] = value; else modules.TryAdd(moduleName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetRoleCommandPermission(Role role, string commandName, bool value) + public static async Task SetRoleCommandPermission(Role role, string commandName, bool value) { var server = role.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -342,10 +342,10 @@ namespace NadekoBot.Modules.Permissions.Classes commands[commandName] = value; else commands.TryAdd(commandName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetUserModulePermission(User user, string moduleName, bool value) + public static async Task SetUserModulePermission(User user, string moduleName, bool value) { var server = user.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -360,10 +360,10 @@ namespace NadekoBot.Modules.Permissions.Classes modules[moduleName] = value; else modules.TryAdd(moduleName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetUserCommandPermission(User user, string commandName, bool value) + public static async Task SetUserCommandPermission(User user, string commandName, bool value) { var server = user.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -377,19 +377,19 @@ namespace NadekoBot.Modules.Permissions.Classes commands[commandName] = value; else commands.TryAdd(commandName, value); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetServerWordPermission(Server server, bool value) + public static async Task SetServerWordPermission(Server server, bool value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); serverPerms.Permissions.FilterWords = value; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetChannelWordPermission(Channel channel, bool value) + public static async Task SetChannelWordPermission(Channel channel, bool value) { var server = channel.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -399,19 +399,19 @@ namespace NadekoBot.Modules.Permissions.Classes serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); serverPerms.ChannelPermissions[channel.Id].FilterWords = value; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetServerFilterInvitesPermission(Server server, bool value) + public static async Task SetServerFilterInvitesPermission(Server server, bool value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); serverPerms.Permissions.FilterInvites = value; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetChannelFilterInvitesPermission(Channel channel, bool value) + public static async Task SetChannelFilterInvitesPermission(Channel channel, bool value) { var server = channel.Server; var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -421,10 +421,10 @@ namespace NadekoBot.Modules.Permissions.Classes serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); serverPerms.ChannelPermissions[channel.Id].FilterInvites = value; - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void SetCommandCooldown(Server server, string commandName, int value) + public static async Task SetCommandCooldown(Server server, string commandName, int value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); @@ -436,26 +436,26 @@ namespace NadekoBot.Modules.Permissions.Classes serverPerms.CommandCooldowns.AddOrUpdate(commandName, value, (str, v) => value); } - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void AddFilteredWord(Server server, string word) + public static async Task AddFilteredWord(Server server, string word) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); if (serverPerms.Words.Contains(word)) throw new InvalidOperationException("That word is already banned."); serverPerms.Words.Add(word); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } - public static void RemoveFilteredWord(Server server, string word) + public static async Task RemoveFilteredWord(Server server, string word) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, new ServerPermissions(server.Id, server.Name)); if (!serverPerms.Words.Contains(word)) throw new InvalidOperationException("That word is not banned."); serverPerms.Words.Remove(word); - Task.Run(() => WriteServerToJson(serverPerms)); + await WriteServerToJson(serverPerms).ConfigureAwait(false); } } /// diff --git a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs index c8e63f23..f65902c6 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs +++ b/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Permissions.Commands var chan = string.IsNullOrWhiteSpace(chanStr) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chanStr); - PermissionsHandler.SetChannelFilterInvitesPermission(chan, state); + await PermissionsHandler.SetChannelFilterInvitesPermission(chan, state).ConfigureAwait(false); await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.") .ConfigureAwait(false); return; @@ -80,7 +80,7 @@ namespace NadekoBot.Modules.Permissions.Commands foreach (var curChannel in e.Server.TextChannels) { - PermissionsHandler.SetChannelFilterInvitesPermission(curChannel, state); + await PermissionsHandler.SetChannelFilterInvitesPermission(curChannel, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.") .ConfigureAwait(false); @@ -102,7 +102,7 @@ namespace NadekoBot.Modules.Permissions.Commands try { var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - PermissionsHandler.SetServerFilterInvitesPermission(e.Server, state); + await PermissionsHandler.SetServerFilterInvitesPermission(e.Server, state).ConfigureAwait(false); await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for this server.") .ConfigureAwait(false); diff --git a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs index 3764245d..c187d398 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs +++ b/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs @@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Permissions.Commands var chan = string.IsNullOrWhiteSpace(chanStr) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chanStr); - PermissionsHandler.SetChannelWordPermission(chan, state); + await PermissionsHandler.SetChannelWordPermission(chan, state).ConfigureAwait(false); await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.").ConfigureAwait(false); return; } @@ -76,7 +76,7 @@ namespace NadekoBot.Modules.Permissions.Commands foreach (var curChannel in e.Server.TextChannels) { - PermissionsHandler.SetChannelWordPermission(curChannel, state); + await PermissionsHandler.SetChannelWordPermission(curChannel, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.").ConfigureAwait(false); } @@ -98,7 +98,7 @@ namespace NadekoBot.Modules.Permissions.Commands var word = e.GetArg("word"); if (string.IsNullOrWhiteSpace(word)) return; - PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim()); + await PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false); await e.Channel.SendMessage($"Successfully added new filtered word.").ConfigureAwait(false); } @@ -120,7 +120,7 @@ namespace NadekoBot.Modules.Permissions.Commands var word = e.GetArg("word"); if (string.IsNullOrWhiteSpace(word)) return; - PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim()); + await PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false); await e.Channel.SendMessage($"Successfully removed filtered word.").ConfigureAwait(false); } @@ -159,7 +159,7 @@ namespace NadekoBot.Modules.Permissions.Commands try { var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - PermissionsHandler.SetServerWordPermission(e.Server, state); + await PermissionsHandler.SetServerWordPermission(e.Server, state).ConfigureAwait(false); await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** on this server.") .ConfigureAwait(false); diff --git a/NadekoBot/Modules/Permissions/PermissionsModule.cs b/NadekoBot/Modules/Permissions/PermissionsModule.cs index 4c13b9ff..c547bfe5 100644 --- a/NadekoBot/Modules/Permissions/PermissionsModule.cs +++ b/NadekoBot/Modules/Permissions/PermissionsModule.cs @@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Permissions await e.Channel.SendMessage($"Role `{arg}` probably doesn't exist. Create the role with that name first.").ConfigureAwait(false); return; } - PermissionsHandler.SetPermissionsRole(e.Server, role.Name); + await PermissionsHandler.SetPermissionsRole(e.Server, role.Name).ConfigureAwait(false); await e.Channel.SendMessage($"Role `{role.Name}` is now required in order to change permissions.").ConfigureAwait(false); }); @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Permissions var args = arg.Split('~').Select(a => a.Trim()).ToArray(); if (args.Length > 2) { - await e.Channel.SendMessage("πŸ’’Invalid number of '~'s in the argument."); + await e.Channel.SendMessage("πŸ’’Invalid number of '~'s in the argument.").ConfigureAwait(false); return; } try @@ -79,12 +79,12 @@ namespace NadekoBot.Modules.Permissions var fromRole = PermissionHelper.ValidateRole(e.Server, args[0]); var toRole = PermissionHelper.ValidateRole(e.Server, args[1]); - PermissionsHandler.CopyRolePermissions(fromRole, toRole); - await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**."); + await PermissionsHandler.CopyRolePermissions(fromRole, toRole).ConfigureAwait(false); + await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**.").ConfigureAwait(false); } catch (Exception ex) { - await e.Channel.SendMessage($"πŸ’’{ex.Message}"); + await e.Channel.SendMessage($"πŸ’’{ex.Message}").ConfigureAwait(false); } }); cgb.CreateCommand(Prefix + "chnlpermscopy") @@ -107,8 +107,8 @@ namespace NadekoBot.Modules.Permissions var fromChannel = PermissionHelper.ValidateChannel(e.Server, args[0]); var toChannel = PermissionHelper.ValidateChannel(e.Server, args[1]); - PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel); - await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**."); + await PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel).ConfigureAwait(false); + await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**.").ConfigureAwait(false); } catch (Exception ex) { @@ -127,7 +127,7 @@ namespace NadekoBot.Modules.Permissions var args = arg.Split('~').Select(a => a.Trim()).ToArray(); if (args.Length > 2) { - await e.Channel.SendMessage("πŸ’’Invalid number of '~'s in the argument."); + await e.Channel.SendMessage("πŸ’’Invalid number of '~'s in the argument.").ConfigureAwait(false); return; } try @@ -135,8 +135,8 @@ namespace NadekoBot.Modules.Permissions var fromUser = PermissionHelper.ValidateUser(e.Server, args[0]); var toUser = PermissionHelper.ValidateUser(e.Server, args[1]); - PermissionsHandler.CopyUserPermissions(fromUser, toUser); - await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**."); + await PermissionsHandler.CopyUserPermissions(fromUser, toUser).ConfigureAwait(false); + await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**.").ConfigureAwait(false); } catch (Exception ex) { @@ -146,13 +146,13 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "verbose") .Alias(Prefix + "v") - .Description("Sets whether to show when a command/module is blocked. | ;verbose true") + .Description($"Sets whether to show when a command/module is blocked. | `{Prefix}verbose true`") .Parameter("arg", ParameterType.Required) .Do(async e => { var arg = e.GetArg("arg"); var val = PermissionHelper.ValidateBool(arg); - PermissionsHandler.SetVerbosity(e.Server, val); + await PermissionsHandler.SetVerbosity(e.Server, val).ConfigureAwait(false); await e.Channel.SendMessage($"Verbosity set to {val}.").ConfigureAwait(false); }); @@ -169,7 +169,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "roleperms") .Alias(Prefix + "rp") - .Description("Shows banned permissions for a certain role. No argument means for everyone. | ;rp AwesomeRole") + .Description($"Shows banned permissions for a certain role. No argument means for everyone. | `{Prefix}rp AwesomeRole`") .Parameter("role", ParameterType.Unparsed) .Do(async e => { @@ -195,7 +195,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "chnlperms") .Alias(Prefix + "cp") - .Description("Shows banned permissions for a certain channel. No argument means for this channel. | ;cp #dev") + .Description($"Shows banned permissions for a certain channel. No argument means for this channel. | `{Prefix}cp #dev`") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -220,7 +220,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "userperms") .Alias(Prefix + "up") - .Description("Shows banned permissions for a certain user. No argument means for yourself. | ;up Kwoth") + .Description($"Shows banned permissions for a certain user. No argument means for yourself. | `{Prefix}up Kwoth`") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -246,7 +246,7 @@ namespace NadekoBot.Modules.Permissions .Alias(Prefix + "sm") .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) - .Description("Sets a module's permission at the server level. | ;sm \"module name\" enable") + .Description($"Sets a module's permission at the server level. | `{Prefix}sm \"module name\" enable`") .Do(async e => { try @@ -254,7 +254,7 @@ namespace NadekoBot.Modules.Permissions var module = PermissionHelper.ValidateModule(e.GetArg("module")); var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - PermissionsHandler.SetServerModulePermission(e.Server, module, state); + await PermissionsHandler.SetServerModulePermission(e.Server, module, state).ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); } catch (ArgumentException exArg) @@ -270,7 +270,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "srvrcmd").Alias(Prefix + "sc") .Parameter("command", ParameterType.Required) .Parameter("bool", ParameterType.Required) - .Description("Sets a command's permission at the server level. | ;sc \"command name\" disable") + .Description($"Sets a command's permission at the server level. | `{Prefix}sc \"command name\" disable`") .Do(async e => { try @@ -278,7 +278,7 @@ namespace NadekoBot.Modules.Permissions var command = PermissionHelper.ValidateCommand(e.GetArg("command")); var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - PermissionsHandler.SetServerCommandPermission(e.Server, command, state); + await PermissionsHandler.SetServerCommandPermission(e.Server, command, state).ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); } catch (ArgumentException exArg) @@ -295,7 +295,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("role", ParameterType.Unparsed) - .Description("Sets a module's permission at the role level. | ;rm \"module name\" enable MyRole") + .Description($"Sets a module's permission at the role level. | `{Prefix}rm \"module name\" enable MyRole`") .Do(async e => { try @@ -307,7 +307,7 @@ namespace NadekoBot.Modules.Permissions { foreach (var role in e.Server.Roles) { - PermissionsHandler.SetRoleModulePermission(role, module, state); + await PermissionsHandler.SetRoleModulePermission(role, module, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); } @@ -315,7 +315,7 @@ namespace NadekoBot.Modules.Permissions { var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - PermissionsHandler.SetRoleModulePermission(role, module, state); + await PermissionsHandler.SetRoleModulePermission(role, module, state).ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); } } @@ -333,7 +333,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("command", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("role", ParameterType.Unparsed) - .Description("Sets a command's permission at the role level. | ;rc \"command name\" disable MyRole") + .Description($"Sets a command's permission at the role level. | `{Prefix}rc \"command name\" disable MyRole`") .Do(async e => { try @@ -345,7 +345,7 @@ namespace NadekoBot.Modules.Permissions { foreach (var role in e.Server.Roles) { - PermissionsHandler.SetRoleCommandPermission(role, command, state); + await PermissionsHandler.SetRoleCommandPermission(role, command, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); } @@ -353,7 +353,7 @@ namespace NadekoBot.Modules.Permissions { var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - PermissionsHandler.SetRoleCommandPermission(role, command, state); + await PermissionsHandler.SetRoleCommandPermission(role, command, state).ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); } } @@ -371,7 +371,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("channel", ParameterType.Unparsed) - .Description("Sets a module's permission at the channel level. | ;cm \"module name\" enable SomeChannel") + .Description($"Sets a module's permission at the channel level. | `{Prefix}cm \"module name\" enable SomeChannel`") .Do(async e => { try @@ -383,20 +383,20 @@ namespace NadekoBot.Modules.Permissions { foreach (var channel in e.Server.TextChannels) { - PermissionsHandler.SetChannelModulePermission(channel, module, state); + await PermissionsHandler.SetChannelModulePermission(channel, module, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); } else if (string.IsNullOrWhiteSpace(channelArg)) { - PermissionsHandler.SetChannelModulePermission(e.Channel, module, state); + await PermissionsHandler.SetChannelModulePermission(e.Channel, module, state).ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{e.Channel.Name}** channel.").ConfigureAwait(false); } else { var channel = PermissionHelper.ValidateChannel(e.Server, channelArg); - PermissionsHandler.SetChannelModulePermission(channel, module, state); + await PermissionsHandler.SetChannelModulePermission(channel, module, state).ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); } } @@ -414,7 +414,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("command", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("channel", ParameterType.Unparsed) - .Description("Sets a command's permission at the channel level. | ;cc \"command name\" enable SomeChannel") + .Description($"Sets a command's permission at the channel level. | `{Prefix}cc \"command name\" enable SomeChannel`") .Do(async e => { try @@ -426,7 +426,7 @@ namespace NadekoBot.Modules.Permissions { foreach (var channel in e.Server.TextChannels) { - PermissionsHandler.SetChannelCommandPermission(channel, command, state); + await PermissionsHandler.SetChannelCommandPermission(channel, command, state).ConfigureAwait(false); } await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); } @@ -434,7 +434,7 @@ namespace NadekoBot.Modules.Permissions { var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); - PermissionsHandler.SetChannelCommandPermission(channel, command, state); + await PermissionsHandler.SetChannelCommandPermission(channel, command, state).ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); } } @@ -452,7 +452,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("user", ParameterType.Unparsed) - .Description("Sets a module's permission at the user level. | ;um \"module name\" enable SomeUsername") + .Description($"Sets a module's permission at the user level. | `{Prefix}um \"module name\" enable SomeUsername`") .Do(async e => { try @@ -461,7 +461,7 @@ namespace NadekoBot.Modules.Permissions var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); - PermissionsHandler.SetUserModulePermission(user, module, state); + await PermissionsHandler.SetUserModulePermission(user, module, state).ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false); } catch (ArgumentException exArg) @@ -478,7 +478,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("command", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("user", ParameterType.Unparsed) - .Description("Sets a command's permission at the user level. | ;uc \"command name\" enable SomeUsername") + .Description($"Sets a command's permission at the user level. | `{Prefix}uc \"command name\" enable SomeUsername`") .Do(async e => { try @@ -487,7 +487,7 @@ namespace NadekoBot.Modules.Permissions var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); - PermissionsHandler.SetUserCommandPermission(user, command, state); + await PermissionsHandler.SetUserCommandPermission(user, command, state).ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false); } catch (ArgumentException exArg) @@ -502,7 +502,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "allsrvrmdls").Alias(Prefix + "asm") .Parameter("bool", ParameterType.Required) - .Description("Sets permissions for all modules at the server level. | ;asm [enable/disable]") + .Description($"Sets permissions for all modules at the server level. | `{Prefix}asm [enable/disable]`") .Do(async e => { try @@ -511,7 +511,7 @@ namespace NadekoBot.Modules.Permissions foreach (var module in NadekoBot.Client.GetService().Modules) { - PermissionsHandler.SetServerModulePermission(e.Server, module.Name, state); + await PermissionsHandler.SetServerModulePermission(e.Server, module.Name, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); } @@ -528,7 +528,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "allsrvrcmds").Alias(Prefix + "asc") .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) - .Description("Sets permissions for all commands from a certain module at the server level. | ;asc \"module name\" [enable/disable]") + .Description($"Sets permissions for all commands from a certain module at the server level. | `{Prefix}asc \"module name\" [enable/disable]`") .Do(async e => { try @@ -538,7 +538,7 @@ namespace NadekoBot.Modules.Permissions foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { - PermissionsHandler.SetServerCommandPermission(e.Server, command.Text, state); + await PermissionsHandler.SetServerCommandPermission(e.Server, command.Text, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); } @@ -555,7 +555,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "allchnlmdls").Alias(Prefix + "acm") .Parameter("bool", ParameterType.Required) .Parameter("channel", ParameterType.Unparsed) - .Description("Sets permissions for all modules at the channel level. | ;acm [enable/disable] SomeChannel") + .Description($"Sets permissions for all modules at the channel level. | `{Prefix}acm [enable/disable] SomeChannel`") .Do(async e => { try @@ -565,7 +565,7 @@ namespace NadekoBot.Modules.Permissions var channel = string.IsNullOrWhiteSpace(chArg) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chArg); foreach (var module in NadekoBot.Client.GetService().Modules) { - PermissionsHandler.SetChannelModulePermission(channel, module.Name, state); + await PermissionsHandler.SetChannelModulePermission(channel, module.Name, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); @@ -584,7 +584,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("channel", ParameterType.Unparsed) - .Description("Sets permissions for all commands from a certain module at the channel level. | ;acc \"module name\" [enable/disable] SomeChannel") + .Description($"Sets permissions for all commands from a certain module at the channel level. | `{Prefix}acc \"module name\" [enable/disable] SomeChannel`") .Do(async e => { try @@ -594,7 +594,7 @@ namespace NadekoBot.Modules.Permissions var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { - PermissionsHandler.SetChannelCommandPermission(channel, command.Text, state); + await PermissionsHandler.SetChannelCommandPermission(channel, command.Text, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); } @@ -611,7 +611,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "allrolemdls").Alias(Prefix + "arm") .Parameter("bool", ParameterType.Required) .Parameter("role", ParameterType.Unparsed) - .Description("Sets permissions for all modules at the role level. | ;arm [enable/disable] MyRole") + .Description($"Sets permissions for all modules at the role level. | `{Prefix}arm [enable/disable] MyRole`") .Do(async e => { try @@ -620,7 +620,7 @@ namespace NadekoBot.Modules.Permissions var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); foreach (var module in NadekoBot.Client.GetService().Modules) { - PermissionsHandler.SetRoleModulePermission(role, module.Name, state); + await PermissionsHandler.SetRoleModulePermission(role, module.Name, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); @@ -639,7 +639,7 @@ namespace NadekoBot.Modules.Permissions .Parameter("module", ParameterType.Required) .Parameter("bool", ParameterType.Required) .Parameter("role", ParameterType.Unparsed) - .Description("Sets permissions for all commands from a certain module at the role level. | ;arc \"module name\" [enable/disable] MyRole") + .Description($"Sets permissions for all commands from a certain module at the role level. | `{Prefix}arc \"module name\" [enable/disable] MyRole`") .Do(async e => { try @@ -652,7 +652,7 @@ namespace NadekoBot.Modules.Permissions { foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { - PermissionsHandler.SetRoleCommandPermission(role, command.Text, state); + await PermissionsHandler.SetRoleCommandPermission(role, command.Text, state).ConfigureAwait(false); } } await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **all roles** role.").ConfigureAwait(false); @@ -663,7 +663,7 @@ namespace NadekoBot.Modules.Permissions foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { - PermissionsHandler.SetRoleCommandPermission(role, command.Text, state); + await PermissionsHandler.SetRoleCommandPermission(role, command.Text, state).ConfigureAwait(false); } await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); } @@ -679,7 +679,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "ubl") - .Description("Blacklists a mentioned user. | ;ubl [user_mention]") + .Description($"Blacklists a mentioned user. | `{Prefix}ubl [user_mention]`") .Parameter("user", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -689,13 +689,13 @@ namespace NadekoBot.Modules.Permissions if (!e.Message.MentionedUsers.Any()) return; var usr = e.Message.MentionedUsers.First(); NadekoBot.Config.UserBlacklist.Add(usr.Id); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted user {usr.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "uubl") - .Description($"Unblacklists a mentioned user. | {Prefix}uubl [user_mention]") + .Description($"Unblacklists a mentioned user. | `{Prefix}uubl [user_mention]`") .Parameter("user", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -707,7 +707,7 @@ namespace NadekoBot.Modules.Permissions if (NadekoBot.Config.UserBlacklist.Contains(usr.Id)) { NadekoBot.Config.UserBlacklist.Remove(usr.Id); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully unblacklisted user {usr.Name}`").ConfigureAwait(false); } else @@ -718,7 +718,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "cbl") - .Description("Blacklists a mentioned channel (#general for example). | ;cbl #some_channel") + .Description($"Blacklists a mentioned channel (#general for example). | `{Prefix}cbl #some_channel`") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -727,13 +727,13 @@ namespace NadekoBot.Modules.Permissions if (!e.Message.MentionedChannels.Any()) return; var ch = e.Message.MentionedChannels.First(); NadekoBot.Config.UserBlacklist.Add(ch.Id); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "cubl") - .Description("Unblacklists a mentioned channel (#general for example). | ;cubl #some_channel") + .Description($"Unblacklists a mentioned channel (#general for example). | `{Prefix}cubl #some_channel`") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -742,13 +742,13 @@ namespace NadekoBot.Modules.Permissions if (!e.Message.MentionedChannels.Any()) return; var ch = e.Message.MentionedChannels.First(); NadekoBot.Config.UserBlacklist.Remove(ch.Id); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "sbl") - .Description("Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;sbl [servername/serverid]") + .Description($"Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `{Prefix}sbl [servername/serverid]`") .Parameter("server", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -767,7 +767,7 @@ namespace NadekoBot.Modules.Permissions } var serverId = server.Id; NadekoBot.Config.ServerBlacklist.Add(serverId); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); //cleanup trivias and typeracing Modules.Games.Commands.Trivia.TriviaGame trivia; TriviaCommands.RunningTrivias.TryRemove(serverId, out trivia); @@ -795,7 +795,7 @@ namespace NadekoBot.Modules.Permissions throw new ArgumentOutOfRangeException("secs", "Invalid second parameter. (Must be a number between 0 and 3600)"); - PermissionsHandler.SetCommandCooldown(e.Server, command, secs); + await PermissionsHandler.SetCommandCooldown(e.Server, command, secs).ConfigureAwait(false); if(secs == 0) await e.Channel.SendMessage($"Command **{command}** has no coooldown now.").ConfigureAwait(false); else diff --git a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/NadekoBot/Modules/Searches/Commands/LoLCommands.cs index bbe2eca9..8826beaf 100644 --- a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/LoLCommands.cs @@ -30,8 +30,6 @@ namespace NadekoBot.Modules.Searches.Commands } private static Dictionary CachedChampionImages = new Dictionary(); - private readonly object cacheLock = new object(); - private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer(); public LoLCommands(DiscordModule module) : base(module) @@ -42,7 +40,6 @@ namespace NadekoBot.Modules.Searches.Commands { try { - lock (cacheLock) CachedChampionImages = CachedChampionImages .Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); @@ -87,16 +84,14 @@ namespace NadekoBot.Modules.Searches.Commands var resolvedRole = role; var name = e.GetArg("champ").Replace(" ", "").ToLower(); CachedChampion champ = null; - lock (cacheLock) - { - CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ); - } - if (champ != null) - { - champ.ImageStream.Position = 0; - await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); - return; - } + + if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) + if (champ != null) + { + champ.ImageStream.Position = 0; + await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); + return; + } var allData = JArray.Parse(await Classes.SearchHelper.GetResponseStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Creds.LOLAPIKey}").ConfigureAwait(false)); JToken data = null; if (role != null) @@ -121,17 +116,13 @@ namespace NadekoBot.Modules.Searches.Commands role = allData[0]["role"].ToString(); resolvedRole = ResolvePos(role); } - lock (cacheLock) - { - CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ); - } - if (champ != null) - { - Console.WriteLine("Sending lol image from cache."); - champ.ImageStream.Position = 0; - await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); - return; - } + if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) + if (champ != null) + { + champ.ImageStream.Position = 0; + await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); + return; + } //name = data["title"].ToString(); // get all possible roles, and "select" the shown one var roles = new string[allData.Count]; diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs index d5c6c755..efe521ff 100644 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs @@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Searches.Commands } } catch { } - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); }; checkTimer.Start(); } @@ -254,7 +254,7 @@ namespace NadekoBot.Modules.Searches.Commands } config.ObservingStreams.Remove(toRemove); - ConfigHandler.SaveConfig(); + await ConfigHandler.SaveConfig().ConfigureAwait(false); await e.Channel.SendMessage($":ok: Removed `{toRemove.Username}`'s stream from notifications.").ConfigureAwait(false); }); diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index 1a2ed20b..45b50c33 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -48,7 +48,7 @@ namespace NadekoBot.Modules.Searches commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "we") - .Description($"Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | {Prefix}we Moscow RF") + .Description($"Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `{Prefix}we Moscow RF`") .Parameter("city", ParameterType.Required) .Parameter("country", ParameterType.Required) .Do(async e => @@ -69,7 +69,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "yt") .Parameter("query", ParameterType.Unparsed) - .Description("Searches youtubes and shows the first result") + .Description($"Searches youtubes and shows the first result | `{Prefix}yt query`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -106,7 +106,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "imdb") .Parameter("query", ParameterType.Unparsed) - .Description("Queries imdb for movies or series, show first result.") + .Description($"Queries imdb for movies or series, show first result. | `{Prefix}imdb query`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -130,7 +130,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "mang") .Alias(Prefix + "manga").Alias(Prefix + "mq") .Parameter("query", ParameterType.Unparsed) - .Description("Queries anilist for a manga and shows the first result.") + .Description($"Queries anilist for a manga and shows the first result. | `{Prefix}mq query`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -157,8 +157,16 @@ $@"🌍 **Weather for** 【{obj["target"]}】 .ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "randomdog") + .Alias(Prefix + "woof") + .Description("Shows a random dog image.") + .Do(async e => + { + await e.Channel.SendMessage("http://random.dog/" + await SearchHelper.GetResponseStringAsync("http://random.dog/woof").ConfigureAwait(false)).ConfigureAwait(false); + }); + cgb.CreateCommand(Prefix + "i") - .Description("Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten") + .Description($"Pulls the first image found using a search parameter. Use ~ir for different results. | `{Prefix}i cute kitten`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -184,7 +192,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "ir") - .Description("Pulls a random image using a search parameter. | ~ir cute kitten") + .Description($"Pulls a random image using a search parameter. | `{Prefix}ir cute kitten`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -211,7 +219,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "lmgtfy") - .Description("Google something for an idiot.") + .Description($"Google something for an idiot. | `{Prefix}lmgtfy query`") .Parameter("ffs", ParameterType.Unparsed) .Do(async e => { @@ -222,7 +230,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "google") .Alias(Prefix + "g") - .Description("Get a google search link for some terms.") + .Description($"Get a google search link for some terms. | `{Prefix}google query`") .Parameter("terms", ParameterType.Unparsed) .Do(async e => { @@ -234,7 +242,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "hs") - .Description("Searches for a Hearthstone card and shows its image. Takes a while to complete. |~hs Ysera") + .Description($"Searches for a Hearthstone card and shows its image. Takes a while to complete. | `{Prefix}hs Ysera`") .Parameter("name", ParameterType.Unparsed) .Do(async e => { @@ -275,7 +283,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "ud") - .Description("Searches Urban Dictionary for a word. |~ud Pineapple") + .Description($"Searches Urban Dictionary for a word. | `{Prefix}ud Pineapple`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -304,7 +312,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); // thanks to Blaubeerwald cgb.CreateCommand(Prefix + "#") - .Description("Searches Tagdef.com for a hashtag. |~# ff") + .Description($"Searches Tagdef.com for a hashtag. | `{Prefix}# ff`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -389,7 +397,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "revav") - .Description("Returns a google reverse image search for someone's avatar.") + .Description($"Returns a google reverse image search for someone's avatar. | `{Prefix}revav \"@SomeGuy\"") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -406,7 +414,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "revimg") - .Description("Returns a google reverse image search for an image from a link.") + .Description($"Returns a google reverse image search for an image from a link. | `{Prefix}revav Image link`") .Parameter("image", ParameterType.Unparsed) .Do(async e => { @@ -418,7 +426,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "safebooru") - .Description("Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~safebooru yuri+kissing") + .Description($"Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}safebooru yuri+kissing`") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -431,7 +439,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "wiki") - .Description("Gives you back a wikipedia link") + .Description($"Gives you back a wikipedia link | `{Prefix}wiki query`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -445,7 +453,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "clr") - .Description("Shows you what color corresponds to that hex. | `~clr 00ff00`") + .Description($"Shows you what color corresponds to that hex. | `{Prefix}clr 00ff00`") .Parameter("color", ParameterType.Unparsed) .Do(async e => { @@ -470,7 +478,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "videocall") - .Description("Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message.") + .Description($"Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `{Prefix}videocall \"@SomeGuy\"`") .Parameter("arg", ParameterType.Unparsed) .Do(async e => { @@ -494,7 +502,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "av") .Alias(Prefix + "avatar") .Parameter("mention", ParameterType.Required) - .Description("Shows a mentioned person's avatar. | ~av @X") + .Description($"Shows a mentioned person's avatar. | `{Prefix}av @X`") .Do(async e => { var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index 9663c7bc..e54aa020 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/NadekoBot/Modules/Translator/TranslateCommand.cs @@ -14,7 +14,7 @@ namespace NadekoBot.Modules.Translator { cgb.CreateCommand(Module.Prefix + "translate") .Alias(Module.Prefix + "trans") - .Description($"Translates from>to text. From the given language to the destiation language. | {Module.Prefix}trans en>fr Hello") + .Description($"Translates from>to text. From the given language to the destiation language. | `{Module.Prefix}trans en>fr Hello`") .Parameter("langs", ParameterType.Required) .Parameter("text", ParameterType.Unparsed) .Do(TranslateFunc()); diff --git a/NadekoBot/Modules/Trello/TrelloModule.cs b/NadekoBot/Modules/Trello/TrelloModule.cs index 5a98fe40..b144a2cf 100644 --- a/NadekoBot/Modules/Trello/TrelloModule.cs +++ b/NadekoBot/Modules/Trello/TrelloModule.cs @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Trello cgb.CreateCommand(Prefix + "bind") .Description("Bind a trello bot to a single channel. " + "You will receive notifications from your board when something is added or edited." + - " | bind [board_id]") + $" | `{Prefix}bind [board_id]`") .Parameter("board_id", Discord.Commands.ParameterType.Required) .Do(async e => { @@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Trello }); cgb.CreateCommand(Prefix + "cards") - .Description("Lists all cards from the supplied list. You can supply either a name or an index.") + .Description($"Lists all cards from the supplied list. You can supply either a name or an index. | `{Prefix}cards index`") .Parameter("list_name", Discord.Commands.ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Utility/UtilityModule.cs b/NadekoBot/Modules/Utility/UtilityModule.cs index 380e5acf..bd172c08 100644 --- a/NadekoBot/Modules/Utility/UtilityModule.cs +++ b/NadekoBot/Modules/Utility/UtilityModule.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Utility commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "whoplays") - .Description("Shows a list of users who are playing the specified game.") + .Description($"Shows a list of users who are playing the specified game. | `{Prefix}whoplays Overwatch`") .Parameter("game", ParameterType.Unparsed) .Do(async e => { @@ -52,16 +52,15 @@ namespace NadekoBot.Modules.Utility }); cgb.CreateCommand(Prefix + "inrole") - .Description("Lists every person from the provided role or roles (separated by a ',') on this server.") + .Description($"Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `{Prefix}inrole Role`") .Parameter("roles", ParameterType.Unparsed) .Do(async e => { await Task.Run(async () => { - if (!e.User.ServerPermissions.MentionEveryone) return; var arg = e.GetArg("roles").Split(',').Select(r => r.Trim()); string send = $"`Here is a list of users in a specfic role:`"; - foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str))) + foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@everyone" && str != "everyone")) { var role = e.Server.FindRoles(roleStr).FirstOrDefault(); if (role == null) continue; @@ -71,6 +70,11 @@ namespace NadekoBot.Modules.Utility while (send.Length > 2000) { + if (!e.User.ServerPermissions.ManageMessages) + { + await e.Channel.SendMessage($"{e.User.Mention} you are not allowed to use this command on roles with a lot of users in them to prevent abuse."); + return; + } var curstr = send.Substring(0, 2000); await e.Channel.Send(curstr.Substring(0, @@ -110,7 +114,7 @@ namespace NadekoBot.Modules.Utility }); cgb.CreateCommand(Prefix + "userid").Alias(Prefix + "uid") - .Description("Shows user ID.") + .Description($"Shows user ID. | `{Prefix}uid` or `{Prefix}uid \"@SomeGuy\"") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -122,11 +126,11 @@ namespace NadekoBot.Modules.Utility }); cgb.CreateCommand(Prefix + "channelid").Alias(Prefix + "cid") - .Description("Shows current channel ID.") + .Description($"Shows current channel ID. | `{Prefix}cid`") .Do(async e => await e.Channel.SendMessage("This channel's ID is " + e.Channel.Id).ConfigureAwait(false)); cgb.CreateCommand(Prefix + "serverid").Alias(Prefix + "sid") - .Description("Shows current server ID.") + .Description($"Shows current server ID. | `{Prefix}sid`") .Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false)); cgb.CreateCommand(Prefix + "roles") @@ -144,6 +148,18 @@ namespace NadekoBot.Modules.Utility } await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false); }); + + + cgb.CreateCommand(Prefix + "channeltopic") + .Alias(Prefix + "ct") + .Description($"Sends current channel's topic as a message. | `{Prefix}ct`") + .Do(async e => + { + var topic = e.Channel.Topic; + if (string.IsNullOrWhiteSpace(topic)) + return; + await e.Channel.SendMessage(topic).ConfigureAwait(false); + }); }); } } diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs index 4a091e3e..ad68beca 100644 --- a/NadekoBot/NadekoBot.cs +++ b/NadekoBot/NadekoBot.cs @@ -197,7 +197,7 @@ namespace NadekoBot return; } #if NADEKO_RELEASE - await Task.Delay(90000).ConfigureAwait(false); + await Task.Delay(100000).ConfigureAwait(false); #else await Task.Delay(1000).ConfigureAwait(false); #endif diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index cd467b7f..1b2e9305 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -46,6 +46,7 @@ true + true AnyCPU @@ -55,6 +56,7 @@ TRACE prompt 4 + true true @@ -78,13 +80,14 @@ true - bin\x64\Debug\ + bin\Debug\ TRACE;DEBUG full x64 prompt MinimumRecommendedRules.ruleset true + false bin\x64\Release\ @@ -115,6 +118,7 @@ prompt MinimumRecommendedRules.ruleset true + true @@ -186,6 +190,7 @@ + @@ -197,6 +202,7 @@ + @@ -259,7 +265,6 @@ - diff --git a/NadekoBot/_Models/DataModels/TestDataModel.cs b/NadekoBot/_Models/DataModels/TestDataModel.cs new file mode 100644 index 00000000..247743ca --- /dev/null +++ b/NadekoBot/_Models/DataModels/TestDataModel.cs @@ -0,0 +1,8 @@ +ο»Ώnamespace NadekoBot.DataModels +{ + internal class TestDataModel : IDataModel + { + public long TestNumber { get; set; } + public string TestString { get; set; } + } +} diff --git a/NadekoBot/_Models/JSONModels/Configuration.cs b/NadekoBot/_Models/JSONModels/Configuration.cs index 295134fe..dbe8455b 100644 --- a/NadekoBot/_Models/JSONModels/Configuration.cs +++ b/NadekoBot/_Models/JSONModels/Configuration.cs @@ -4,6 +4,8 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; namespace NadekoBot.Classes.JSONModels { @@ -88,6 +90,17 @@ namespace NadekoBot.Classes.JSONModels public bool IsRotatingStatus { get; set; } = false; public int BufferSize { get; set; } = 4.MiB(); + public string[] RaceAnimals { get; internal set; } = { + "🐼", + "🐻", + "🐧", + "🐨", + "🐬", + "🐞", + "πŸ¦€", + "πŸ¦„" }; + + [JsonIgnore] public List Quotes { get; set; } = new List(); [JsonIgnore] @@ -187,13 +200,17 @@ Nadeko Support Server: "; public static class ConfigHandler { - private static readonly object configLock = new object(); - public static void SaveConfig() + private static readonly SemaphoreSlim configLock = new SemaphoreSlim(1, 1); + public static async Task SaveConfig() { - lock (configLock) + await configLock.WaitAsync(); + try { File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented)); } + finally { + configLock.Release(); + } } public static bool IsBlackListed(MessageEventArgs evArgs) => IsUserBlacklisted(evArgs.User.Id) || diff --git a/NadekoBot/bin/Debug/data/config_example.json b/NadekoBot/bin/Debug/data/config_example.json index 1ae8a26e..9d6995c2 100644 --- a/NadekoBot/bin/Debug/data/config_example.json +++ b/NadekoBot/bin/Debug/data/config_example.json @@ -4,7 +4,16 @@ "ForwardToAllOwners": false, "IsRotatingStatus": false, "BufferSize": 4194304, - "Quotes": [], + "RaceAnimals": [ + "🐼", + "🐻", + "🐧", + "🐨", + "🐬", + "🐞", + "πŸ¦€", + "πŸ¦„" + ], "RemindMessageFormat": "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗", "CustomReactions": { "\\o\\": [ diff --git a/commandlist.md b/commandlist.md index 50cb13cc..7a9ee692 100644 --- a/commandlist.md +++ b/commandlist.md @@ -1,8 +1,9 @@ ######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/** -######You can donate on paypal: `nadekodiscordbot@gmail.com` +######You can donate on patreon: `https://patreon.com/nadekobot` +######or paypal: `nadekodiscordbot@gmail.com` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6048.2992` +Version: `NadekoBot v0.9.6053.28429` ### Help Command and aliases | Description | Usage ----------------|--------------|------- @@ -57,38 +58,38 @@ Command and aliases | Description | Usage `.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. `.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. `.restart` | Restarts the bot. Might not work. **Bot Owner Only** -`.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest -`.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin +`.setrole`, `.sr` | Sets a role for a given user. | `.sr @User Guest` +`.removerole`, `.rr` | Removes a role from a given user. | `.rr @User Admin` `.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` -`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User -`.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role` -`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55` -`.ban`, `.b` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic. -`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | .sb "@some Guy" Your behaviour is toxic. -`.kick`, `.k` | Kicks a mentioned user. -`.mute` | Mutes mentioned user or users. -`.unmute` | Unmutes mentioned user or users. -`.deafen`, `.deaf` | Deafens mentioned user or users -`.undeafen`, `.undef` | Undeafens mentioned user or users -`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. -`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. -`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. -`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. +`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | `.rar @User` +`.createrole`, `.cr` | Creates a role with a given name. | `.cr Awesome Role` +`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.rc Admin 255 200 100` or `.rc Admin ffba55` +`.ban`, `.b` | Bans a user by id or name with an optional message. | `.b "@some Guy" Your behaviour is toxic.` +`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | `.sb "@some Guy" Your behaviour is toxic.` +`.kick`, `.k` | Kicks a mentioned user. | `.k "@some Guy" Your behaviour is toxic.` +`.mute` | Mutes mentioned user or users. | `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"` +`.unmute` | Unmutes mentioned user or users. | `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` +`.deafen`, `.deaf` | Deafens mentioned user or users | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` +`.undeafen`, `.undef` | Undeafens mentioned user or users | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` +`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. | `.dvch VoiceChannelName` +`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. | `.cvch VoiceChannelName` +`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. | `.dtch TextChannelName` +`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. | `.ctch TextChannelName` `.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` -`.setchanlname`, `.schn` | Changed the name of the current channel. +`.setchanlname`, `.schn` | Changed the name of the current channel.| `.schn NewName` `.heap` | Shows allocated memory - **Bot Owner Only!** `.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** -`.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** +`.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** | .newnm BotName `.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg` -`.setgame` | Sets the bots game. **Bot Owner Only!** +`.setgame` | Sets the bots game. **Bot Owner Only!** | `.setgame Playing with kwoth` `.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!` -`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. +`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `.menro RoleName` `.unstuck` | Clears the message queue. **Bot Owner Only!** `.donators` | List of lovely people who donated to keep this project alive. -`.donadd` | Add a donator to the database. -`.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | .announce Useless spam -`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150` +`.donadd` | Add a donator to the database. | `.donadd Donate Amount` +`.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `.announce Useless spam` +`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.savechat 150` ### Utility Command and aliases | Description | Usage @@ -98,15 +99,16 @@ Command and aliases | Description | Usage `.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server `.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel `.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser -`.whoplays` | Shows a list of users who are playing the specified game. -`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. +`.whoplays` | Shows a list of users who are playing the specified game. | `.whoplays Overwatch` +`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role` `.checkmyperms` | Checks your userspecific permissions on this channel. `.stats` | Shows some basic stats for Nadeko. `.dysyd` | Shows some basic stats for Nadeko. -`.userid`, `.uid` | Shows user ID. -`.channelid`, `.cid` | Shows current channel ID. -`.serverid`, `.sid` | Shows current server ID. +`.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy" +`.channelid`, `.cid` | Shows current channel ID. | `.cid` +`.serverid`, `.sid` | Shows current server ID. | `.sid` `.roles` | List all roles on this server or a single user if specified. +`.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct` ### Permissions Command and aliases | Description | Usage @@ -122,38 +124,38 @@ Command and aliases | Description | Usage `;rolepermscopy`, `;rpc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role` `;chnlpermscopy`, `;cpc` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel` `;usrpermscopy`, `;upc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;upc @SomeUser ~ @SomeOtherUser` -`;verbose`, `;v` | Sets whether to show when a command/module is blocked. | ;verbose true +`;verbose`, `;v` | Sets whether to show when a command/module is blocked. | `;verbose true` `;srvrperms`, `;sp` | Shows banned permissions for this server. -`;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | ;rp AwesomeRole -`;chnlperms`, `;cp` | Shows banned permissions for a certain channel. No argument means for this channel. | ;cp #dev -`;userperms`, `;up` | Shows banned permissions for a certain user. No argument means for yourself. | ;up Kwoth -`;srvrmdl`, `;sm` | Sets a module's permission at the server level. | ;sm "module name" enable -`;srvrcmd`, `;sc` | Sets a command's permission at the server level. | ;sc "command name" disable -`;rolemdl`, `;rm` | Sets a module's permission at the role level. | ;rm "module name" enable MyRole -`;rolecmd`, `;rc` | Sets a command's permission at the role level. | ;rc "command name" disable MyRole -`;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | ;cm "module name" enable SomeChannel -`;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | ;cc "command name" enable SomeChannel -`;usrmdl`, `;um` | Sets a module's permission at the user level. | ;um "module name" enable SomeUsername -`;usrcmd`, `;uc` | Sets a command's permission at the user level. | ;uc "command name" enable SomeUsername -`;allsrvrmdls`, `;asm` | Sets permissions for all modules at the server level. | ;asm [enable/disable] -`;allsrvrcmds`, `;asc` | Sets permissions for all commands from a certain module at the server level. | ;asc "module name" [enable/disable] -`;allchnlmdls`, `;acm` | Sets permissions for all modules at the channel level. | ;acm [enable/disable] SomeChannel -`;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | ;acc "module name" [enable/disable] SomeChannel -`;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | ;arm [enable/disable] MyRole -`;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | ;arc "module name" [enable/disable] MyRole -`;ubl` | Blacklists a mentioned user. | ;ubl [user_mention] -`;uubl` | Unblacklists a mentioned user. | ;uubl [user_mention] -`;cbl` | Blacklists a mentioned channel (#general for example). | ;cbl #some_channel -`;cubl` | Unblacklists a mentioned channel (#general for example). | ;cubl #some_channel -`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;sbl [servername/serverid] +`;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | `;rp AwesomeRole` +`;chnlperms`, `;cp` | Shows banned permissions for a certain channel. No argument means for this channel. | `;cp #dev` +`;userperms`, `;up` | Shows banned permissions for a certain user. No argument means for yourself. | `;up Kwoth` +`;srvrmdl`, `;sm` | Sets a module's permission at the server level. | `;sm "module name" enable` +`;srvrcmd`, `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable` +`;rolemdl`, `;rm` | Sets a module's permission at the role level. | `;rm "module name" enable MyRole` +`;rolecmd`, `;rc` | Sets a command's permission at the role level. | `;rc "command name" disable MyRole` +`;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | `;cm "module name" enable SomeChannel` +`;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | `;cc "command name" enable SomeChannel` +`;usrmdl`, `;um` | Sets a module's permission at the user level. | `;um "module name" enable SomeUsername` +`;usrcmd`, `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername` +`;allsrvrmdls`, `;asm` | Sets permissions for all modules at the server level. | `;asm [enable/disable]` +`;allsrvrcmds`, `;asc` | Sets permissions for all commands from a certain module at the server level. | `;asc "module name" [enable/disable]` +`;allchnlmdls`, `;acm` | Sets permissions for all modules at the channel level. | `;acm [enable/disable] SomeChannel` +`;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | `;acc "module name" [enable/disable] SomeChannel` +`;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | `;arm [enable/disable] MyRole` +`;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | `;arc "module name" [enable/disable] MyRole` +`;ubl` | Blacklists a mentioned user. | `;ubl [user_mention]` +`;uubl` | Unblacklists a mentioned user. | `;uubl [user_mention]` +`;cbl` | Blacklists a mentioned channel (#general for example). | `;cbl #some_channel` +`;cubl` | Unblacklists a mentioned channel (#general for example). | `;cubl #some_channel` +`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `;sbl [servername/serverid]` `;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. | `;cmdcd "some cmd" 5` `;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns. ### Conversations Command and aliases | Description | Usage ----------------|--------------|------- -`..` | Adds a new quote with the specified name (single word) and message (no limit). | .. abc My message -`...` | Shows a random quote with a specified name. | .. abc +`..` | Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message` +`...` | Shows a random quote with a specified name. | `... abc` `..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc` `@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000 `@BotName die` | Works only for the owner. Shuts the bot down. @@ -173,11 +175,13 @@ Command and aliases | Description | Usage `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5 `$rolluo` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice (unordered). If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5 `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` -`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. +`$race` | Starts a new animal race. +`$joinrace`, `$jr` | Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5` +`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName `$$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone` -`$give` | Give someone a certain amount of NadekoFlowers +`$give` | Give someone a certain amount of NadekoFlowers|`$give 1 "@SomeGuy"` `$award` | Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person` -`$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** +`$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** | `$take 1 "@someguy"` `$betroll`, `$br` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | $br 5 `$leaderboard`, `$lb` | @@ -196,9 +200,9 @@ Command and aliases | Description | Usage `>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) `>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `>gc` or `>gc 60` `>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | >leet 3 Hello -`>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more -`>8ball` | Ask the 8ball a yes/no question. -`>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors +`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more` +`>8ball` | Ask the 8ball a yes/no question. | `>8ball should i do something` +`>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors` `>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` ### Music @@ -234,7 +238,7 @@ Command and aliases | Description | Usage `!!load` | Loads a playlist under a certain name. | `!!load classical-1` `!!playlists`, `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1` `!!deleteplaylist`, `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5` -`!!goto` | Goes to a specific time in seconds in a song. +`!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30` `!!getlink`, `!!gl` | Shows a link to the currently playing song. `!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) @@ -262,41 +266,42 @@ Command and aliases | Description | Usage `~pokemonability`, `~pokeab` | Searches for a pokemon ability. `~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ `~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` -`~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | ~we Moscow RF -`~yt` | Searches youtubes and shows the first result +`~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF` +`~yt` | Searches youtubes and shows the first result | `~yt query` `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. -`~imdb` | Queries imdb for movies or series, show first result. -`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. +`~imdb` | Queries imdb for movies or series, show first result. | `~imdb query` +`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq query` `~randomcat`, `~meow` | Shows a random cat image. -`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten -`~ir` | Pulls a random image using a search parameter. | ~ir cute kitten -`~lmgtfy` | Google something for an idiot. -`~google`, `~g` | Get a google search link for some terms. -`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera -`~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple -`~#` | Searches Tagdef.com for a hashtag. | ~# ff +`~randomdog`, `~woof` | Shows a random dog image. +`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten` +`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten` +`~lmgtfy` | Google something for an idiot. | `~lmgtfy query` +`~google`, `~g` | Get a google search link for some terms. | `~google query` +`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera` +`~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple` +`~#` | Searches Tagdef.com for a hashtag. | `~# ff` `~quote` | Shows a random quote. `~catfact` | Shows a random catfact from `~yomama`, `~ym` | Shows a random joke from `~randjoke`, `~rj` | Shows a random joke from `~chucknorris`, `~cn` | Shows a random chucknorris joke from `~magicitem`, `~mi` | Shows a random magicitem from -`~revav` | Returns a google reverse image search for someone's avatar. -`~revimg` | Returns a google reverse image search for an image from a link. -`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~safebooru yuri+kissing -`~wiki` | Gives you back a wikipedia link +`~revav` | Returns a google reverse image search for someone's avatar. | `~revav "@SomeGuy" +`~revimg` | Returns a google reverse image search for an image from a link. | `~revav Image link` +`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~safebooru yuri+kissing` +`~wiki` | Gives you back a wikipedia link | `~wiki query` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` -`~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. -`~av`, `~avatar` | Shows a mentioned person's avatar. | ~av @X +`~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"` +`~av`, `~avatar` | Shows a mentioned person's avatar. | `~av @X` ### NSFW Command and aliases | Description | Usage ----------------|--------------|------- -`~hentai` | Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~hentai yuri+kissing -`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~danbooru yuri+kissing -`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~gelbooru yuri+kissing -`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~rule34 yuri+kissing -`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | ~e621 yuri kissing +`~hentai` | Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~hentai yuri+kissing` +`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~danbooru yuri+kissing` +`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~gelbooru yuri+kissing` +`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~rule34 yuri+kissing` +`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `~e621 yuri kissing` `~cp` | We all know where this will lead you to. `~boobs` | Real adult content. `~butts`, `~ass`, `~butt` | Real adult content. @@ -311,7 +316,7 @@ Command and aliases | Description | Usage `,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] `,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] `,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] -`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whos place to unclaim | ,uc [war_number] [optional_other_name] +`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | ,uc [war_number] [optional_other_name] `,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number] ### Pokegame @@ -326,7 +331,7 @@ Command and aliases | Description | Usage ### Translator Command and aliases | Description | Usage ----------------|--------------|------- -`~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | ~trans en>fr Hello +`~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | `~trans en>fr Hello` `~translangs` | List the valid languages for translation. ### Customreactions @@ -352,7 +357,7 @@ Command and aliases | Description | Usage ### Trello Command and aliases | Description | Usage ----------------|--------------|------- -`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | bind [board_id] +`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | `trello bind [board_id]` `trello unbind` | Unbinds a bot from the channel and board. `trello lists`, `trello list` | Lists all lists yo ;) -`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. +`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. | `trello cards index` diff --git a/discord.net b/discord.net index 3e519b5e..3a337311 160000 --- a/discord.net +++ b/discord.net @@ -1 +1 @@ -Subproject commit 3e519b5e0b33175e5a5ca247322b7082de484e15 +Subproject commit 3a33731135f1b7dd2efdb51b16158c84bb22da66 diff --git a/ffmpeg-installer b/ffmpeg-installer new file mode 160000 index 00000000..d593fe3a --- /dev/null +++ b/ffmpeg-installer @@ -0,0 +1 @@ +Subproject commit d593fe3a86be7da9e4177865446f2f5ca58b6be4