diff --git a/.gitignore b/.gitignore index 0b9a35ab..1dd55335 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ NadekoBot/bin/Debug/data/ServerSpecificConfigs.json NadekoBot.sln.iml .idea/workspace.xml .idea/vcs.xml -.idea/modules.xml \ No newline at end of file +.idea/modules.xml +NadekoBot/bin/Debug/data/config_xnaas.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 9ff1a297..1fa69c2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "discord.net"] path = discord.net - url = git://github.com/rogueexception/discord.net.git + url = git://github.com/kwoth/discord.net.git diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index db4c7314..41037d4a 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -36,8 +36,11 @@ ________________________________________________________________________________ ###### 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 + 5) Follow these steps on how to setup API keys: - Go to https://console.developers.google.com and log in. - Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs." diff --git a/LinuxSetup.md b/LinuxSetup.md index 14152c90..224f0463 100644 --- a/LinuxSetup.md +++ b/LinuxSetup.md @@ -53,6 +53,15 @@ Note if the command is not be initiated, hit **Enter**
echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list
 
+**2.6)** +*ONLY CentOS 7, Fedora 19 (and later)* +
yum install yum-util
+
+
rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"
+
+
yum-config-manager --add-repo http://download.mono-project.com/repo/centos/
+
+ **3)**
apt-get install mono-devel
 
@@ -68,6 +77,18 @@ Note if the command is not be initiated, hit **Enter**
sudo apt-get install libopus-dev
 
+**In case you are having issues with Mono where you get a random string and the bot won't run, do this:** + +
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
+
+
echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
+
+
apt-get install ca-certificates-mono
+
+
mozroots --import --sync
+
+ + ######FFMPEG **6)** @@ -87,6 +108,20 @@ Note if the command is not be initiated, hit **Enter** *Before executing* `sudo apt-get install ffmpeg` +*If you are running Debian 8 Jessie, please, follow these steps:* + +`wget http://luxcaeli.de/installer.sh && sudo bash installer.sh` (Thanks to Eleria<3) + +In case you are not able to install it with installer ^up there, follow these steps: + +`sudo apt-get update` + +`echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list` + +`sudo apt-get update` + +`sudo apt-get install ffmpeg -y` + ######Uncomplicated Firewall UFW **7)** @@ -121,12 +156,10 @@ Note if the command is not be initiated, hit **Enter** ######NOW WE NEED TO IMPORT SOME DISCORD CERTS **13)** -
mozroots --import --ask-remove --machine
-
+`certmgr -ssl https://discordapp.com` **14)** -
certmgr --ssl https://gateway.discord.gg
-
+`certmgr --ssl https://gateway.discord.gg` Type `yes` and hit Enter **(three times - as it will ask for three times)** diff --git a/NadekoBot.sln b/NadekoBot.sln index 096b6db8..eb9860d0 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot", "NadekoBot\NadekoBot.csproj", "{27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}" EndProject @@ -13,70 +13,104 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "disc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "discord.net\src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{45B2545D-C612-4919-B34C-D65EA1371C51}" -EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{7bfef748-b934-4621-9b11-6302e3a9f6b3}*SharedItemsImports = 4 discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{1b5603b4-6f8f-4289-b945-7baae523d740}*SharedItemsImports = 4 discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{3091164f-66ae-4543-a63d-167c1116241d}*SharedItemsImports = 4 + discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{7bfef748-b934-4621-9b11-6302e3a9f6b3}*SharedItemsImports = 4 discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{8d71a857-879a-4a10-859e-5ff824ed6688}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 FullDebug|Any CPU = FullDebug|Any CPU + FullDebug|x64 = FullDebug|x64 NadekoRelease|Any CPU = NadekoRelease|Any CPU + NadekoRelease|x64 = NadekoRelease|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|x64.ActiveCfg = Debug|x64 + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|x64.Build.0 = Debug|x64 {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|x64.ActiveCfg = Debug|x64 + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|x64.Build.0 = Debug|x64 {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|x64.ActiveCfg = NadekoRelease|x64 + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|x64.Build.0 = NadekoRelease|x64 {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|Any CPU.ActiveCfg = Release|Any CPU {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|Any CPU.Build.0 = Release|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|x64.ActiveCfg = Release|x64 + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|x64.Build.0 = Release|x64 {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|x64.ActiveCfg = Debug|x64 + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|x64.Build.0 = Debug|x64 {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|x64.ActiveCfg = Debug|x64 + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|x64.Build.0 = Debug|x64 {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|x64.ActiveCfg = Release|x64 + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|x64.Build.0 = Release|x64 {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|x64.ActiveCfg = Release|x64 + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|x64.Build.0 = Release|x64 {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|x64.ActiveCfg = Debug|x64 + {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|x64.Build.0 = Debug|x64 {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|x64.ActiveCfg = Debug|x64 + {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|x64.Build.0 = Debug|x64 {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|x64.ActiveCfg = Release|x64 + {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|x64.Build.0 = Release|x64 {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|x64.ActiveCfg = Release|x64 + {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|x64.Build.0 = Release|x64 {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Debug|x64.ActiveCfg = Debug|x64 + {3091164F-66AE-4543-A63D-167C1116241D}.Debug|x64.Build.0 = Debug|x64 {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|x64.ActiveCfg = Debug|x64 + {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|x64.Build.0 = Debug|x64 {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|x64.ActiveCfg = Release|x64 + {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|x64.Build.0 = Release|x64 {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Release|x64.ActiveCfg = Release|x64 + {3091164F-66AE-4543-A63D-167C1116241D}.Release|x64.Build.0 = Release|x64 {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|x64.ActiveCfg = Debug|x64 + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|x64.Build.0 = Debug|x64 {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|x64.ActiveCfg = Debug|x64 + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|x64.Build.0 = Debug|x64 {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|x64.ActiveCfg = Release|x64 + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|x64.Build.0 = Release|x64 {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.Release|Any CPU.Build.0 = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|x64.ActiveCfg = Release|x64 + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NadekoBot/Classes/DBHandler.cs b/NadekoBot/Classes/DBHandler.cs index 9fb664f9..3f2963cc 100644 --- a/NadekoBot/Classes/DBHandler.cs +++ b/NadekoBot/Classes/DBHandler.cs @@ -161,9 +161,7 @@ namespace NadekoBot.Classes using (var conn = new SQLiteConnection(FilePath)) { foreach (var o in ocol) - { - conn.InsertOrReplace(o, typeof(T)); - } + conn.InsertOrReplace(o); } } diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs index 1b162209..a4071ded 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/NadekoBot/Classes/Extensions.cs @@ -46,10 +46,10 @@ namespace NadekoBot.Extensions if (num == 0) return string.Empty; if (num <= 3) - return string.Join("", str.Select(c => '.')); + return string.Concat(str.Select(c => '.')); if (str.Length < num) return str; - return string.Join("", str.Take(num - 3)) + (hideDots ? "" : "..."); + return string.Concat(str.Take(num - 3)) + (hideDots ? "" : "..."); } /// /// Removes trailing S or ES (if specified) on the given string if the num is 1 @@ -237,7 +237,7 @@ namespace NadekoBot.Extensions public static string Matrix(this string s) => - string.Join("", s.Select(c => c.ToString() + " ̵̢̬̜͉̞̭̖̰͋̉̎ͬ̔̇̌̀".TrimTo(rng.Next(0, 12), true))); + string.Concat(s.Select(c => c.ToString() + " ̵̢̬̜͉̞̭̖̰͋̉̎ͬ̔̇̌̀".TrimTo(rng.Next(0, 12), true))); //.Replace("`", ""); public static void ForEach(this IEnumerable source, Action action) diff --git a/NadekoBot/Classes/FlowersHandler.cs b/NadekoBot/Classes/FlowersHandler.cs index 86e4086b..6fa042ea 100644 --- a/NadekoBot/Classes/FlowersHandler.cs +++ b/NadekoBot/Classes/FlowersHandler.cs @@ -21,12 +21,12 @@ namespace NadekoBot.Classes if (silent) return; - var flows = amount +" " + NadekoBot.Config.CurrencySign; + var flows = amount + " " + NadekoBot.Config.CurrencySign; await u.SendMessage("👑Congratulations!👑\nYou received: " + flows).ConfigureAwait(false); } - public static bool RemoveFlowers(Discord.User u, string reason, int amount) + public static async Task RemoveFlowers(Discord.User u, string reason, int amount, bool silent=false, string message="👎`Bot owner has taken {0}{1} from you.`") { if (amount <= 0) return false; @@ -42,6 +42,11 @@ namespace NadekoBot.Classes UserId = (long)u.Id, Value = -amount, }); + + if (silent) + return true; + + await u.SendMessage(string.Format(message,amount,NadekoBot.Config.CurrencySign)).ConfigureAwait(false); return true; } } diff --git a/NadekoBot/Classes/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs index 42f70398..43bfbe37 100644 --- a/NadekoBot/Classes/SearchHelper.cs +++ b/NadekoBot/Classes/SearchHelper.cs @@ -145,8 +145,6 @@ namespace NadekoBot.Classes public static async Task FindYoutubeUrlByKeywords(string keywords) { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) - throw new InvalidCredentialException("Google API Key is missing."); if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords), "Query not specified."); if (keywords.Length > 150) @@ -158,6 +156,10 @@ namespace NadekoBot.Classes { return $"https://www.youtube.com/watch?v={match.Groups["id"].Value}"; } + + if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) + throw new InvalidCredentialException("Google API Key is missing."); + var response = await GetResponseStringAsync( $"https://www.googleapis.com/youtube/v3/search?" + $"part=snippet&maxResults=1" + @@ -357,7 +359,7 @@ namespace NadekoBot.Classes using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false))) { - var json = "{\"longUrl\":\"" + url + "\"}"; + var json = "{\"longUrl\":\"" + Uri.EscapeDataString(url) + "\"}"; streamWriter.Write(json); } @@ -382,7 +384,7 @@ namespace NadekoBot.Classes { var i = 0; return "```xl\n" + string.Join("\n", items.GroupBy(item => (i++) / cols) - .Select(ig => string.Join("", ig.Select(el => howToPrint(el))))) + .Select(ig => string.Concat(ig.Select(el => howToPrint(el))))) + $"\n```"; } } diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index bbc4eadd..ff42f0fd 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/NadekoBot/Classes/ServerSpecificConfig.cs @@ -27,7 +27,13 @@ namespace NadekoBot.Classes { configs = JsonConvert .DeserializeObject>( - File.ReadAllText(filePath)); + File.ReadAllText(filePath), new JsonSerializerSettings() { + Error = (s,e) => { + if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") { + e.ErrorContext.Handled = true; + } + } + }); } catch (Exception ex) { @@ -94,6 +100,21 @@ namespace NadekoBot.Classes } } + [JsonIgnore] + private ObservableCollection logserverIgnoreChannels; + public ObservableCollection LogserverIgnoreChannels { + get { return logserverIgnoreChannels; } + set { + logserverIgnoreChannels = value; + if (value != null) + logserverIgnoreChannels.CollectionChanged += (s, e) => + { + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + }; + } + } + [JsonProperty("LogPresenceChannel")] private ulong? logPresenceChannel = null; [JsonIgnore] @@ -136,6 +157,8 @@ namespace NadekoBot.Classes } } + + [JsonIgnore] private ulong autoAssignedRole = 0; public ulong AutoAssignedRole { @@ -148,8 +171,8 @@ namespace NadekoBot.Classes } [JsonIgnore] - private ObservableCollection generateCurrencyChannels; - public ObservableCollection GenerateCurrencyChannels { + private ObservableConcurrentDictionary generateCurrencyChannels; + public ObservableConcurrentDictionary GenerateCurrencyChannels { get { return generateCurrencyChannels; } set { generateCurrencyChannels = value; @@ -173,6 +196,19 @@ namespace NadekoBot.Classes } } + [JsonIgnore] + private bool exclusiveSelfAssignedRoles = false; + public bool ExclusiveSelfAssignedRoles + { + get { return exclusiveSelfAssignedRoles; } + set + { + exclusiveSelfAssignedRoles = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + [JsonIgnore] private ObservableCollection observingStreams; @@ -204,8 +240,9 @@ namespace NadekoBot.Classes { ListOfSelfAssignableRoles = new ObservableCollection(); ObservingStreams = new ObservableCollection(); - GenerateCurrencyChannels = new ObservableCollection(); + GenerateCurrencyChannels = new ObservableConcurrentDictionary(); VoiceChannelLog = new ObservableConcurrentDictionary(); + LogserverIgnoreChannels = new ObservableCollection(); } public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; @@ -239,7 +276,7 @@ namespace NadekoBot.Classes public override int GetHashCode() { - return (int)((int)ServerId + Username.Length + (int)Type); + return (int)ServerId + Username.Length + (int)Type; } } } diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index 36538813..ab6dafae 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -90,7 +90,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "setrole").Alias(Prefix + "sr") - .Description("Sets a role for a given user.\n**Usage**: .sr @User Guest") + .Description("Sets a role for a given user. | .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.\n**Usage**: .rr @User Admin") + .Description("Removes a role from a given user. | .rr @User Admin") .Parameter("user_name", ParameterType.Required) .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) @@ -171,7 +171,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "renamerole") .Alias(Prefix + "renr") - .Description($"Renames a role. Role you are renaming must be lower than bot's highest role.\n**Usage**: `{Prefix}renr \"First role\" SecondRole`") + .Description($"Renames a role. Role you are renaming must be lower than bot's highest role. | `{Prefix}renr \"First role\" SecondRole`") .Parameter("r1", ParameterType.Required) .Parameter("r2", ParameterType.Required) .AddCheck(new SimpleCheckers.ManageRoles()) @@ -204,7 +204,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "removeallroles").Alias(Prefix + "rar") - .Description("Removes all roles from a mentioned user.\n**Usage**: .rar @User") + .Description("Removes all roles from a mentioned user. | .rar @User") .Parameter("user_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.\n**Usage**: `.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. | `.color Admin 255 200 100` or `.color 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.\n**Usage**: .b \"@some Guy\" Your behaviour is toxic.") + .Description("Bans a user by id or name with an optional message. | .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.\n**Usage**: .sb \"@some Guy\" Your behaviour is toxic.") + .Description("Bans and then unbans a user by id or name with an optional message. | .sb \"@some Guy\" Your behaviour is toxic.") .Do(async e => { var msg = e.GetArg("msg"); @@ -592,7 +592,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "settopic") .Alias(Prefix + "st") - .Description($"Sets a topic on the current channel.\n**Usage**: `{Prefix}st My new topic`") + .Description($"Sets a topic on the current channel. | `{Prefix}st My new topic`") .AddCheck(SimpleCheckers.ManageChannels()) .Parameter("topic", ParameterType.Unparsed) .Do(async e => @@ -628,27 +628,18 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "prune") .Alias(Prefix + "clr") .Description( - "`.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.\n**Usage**: `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`") + "`.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`") .Parameter("user_or_num", ParameterType.Optional) .Parameter("num", ParameterType.Optional) .Do(async e => { + Message[] msgs; if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear nadeko's messages, no permissions required { - await Task.Run(async () => - { - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == e.Server.CurrentUser.Id); - foreach (var m in msgs) - { - try - { - await m.Delete().ConfigureAwait(false); - } - catch { } - await Task.Delay(100).ConfigureAwait(false); - } - - }).ConfigureAwait(false); + msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray(); + if (!msgs.Any()) + return; + await e.Channel.DeleteMessages(msgs).ConfigureAwait(false); return; } if (!e.User.GetPermissions(e.Channel).ManageMessages) @@ -665,11 +656,7 @@ namespace NadekoBot.Modules.Administration if (val <= 0) return; val++; - foreach (var msg in await e.Channel.DownloadMessages(val).ConfigureAwait(false)) - { - await msg.Delete().ConfigureAwait(false); - await Task.Delay(100).ConfigureAwait(false); - } + await e.Channel.DeleteMessages((await e.Channel.DownloadMessages(val).ConfigureAwait(false)).ToArray()).ConfigureAwait(false); return; } //else if first argument is user @@ -679,20 +666,10 @@ namespace NadekoBot.Modules.Administration val = 100; if (!int.TryParse(e.GetArg("num"), out val)) val = 100; - await Task.Run(async () => - { - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == usr.Id).Take(val); - foreach (var m in msgs) - { - try - { - await m.Delete().ConfigureAwait(false); - } - catch { } - await Task.Delay(100).ConfigureAwait(false); - } - - }).ConfigureAwait(false); + msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == usr.Id).Take(val).ToArray(); + if (!msgs.Any()) + return; + await e.Channel.DeleteMessages(msgs).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "die") @@ -719,7 +696,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!**\n**Usage**: `.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!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`") .Parameter("img", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -750,7 +727,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!**\n**Usage**: `.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!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`") .Parameter("ids", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) @@ -761,7 +738,7 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(msg)) return; - var ids = e.GetArg("ids").Split('-'); + var ids = e.GetArg("ids").Split('|'); if (ids.Length != 2) return; var sid = ulong.Parse(ids[0]); @@ -877,7 +854,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!**\n**Usage**: {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 => @@ -890,21 +867,6 @@ namespace NadekoBot.Modules.Administration await e.Channel.SendMessage(":ok:").ConfigureAwait(false); }); - cgb.CreateCommand(Prefix + "leave") - .Description("Leaves a server with a supplied ID.\n**Usage**: `.leave 493243292839`") - .Parameter("num", ParameterType.Required) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var srvr = NadekoBot.Client.Servers.Where(s => s.Id.ToString() == e.GetArg("num").Trim()).FirstOrDefault(); - if (srvr == null) - { - return; - } - await srvr.Leave().ConfigureAwait(false); - await e.Channel.SendMessage("`Done.`").ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "savechat") .Description("Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150`") .Parameter("cnt", ParameterType.Required) @@ -929,7 +891,7 @@ namespace NadekoBot.Modules.Administration lastmsgId = msgs[msgs.Count - 1].Id; cnt -= 100; } - await e.User.SendFile($"Chatlog-{e.Server.Name}/#{e.Channel.Name}-{DateTime.Now}.txt", JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream()); + await e.User.SendFile($"Chatlog-{e.Server.Name}/#{e.Channel.Name}-{DateTime.Now}.txt", JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream()).ConfigureAwait(false); }); }); diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index 4aba4b10..fcd970a7 100644 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -2,6 +2,7 @@ using Discord.Commands; using NadekoBot.Classes; using NadekoBot.Modules.Permissions.Classes; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -22,7 +23,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "addcustreact") .Alias(Prefix + "acr") - .Description($"Add a custom reaction. Guide here: **Bot Owner Only!** \n**Usage**: {Prefix}acr \"hello\" I love saying hello to %user%") + .Description($"Add a custom reaction. Guide here: **Bot Owner Only!** | {Prefix}acr \"hello\" I love saying hello to %user%") .AddCheck(SimpleCheckers.OwnerOnly()) .Parameter("name", ParameterType.Required) .Parameter("message", ParameterType.Unparsed) @@ -46,16 +47,29 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "listcustreact") .Alias(Prefix + "lcr") - .Description($"Lists all current custom reactions (paginated with 30 commands per page).\n**Usage**:{Prefix}lcr 1") + .Description($"Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. |{Prefix}lcr 1") .Parameter("num", ParameterType.Required) .Do(async e => { + var numStr = e.GetArg("num"); + + if (numStr.ToUpperInvariant() == "ALL") + { + var fullstr = String.Join("\n", NadekoBot.Config.CustomReactions.Select(kvp => kvp.Key)); + do + { + var str = string.Concat(fullstr.Take(1900)); + fullstr = new string(fullstr.Skip(1900).ToArray()); + await e.User.SendMessage("```xl\n" + str + "```"); + } while (fullstr.Length != 0); + return; + } int num; - if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) num = 1; + if (!int.TryParse(numStr, out num) || num <= 0) num = 1; var cmds = GetCustomsOnPage(num - 1); if (!cmds.Any()) { - await e.Channel.SendMessage(""); + await e.Channel.SendMessage("`There are no custom reactions.`"); } else { @@ -66,7 +80,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "showcustreact") .Alias(Prefix + "scr") - .Description($"Shows all possible responses from a single custom reaction.\n**Usage**:{Prefix}scr %mention% bb") + .Description($"Shows all possible responses from a single custom reaction. |{Prefix}scr %mention% bb") .Parameter("name", ParameterType.Unparsed) .Do(async e => { @@ -85,7 +99,7 @@ namespace NadekoBot.Modules.Administration.Commands int i = 1; foreach (var reaction in items) { - message.AppendLine($"[{i++}] " + Format.Code(reaction)); + message.AppendLine($"[{i++}] " + Format.Code(Format.Escape(reaction))); } await e.Channel.SendMessage(message.ToString()); }); diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 8f51c9e5..3bbce6d2 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -51,8 +51,9 @@ namespace NadekoBot.Modules.Administration.Commands { try { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || config.LogserverIgnoreChannels.Contains(e.After.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -72,8 +73,9 @@ namespace NadekoBot.Modules.Administration.Commands { try { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -87,8 +89,9 @@ namespace NadekoBot.Modules.Administration.Commands { try { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -164,8 +167,9 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null || e.Channel.Id == chId) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -192,8 +196,9 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null || e.Channel.Id == chId) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -219,8 +224,9 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null || e.Channel.Id == chId) + var config = SpecificConfigurations.Default.Of(e.Server.Id); + var chId = config.LogServerChannel; + if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -370,6 +376,25 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); }); + + cgb.CreateCommand(Prefix + "logignore") + .Description($"Toggles whether the {Prefix}logserver command ignores this channel. Useful if you have hidden admin channel and public log channel.") + .AddCheck(SimpleCheckers.OwnerOnly()) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(async e => + { + var config = SpecificConfigurations.Default.Of(e.Server.Id); + if (config.LogserverIgnoreChannels.Remove(e.Channel.Id)) + { + await e.Channel.SendMessage($"`{Prefix}logserver will stop ignoring this channel.`"); + } + else + { + config.LogserverIgnoreChannels.Add(e.Channel.Id); + await e.Channel.SendMessage($"`{Prefix}logserver will ignore this channel.`"); + } + }); + cgb.CreateCommand(Module.Prefix + "userpresence") .Description("Starts logging to this channel when someone from the server goes online/offline/idle.") .AddCheck(SimpleCheckers.ManageServer()) diff --git a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs index abcf35e5..4aa521a1 100644 --- a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs +++ b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs @@ -73,7 +73,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "repeat") .Description("Repeat a message every X minutes. If no parameters are specified, " + - "repeat is disabled. Requires manage messages.\n**Usage**:`.repeat 5 Hello there`") + "repeat is disabled. Requires manage messages. |`.repeat 5 Hello there`") .Parameter("minutes", ParameterType.Optional) .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.ManageMessages()) diff --git a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index eaf59a3a..2ef54772 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -1,6 +1,8 @@ using Discord.Commands; +using Discord.Net; using NadekoBot.Classes; using NadekoBot.Modules.Permissions.Classes; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -15,7 +17,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "asar") .Description("Adds a role, or list of roles separated by whitespace" + - "(use quotations for multiword roles) to the list of self-assignable roles.\n**Usage**: .asar Gamer") + "(use quotations for multiword roles) to the list of self-assignable roles. | .asar Gamer") .Parameter("roles", ParameterType.Multiple) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -74,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) + foreach (var roleId in config.ListOfSelfAssignableRoles.OrderBy(r=>r.ToString())) { var role = e.Server.GetRole(roleId); if (role == null) @@ -94,10 +96,23 @@ namespace NadekoBot.Modules.Administration.Commands await e.Channel.SendMessage(msg.ToString()).ConfigureAwait(false); }); + + + 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 => + { + var config = SpecificConfigurations.Default.Of(e.Server.Id); + config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles; + string exl = config.ExclusiveSelfAssignedRoles ? "exclusive" : "not exclusive"; + await e.Channel.SendMessage("Self assigned roles are now " + exl); + }); + cgb.CreateCommand(Module.Prefix + "iam") .Description("Adds a role to you that you choose. " + "Role must be on a list of self-assignable roles." + - "\n**Usage**: .iam Gamer") + " | .iam Gamer") .Parameter("role", ParameterType.Unparsed) .Do(async e => { @@ -121,17 +136,26 @@ namespace NadekoBot.Modules.Administration.Commands await e.Channel.SendMessage($":anger:You already have {role.Name} role.").ConfigureAwait(false); return; } + var sameRoles = e.User.Roles.Where(r => config.ListOfSelfAssignableRoles.Contains(r.Id)); + if (config.ExclusiveSelfAssignedRoles && sameRoles.Any()) + { + await e.Channel.SendMessage($":anger:You already have {sameRoles.FirstOrDefault().Name} role.").ConfigureAwait(false); + return; + } try { await e.User.AddRoles(role).ConfigureAwait(false); } - catch + catch(HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + } + catch (Exception) { 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); } var msg = await e.Channel.SendMessage($":ok:You now have {role.Name} role.").ConfigureAwait(false); - await Task.Delay(3000); - await msg.Delete(); + await Task.Delay(3000).ConfigureAwait(false); + await msg.Delete().ConfigureAwait(false); try { await e.Message.Delete().ConfigureAwait(false); @@ -143,7 +167,7 @@ namespace NadekoBot.Modules.Administration.Commands .Alias(Module.Prefix + "iamn") .Description("Removes a role to you that you choose. " + "Role must be on a list of self-assignable roles." + - "\n**Usage**: .iamn Gamer") + " | .iamn Gamer") .Parameter("role", ParameterType.Unparsed) .Do(async e => { @@ -168,7 +192,14 @@ namespace NadekoBot.Modules.Administration.Commands return; } await e.User.RemoveRoles(role).ConfigureAwait(false); - await e.Channel.SendMessage($":ok:Successfuly removed {role.Name} role from you.").ConfigureAwait(false); + var msg = await e.Channel.SendMessage($":ok:Successfuly removed {role.Name} role from you.").ConfigureAwait(false); + await Task.Delay(3000).ConfigureAwait(false); + await msg.Delete().ConfigureAwait(false); + try + { + await e.Message.Delete().ConfigureAwait(false); + } + catch { } }); } } diff --git a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/NadekoBot/Modules/Administration/Commands/SelfCommands.cs index e905d807..c3b5fad2 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -14,7 +14,7 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "leave") - .Description("Makes Nadeko leave the server. Either name or id required.\n**Usage**: `.leave 123123123331`") + .Description("Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331`") .Parameter("arg", ParameterType.Required) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs b/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs index cfa898c5..54db0305 100644 --- a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs @@ -245,7 +245,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "greetmsg") - .Description("Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message.\n**Usage**: .greetmsg Welcome to the server, %user%.") + .Description("Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. | .greetmsg Welcome to the server, %user%.") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { @@ -278,7 +278,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "byemsg") - .Description("Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message.\n**Usage**: .byemsg %user% has left the server.") + .Description("Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. | .byemsg %user% has left the server.") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs b/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs index b17fd1a9..ae63a7db 100644 --- a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs @@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "voicenotif") - .Description("Enables notifications on who joined/left the voice channel.\n**Usage**:.voicenotif Karaoke club") + .Description("Enables notifications on who joined/left the voice channel. |.voicenotif Karaoke club") .Parameter("voice_name", ParameterType.Unparsed) .Do(DoFunc()); } diff --git a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs b/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs index e307c796..be6bae12 100644 --- a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs @@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Administration.Commands sendMessages: PermValue.Deny)).ConfigureAwait(false); } var afterVch = e.After.VoiceChannel; - if (afterVch != null) + if (afterVch != null && e.Server.AFKChannel != afterVch) { var textChannel = e.Server.FindChannels( GetChannelName(afterVch.Name), diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs index 85168971..668afd73 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -7,6 +7,10 @@ using System.Threading.Tasks; namespace NadekoBot.Classes.ClashOfClans { + public enum DestroyStars + { + One, Two, Three + } internal class Caller { public string CallUser { get; } @@ -15,6 +19,9 @@ namespace NadekoBot.Classes.ClashOfClans public bool BaseDestroyed { get; internal set; } + public int Stars { get; set; } = 3; + + public Caller(string callUser, DateTime timeAdded, bool baseDestroyed) { CallUser = callUser; @@ -148,7 +155,7 @@ namespace NadekoBot.Classes.ClashOfClans { if (bases[i].BaseDestroyed) { - sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` ⭐ ⭐ ⭐"); + sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` {new string('⭐', bases[i].Stars)}"); } else { @@ -161,13 +168,14 @@ namespace NadekoBot.Classes.ClashOfClans return sb.ToString(); } - internal int FinishClaim(string user) + internal int FinishClaim(string user, int stars = 3) { user = user.Trim(); 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; 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 a96a7347..9c383509 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; +using NadekoBot.Modules.Permissions.Classes; namespace NadekoBot.Modules.ClashOfClans { @@ -21,10 +23,11 @@ namespace NadekoBot.Modules.ClashOfClans manager.CreateCommands("", cgb => { - cgb.CreateCommand(Prefix + "createwar") + 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.\n**Usage**:{Prefix}cw 15 The Enemy Clan") + .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 => @@ -102,7 +105,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "listwar") .Alias(Prefix + "lw") - .Description($"Shows the active war claims by a number. Shows all wars in a short way if no number is specified.\n**Usage**: {Prefix}lw [war_number] or {Prefix}lw") + .Description($"Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | {Prefix}lw [war_number] or {Prefix}lw") .Parameter("number", ParameterType.Optional) .Do(async e => { @@ -143,7 +146,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "claim") .Alias(Prefix + "call") .Alias(Prefix + "c") - .Description($"Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. \n**Usage**: {Prefix}call [war_number] [base_number] [optional_other_name]") + .Description($"Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | {Prefix}call [war_number] [base_number] [optional_other_name]") .Parameter("number") .Parameter("baseNumber") .Parameter("other_name", ParameterType.Unparsed) @@ -179,38 +182,31 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "claimfinish") .Alias(Prefix + "cf") - .Description($"Finish your claim if you destroyed a base. Optional second argument finishes for someone else.\n**Usage**: {Prefix}cf [war_number] [optional_other_name]") + .Alias(Prefix + "cf3") + .Alias(Prefix + "claimfinish3") + .Description($"Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | {Prefix}cf [war_number] [optional_other_name]") .Parameter("number", ParameterType.Required) .Parameter("other_name", ParameterType.Unparsed) - .Do(async e => - { - var warInfo = GetInfo(e); - if (warInfo == null || warInfo.Item1.Count == 0) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(e.GetArg("other_name")) ? - e.User.Name : - e.GetArg("other_name"); + .Do(e => FinishClaim(e)); - var war = warInfo.Item1[warInfo.Item2]; - try - { - var baseNum = war.FinishClaim(usr); - await e.Channel.SendMessage($"❗🔰{e.User.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢🔰 {ex.Message}").ConfigureAwait(false); - } - }); + cgb.CreateCommand(Prefix + "claimfinish2") + .Alias(Prefix + "cf2") + .Description($"Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | {Prefix}cf [war_number] [optional_other_name]") + .Parameter("number", ParameterType.Required) + .Parameter("other_name", ParameterType.Unparsed) + .Do(e => FinishClaim(e, 2)); + + cgb.CreateCommand(Prefix + "claimfinish1") + .Alias(Prefix + "cf1") + .Description($"Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | {Prefix}cf [war_number] [optional_other_name]") + .Parameter("number", ParameterType.Required) + .Parameter("other_name", ParameterType.Unparsed) + .Do(e => FinishClaim(e, 1)); 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\n**Usage**: {Prefix}uc [war_number] [optional_other_name]") + .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]") .Parameter("number", ParameterType.Required) .Parameter("other_name", ParameterType.Unparsed) .Do(async e => @@ -239,7 +235,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "endwar") .Alias(Prefix + "ew") - .Description($"Ends the war with a given index.\n**Usage**:{Prefix}ew [war_number]") + .Description($"Ends the war with a given index. |{Prefix}ew [war_number]") .Parameter("number") .Do(async e => { @@ -257,6 +253,31 @@ namespace NadekoBot.Modules.ClashOfClans }); } + private async Task FinishClaim(CommandEventArgs e, int stars = 3) + { + var warInfo = GetInfo(e); + if (warInfo == null || warInfo.Item1.Count == 0) + { + await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + var usr = + string.IsNullOrWhiteSpace(e.GetArg("other_name")) ? + e.User.Name : + e.GetArg("other_name"); + + var war = warInfo.Item1[warInfo.Item2]; + try + { + 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); + } + catch (Exception ex) + { + await e.Channel.SendMessage($"💢🔰 {ex.Message}").ConfigureAwait(false); + } + } + private static Tuple, int> GetInfo(CommandEventArgs e) { //check if there are any wars diff --git a/NadekoBot/Modules/Conversations/Commands/RipCommand.cs b/NadekoBot/Modules/Conversations/Commands/RipCommand.cs index 5557ade8..f6960e5f 100644 --- a/NadekoBot/Modules/Conversations/Commands/RipCommand.cs +++ b/NadekoBot/Modules/Conversations/Commands/RipCommand.cs @@ -23,7 +23,7 @@ namespace NadekoBot.Modules.Conversations.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand("rip") - .Description("Shows a grave image of someone with a start year\n**Usage**: @NadekoBot rip @Someone 2000") + .Description("Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000") .Parameter("user", ParameterType.Required) .Parameter("year", ParameterType.Optional) .Do(async e => diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs index 606084b1..328be562 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/NadekoBot/Modules/Conversations/Conversations.cs @@ -9,13 +9,14 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; namespace NadekoBot.Modules.Conversations { internal class Conversations : DiscordModule { - private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; + private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; public Conversations() { commands.Add(new RipCommand(this)); @@ -32,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).\n**Usage**: .. 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 => @@ -53,7 +54,7 @@ namespace NadekoBot.Modules.Conversations }); cgb.CreateCommand("...") - .Description("Shows a random quote with a specified name.\n**Usage**: .. abc") + .Description("Shows a random quote with a specified name. | .. abc") .Parameter("keyword", ParameterType.Required) .Do(async e => { @@ -73,7 +74,7 @@ namespace NadekoBot.Modules.Conversations cgb.CreateCommand("..qdel") .Alias("..quotedelete") - .Description("Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it.\n**Usage**: `..qdel abc`") + .Description("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`") .Parameter("quote", ParameterType.Required) .Do(async e => { @@ -100,15 +101,6 @@ namespace NadekoBot.Modules.Conversations commands.ForEach(cmd => cmd.Init(cgb)); - cgb.CreateCommand("uptime") - .Description("Shows how long Nadeko has been running for.") - .Do(async e => - { - var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); - var str = string.Format("I have been running for {0} days, {1} hours, and {2} minutes.", time.Days, time.Hours, time.Minutes); - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); - cgb.CreateCommand("die") .Description("Works only for the owner. Shuts the bot down.") .Do(async e => @@ -158,58 +150,27 @@ namespace NadekoBot.Modules.Conversations }); cgb.CreateCommand("fire") - .Description("Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire.\n**Usage**: @NadekoBot fire [x]") + .Description("Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | @NadekoBot fire [x]") .Parameter("times", ParameterType.Optional) .Do(async e => { - var count = 1; - int.TryParse(e.Args[0], out count); - if (count == 0) + int count; + if (string.IsNullOrWhiteSpace(e.Args[0])) count = 1; + else + int.TryParse(e.Args[0], out count); if (count < 1 || count > 12) { - await e.Channel.SendMessage("Number must be between 0 and 12").ConfigureAwait(false); + await e.Channel.SendMessage("Number must be between 1 and 12").ConfigureAwait(false); return; } - var str = ""; + var str = new StringBuilder(); for (var i = 0; i < count; i++) { - str += firestr; + str.Append(firestr); } - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); - - cgb.CreateCommand("slm") - .Description("Shows the message where you were last mentioned in this channel (checks last 10k messages)") - .Do(async e => - { - - Message msg = null; - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)) - .Where(m => m.MentionedUsers.Contains(e.User)) - .OrderByDescending(m => m.Timestamp); - if (msgs.Any()) - msg = msgs.First(); - else - { - var attempt = 0; - Message lastMessage = null; - while (msg == null && attempt++ < 5) - { - var msgsarr = await e.Channel.DownloadMessages(100, lastMessage?.Id).ConfigureAwait(false); - msg = msgsarr - .Where(m => m.MentionedUsers.Contains(e.User)) - .OrderByDescending(m => m.Timestamp) - .FirstOrDefault(); - lastMessage = msgsarr.OrderBy(m => m.Timestamp).First(); - } - } - if (msg != null) - await e.Channel.SendMessage($"Last message mentioning you was at {msg.Timestamp}\n**Message from {msg.User.Name}:** {msg.RawText}") - .ConfigureAwait(false); - else - await e.Channel.SendMessage("I can't find a message mentioning you.").ConfigureAwait(false); + await e.Channel.SendMessage(str.ToString()).ConfigureAwait(false); }); cgb.CreateCommand("dump") diff --git a/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/NadekoBot/Modules/CustomReactions/CustomReactions.cs index a2e3973c..a613b6c5 100644 --- a/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.CustomReactions var c = cgb.CreateCommand(commandName); if (commandName.Contains(NadekoBot.BotMention)) c.Alias(commandName.Replace("<@", "<@!")); - c.Description($"Custom reaction.\n**Usage**:{command.Key}") + c.Description($"Custom reaction. |{command.Key}") .Parameter("args", ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/DiscordCommand.cs b/NadekoBot/Modules/DiscordCommand.cs index 726c813b..05f9a42a 100644 --- a/NadekoBot/Modules/DiscordCommand.cs +++ b/NadekoBot/Modules/DiscordCommand.cs @@ -7,7 +7,7 @@ namespace NadekoBot.Classes /// Base DiscordCommand Class. /// Inherit this class to create your own command. /// - internal abstract class DiscordCommand + public abstract class DiscordCommand { /// diff --git a/NadekoBot/Modules/DiscordModule.cs b/NadekoBot/Modules/DiscordModule.cs index 48427732..5e131233 100644 --- a/NadekoBot/Modules/DiscordModule.cs +++ b/NadekoBot/Modules/DiscordModule.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using NadekoBot.Classes; namespace NadekoBot.Modules { - internal abstract class DiscordModule : IModule { + public abstract class DiscordModule : IModule { protected readonly HashSet commands = new HashSet(); public abstract string Prefix { get; } diff --git a/NadekoBot/Modules/Gambling/DiceRollCommand.cs b/NadekoBot/Modules/Gambling/DiceRollCommand.cs index 2724d3ab..68a665fe 100644 --- a/NadekoBot/Modules/Gambling/DiceRollCommand.cs +++ b/NadekoBot/Modules/Gambling/DiceRollCommand.cs @@ -21,11 +21,18 @@ namespace NadekoBot.Modules.Gambling { cgb.CreateCommand(Module.Prefix + "roll") .Description("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.\n**Usage**: $roll or $roll 7 or $roll 3d5") + " 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") .Parameter("num", ParameterType.Optional) .Do(RollFunc()); + + cgb.CreateCommand(Module.Prefix + "rolluo") + .Description("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") + .Parameter("num", ParameterType.Optional) + .Do(RollFunc(false)); + cgb.CreateCommand(Module.Prefix + "nroll") - .Description("Rolls in a given range.\n**Usage**: `$nroll 5` (rolls 0-5) or `$nroll 5-15`") + .Description("Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`") .Parameter("range", ParameterType.Required) .Do(NRollFunc()); } @@ -40,7 +47,7 @@ namespace NadekoBot.Modules.Gambling Regex dndRegex = new Regex(@"(?\d+)d(?\d+)", RegexOptions.Compiled); - private Func RollFunc() + private Func RollFunc(bool ordered = true) { var r = new Random(); return async e => @@ -73,7 +80,7 @@ namespace NadekoBot.Modules.Gambling arr[i] = r.Next(1, n2 + 1); } var elemCnt = 0; - await e.Channel.SendMessage($"`Rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.`\n`Result:` " + string.Join(", ", arr.OrderBy(x => x).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false); + await e.Channel.SendMessage($"`Rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.`\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false); } return; } @@ -92,17 +99,23 @@ namespace NadekoBot.Modules.Gambling { var randomNumber = r.Next(1, 7); var toInsert = dices.Count; - if (randomNumber == 6 || dices.Count == 0) - toInsert = 0; - else if (randomNumber != 1) - for (var j = 0; j < dices.Count; j++) - { - if (values[j] < randomNumber) + if (ordered) + { + if (randomNumber == 6 || dices.Count == 0) + toInsert = 0; + else if (randomNumber != 1) + for (var j = 0; j < dices.Count; j++) { - toInsert = j; - break; + if (values[j] < randomNumber) + { + toInsert = j; + break; + } } - } + } + else { + toInsert = dices.Count; + } dices.Insert(toInsert, GetDice(randomNumber)); values.Insert(toInsert, randomNumber); } diff --git a/NadekoBot/Modules/Gambling/DrawCommand.cs b/NadekoBot/Modules/Gambling/DrawCommand.cs index 0f7d92e9..35d789bf 100644 --- a/NadekoBot/Modules/Gambling/DrawCommand.cs +++ b/NadekoBot/Modules/Gambling/DrawCommand.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Gambling internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "draw") - .Description("Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck.\n**Usage**: $draw [x]") + .Description("Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | $draw [x]") .Parameter("count", ParameterType.Optional) .Do(DrawCardFunc()); diff --git a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs b/NadekoBot/Modules/Gambling/FlipCoinCommand.cs index 5c23cd5b..6d8edf3c 100644 --- a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs +++ b/NadekoBot/Modules/Gambling/FlipCoinCommand.cs @@ -15,14 +15,68 @@ namespace NadekoBot.Modules.Gambling internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "flip") - .Description("Flips coin(s) - heads or tails, and shows an image.\n**Usage**: `$flip` or `$flip 3`") + .Description("Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3`") .Parameter("count", ParameterType.Optional) .Do(FlipCoinFunc()); + + cgb.CreateCommand(Module.Prefix + "betflip") + .Alias(Prefix+"bf") + .Description($"Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `{Prefix}bf 5 heads` or `{Prefix}bf 3 t`") + .Parameter("amount", ParameterType.Required) + .Parameter("guess", ParameterType.Required) + .Do(BetFlipCoinFunc()); } private readonly Random rng = new Random(); + public Func BetFlipCoinFunc() => async e => + { + + var amountstr = e.GetArg("amount").Trim(); + + var guessStr = e.GetArg("guess").Trim().ToUpperInvariant(); + if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS") + return; + + int amount; + if (!int.TryParse(amountstr, out amount) || amount < 1) + return; + + 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; + } + + await FlowersHandler.RemoveFlowers(e.User, "Betflip Gamble", (int)amount, true).ConfigureAwait(false); + //heads = true + //tails = false + + var guess = guessStr == "HEADS" || guessStr == "H"; + bool result = false; + if (rng.Next(0, 2) == 1) { + await e.Channel.SendFile("heads.png", Properties.Resources.heads.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); + result = true; + } + else { + await e.Channel.SendFile("tails.png", Properties.Resources.tails.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); + } + + string str; + if (guess == result) + { + str = $"{e.User.Mention}`You guessed it!` You won {amount * 2}{NadekoBot.Config.CurrencySign}"; + await FlowersHandler.AddFlowersAsync(e.User, "Betflip Gamble", amount * 2, true).ConfigureAwait(false); + + } + else + str = $"{e.User.Mention}`More luck next time.`"; + + await e.Channel.SendMessage(str).ConfigureAwait(false); + }; public Func FlipCoinFunc() => async e => { diff --git a/NadekoBot/Modules/Gambling/GamblingModule.cs b/NadekoBot/Modules/Gambling/GamblingModule.cs index 668c1383..579e1ed6 100644 --- a/NadekoBot/Modules/Gambling/GamblingModule.cs +++ b/NadekoBot/Modules/Gambling/GamblingModule.cs @@ -13,7 +13,6 @@ namespace NadekoBot.Modules.Gambling { internal class GamblingModule : DiscordModule { - public GamblingModule() { commands.Add(new DrawCommand(this)); @@ -50,7 +49,7 @@ namespace NadekoBot.Modules.Gambling }); cgb.CreateCommand(Prefix + "$$") - .Description(string.Format("Check how much {0}s a person has. (Defaults to yourself)\n**Usage**:`{1}$$` or `{1}$$ @Someone`", + .Description(string.Format("Check how much {0}s a person has. (Defaults to yourself) |`{1}$$` or `{1}$$ @Someone`", NadekoBot.Config.CurrencyName, Prefix)) .Parameter("all", ParameterType.Unparsed) .Do(async e => @@ -69,7 +68,7 @@ namespace NadekoBot.Modules.Gambling { var amountStr = e.GetArg("amount")?.Trim(); long amount; - if (!long.TryParse(amountStr, out amount) || amount < 0) + if (!long.TryParse(amountStr, out amount) || amount <= 0) return; var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => @@ -82,11 +81,11 @@ namespace NadekoBot.Modules.Gambling if (userFlowers < amount) { - await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {NadekoBot.Config.CurrencyName}s. You have only {userFlowers}{NadekoBot.Config.CurrencySign}.").ConfigureAwait(false); + 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; } - FlowersHandler.RemoveFlowers(e.User, "Gift", (int)amount); + await FlowersHandler.RemoveFlowers(e.User, "Gift", (int)amount, true).ConfigureAwait(false); await FlowersHandler.AddFlowersAsync(mentionedUser, "Gift", (int)amount).ConfigureAwait(false); await e.Channel.SendMessage($"{e.User.Mention} successfully sent {amount} {NadekoBot.Config.CurrencyName}s to {mentionedUser.Mention}!").ConfigureAwait(false); @@ -94,7 +93,7 @@ namespace NadekoBot.Modules.Gambling }); cgb.CreateCommand(Prefix + "award") - .Description("Gives someone a certain amount of flowers. **Bot Owner Only!**\n**Usage**: `$award 100 @person`") + .Description("Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person`") .AddCheck(SimpleCheckers.OwnerOnly()) .Parameter("amount", ParameterType.Required) .Parameter("receiver", ParameterType.Unparsed) @@ -132,11 +131,58 @@ namespace NadekoBot.Modules.Gambling if (mentionedUser == null) return; - FlowersHandler.RemoveFlowers(mentionedUser, $"Taken by bot owner.({e.User.Name}/{e.User.Id})", (int)amount); + await FlowersHandler.RemoveFlowers(mentionedUser, $"Taken by bot owner.({e.User.Name}/{e.User.Id})", (int)amount).ConfigureAwait(false); await e.Channel.SendMessage($"{e.User.Mention} successfully took {amount} {NadekoBot.Config.CurrencyName}s from {mentionedUser.Mention}!").ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "betroll") + .Alias(Prefix + "br") + .Description($"Bets a certain amount of {NadekoBot.Config.CurrencyName}s and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | {Prefix}br 5") + .Parameter("amount",ParameterType.Required) + .Do(async e => + { + var amountstr = e.GetArg("amount").Trim(); + int amount; + + if (!int.TryParse(amountstr, out amount) || amount < 1) + return; + + var userFlowers = 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; + } + + await FlowersHandler.RemoveFlowers(e.User, "Betroll Gamble", (int)amount, true).ConfigureAwait(false); + + var rng = new Random().Next(0, 101); + var str = $"{e.User.Mention} `You rolled {rng}.` "; + if (rng < 67) + { + str += "Better luck next time."; + } + else if (rng < 90) + { + str += $"Congratulations! You won {amount * 2}{NadekoBot.Config.CurrencySign} for rolling above 66"; + await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 2, true).ConfigureAwait(false); + } + else if (rng < 100) + { + str += $"Congratulations! You won {amount * 3}{NadekoBot.Config.CurrencySign} for rolling above 90."; + await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 3, true).ConfigureAwait(false); + } + else { + str += $"👑 Congratulations! You won {amount * 10}{NadekoBot.Config.CurrencySign} for rolling **100**. 👑"; + await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 10, true).ConfigureAwait(false); + } + + await e.Channel.SendMessage(str).ConfigureAwait(false); + + }); + cgb.CreateCommand(Prefix + "leaderboard") .Alias(Prefix + "lb") .Do(async e => @@ -147,19 +193,19 @@ namespace NadekoBot.Modules.Gambling return; await e.Channel.SendMessage( richest.Aggregate(new StringBuilder( -$@"```xl -┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ -┃ Id ┃ $$$ ┃ + $@"```xl +┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ +┃ Id ┃ $$$ ┃ "), (cur, cs) => cur.AppendLine( -$@"┣━━━━━━━━━━━━━━━━━━━╋━━━━━━━┫ -┃{cs.UserId,-18} ┃ {cs.Value,5} ┃") - ).ToString() + "┗━━━━━━━━━━━━━━━━━━━┻━━━━━━━┛```"); + $@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━┫ +┃{(e.Server.Users.Where(u => u.Id == (ulong)cs.UserId).FirstOrDefault()?.Name.TrimTo(18, true) ?? cs.UserId.ToString()),-20} ┃ {cs.Value,5} ┃") + ).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━┛```").ConfigureAwait(false); }); }); } - private static long GetUserFlowers(ulong userId) => + public static long GetUserFlowers(ulong userId) => Classes.DbHandler.Instance.GetStateByUserId((long)userId)?.Value ?? 0; } } diff --git a/NadekoBot/Modules/Gambling/Helpers/Cards.cs b/NadekoBot/Modules/Gambling/Helpers/Cards.cs index 76dc73a5..8408535e 100644 --- a/NadekoBot/Modules/Gambling/Helpers/Cards.cs +++ b/NadekoBot/Modules/Gambling/Helpers/Cards.cs @@ -146,7 +146,7 @@ namespace NadekoBot.Modules.Gambling.Helpers var orderedPool = cardPool.OrderBy(x => r.Next()); cardPool = cardPool as List ?? orderedPool.ToList(); } - public override string ToString() => string.Join("", cardPool.Select(c => c.ToString())) + Environment.NewLine; + public override string ToString() => string.Concat(cardPool.Select(c => c.ToString())) + Environment.NewLine; private static void InitHandValues() { @@ -226,7 +226,7 @@ namespace NadekoBot.Modules.Gambling.Helpers { return kvp.Key; } - return "High card " + cards.Max().GetName(); + return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetName() ?? cards.Max().GetName()); } } } diff --git a/NadekoBot/Modules/Games/Commands/Leet.cs b/NadekoBot/Modules/Games/Commands/Leet.cs index 33845299..22cdedbf 100644 --- a/NadekoBot/Modules/Games/Commands/Leet.cs +++ b/NadekoBot/Modules/Games/Commands/Leet.cs @@ -297,7 +297,7 @@ namespace NadekoBot.Modules.Games.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "leet") - .Description($"Converts a text to leetspeak with 6 (1-6) severity levels\n**Usage**: {Module.Prefix}leet 3 Hello") + .Description($"Converts a text to leetspeak with 6 (1-6) severity levels | {Module.Prefix}leet 3 Hello") .Parameter("level", ParameterType.Required) .Parameter("text", ParameterType.Unparsed) .Do(async e => diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs index cbc4b01a..6af69857 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/NadekoBot/Modules/Games/Commands/PlantPick.cs @@ -1,9 +1,11 @@ using Discord; using Discord.Commands; using NadekoBot.Classes; +using NadekoBot.Extensions; using NadekoBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -28,25 +30,34 @@ namespace NadekoBot.Modules.Games.Commands rng = new Random(); } + private static readonly ConcurrentDictionary plantpickCooldowns = new ConcurrentDictionary(); private async void PotentialFlowerGeneration(object sender, Discord.MessageEventArgs e) { - if (e.Server == null || e.Channel.IsPrivate) - return; - var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id); - if (config.GenerateCurrencyChannels.Contains(e.Channel.Id)) + try { - var rnd = Math.Abs(GetRandomNumber()); - if ((rnd % 50) == 0) - { - var msg = await e.Channel.SendFile(GetRandomCurrencyImagePath()); - await e.Channel.SendMessage($"❗ A random {NadekoBot.Config.CurrencyName} appeared! Pick it up by typing `>pick`"); - plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msg, (u, m) => { m.Delete().GetAwaiter().GetResult(); return msg; }); - } + if (e.Server == null || e.Channel.IsPrivate || e.Message.IsAuthor) + return; + var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id); + var now = DateTime.Now; + int cd; + DateTime lastSpawned; + if (config.GenerateCurrencyChannels.TryGetValue(e.Channel.Id, out cd)) + if (!plantpickCooldowns.TryGetValue(e.Channel.Id, out lastSpawned) || (lastSpawned + new TimeSpan(0, cd, 0)) < now) + { + var rnd = Math.Abs(rng.Next(0,101)); + if (rnd == 0) + { + var msgs = new[] { await e.Channel.SendFile(GetRandomCurrencyImagePath()), await e.Channel.SendMessage($"❗ A random {NadekoBot.Config.CurrencyName} appeared! Pick it up by typing `>pick`") }; + plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msgs, (u, m) => { m.ForEach(async msgToDelete => { try { await msgToDelete.Delete(); } catch { } }); return msgs; }); + plantpickCooldowns.AddOrUpdate(e.Channel.Id, now, (i, d) => now); + } + } } + catch { } } //channelid/messageid pair - ConcurrentDictionary plantedFlowerChannels = new ConcurrentDictionary(); + ConcurrentDictionary> plantedFlowerChannels = new ConcurrentDictionary>(); private object locker = new object(); @@ -56,22 +67,24 @@ namespace NadekoBot.Modules.Games.Commands .Description("Picks a flower planted in this channel.") .Do(async e => { - Message msg; + IEnumerable msgs; await e.Message.Delete().ConfigureAwait(false); - if (!plantedFlowerChannels.TryRemove(e.Channel.Id, out msg)) + if (!plantedFlowerChannels.TryRemove(e.Channel.Id, out msgs)) return; - await msg.Delete().ConfigureAwait(false); + foreach(var msgToDelete in msgs) + await msgToDelete.Delete().ConfigureAwait(false); + await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true).ConfigureAwait(false); - msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!").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); }); 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(async e => + .Do(e => { lock (locker) { @@ -80,7 +93,7 @@ namespace NadekoBot.Modules.Games.Commands e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel."); return; } - var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1); + var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).GetAwaiter().GetResult(); if (!removed) { e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").Wait(); @@ -89,34 +102,39 @@ namespace NadekoBot.Modules.Games.Commands var file = GetRandomCurrencyImagePath(); Message msg; - //todo send message after, not in lock if (file == null) msg = e.Channel.SendMessage(NadekoBot.Config.CurrencySign).GetAwaiter().GetResult(); else msg = e.Channel.SendFile(file).GetAwaiter().GetResult(); - plantedFlowerChannels.TryAdd(e.Channel.Id, msg); + 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(); + plantedFlowerChannels.TryAdd(e.Channel.Id, new[] { msg, msg2 }); } - var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]); - 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"); - await Task.Delay(20000).ConfigureAwait(false); - await msg2.Delete().ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "gencurrency") .Alias(Prefix + "gc") - .Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {NadekoBot.Config.CurrencyName}. Requires Manage Messages permission. | `>gc`") + .Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {NadekoBot.Config.CurrencyName}. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `>gc` or `>gc 60`") .AddCheck(SimpleCheckers.ManageMessages()) + .Parameter("cd", ParameterType.Unparsed) .Do(async e => { + var cdStr = e.GetArg("cd"); + int cd = 2; + if (!int.TryParse(cdStr, out cd) || cd < 0) + { + cd = 2; + } var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (config.GenerateCurrencyChannels.Remove(e.Channel.Id)) + int throwaway; + if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway)) { await e.Channel.SendMessage("`Currency generation disabled on this channel.`"); } else { - config.GenerateCurrencyChannels.Add(e.Channel.Id); - await e.Channel.SendMessage("`Currency generation enabled on this channel.`"); + if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd)) + await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`"); } }); } diff --git a/NadekoBot/Modules/Games/Commands/PollCommand.cs b/NadekoBot/Modules/Games/Commands/PollCommand.cs index 1e1c7b03..e1cd75e4 100644 --- a/NadekoBot/Modules/Games/Commands/PollCommand.cs +++ b/NadekoBot/Modules/Games/Commands/PollCommand.cs @@ -23,7 +23,7 @@ namespace NadekoBot.Modules.Games.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "poll") - .Description("Creates a poll, only person who has manage server permission can do it.\n**Usage**: >poll Question?;Answer1;Answ 2;A_3") + .Description("Creates a poll, only person who has manage server permission can do it. | >poll Question?;Answer1;Answ 2;A_3") .Parameter("allargs", ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs b/NadekoBot/Modules/Games/Commands/SpeedTyping.cs index 8a807208..c224de1d 100644 --- a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs +++ b/NadekoBot/Modules/Games/Commands/SpeedTyping.cs @@ -190,8 +190,6 @@ namespace NadekoBot.Modules.Games.Commands await e.Channel.SendMessage("Added new article for typing game.").ConfigureAwait(false); }); - - //todo add user submissions } } } diff --git a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs b/NadekoBot/Modules/Games/Commands/TriviaCommand.cs index 57cc4a4a..1873c254 100644 --- a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs +++ b/NadekoBot/Modules/Games/Commands/TriviaCommand.cs @@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Commands cgb.CreateCommand(Module.Prefix + "t") .Description($"Starts a game of trivia. You can add nohint to prevent hints." + "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + - $"\n**Usage**:`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") + $" |`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") .Parameter("args", ParameterType.Multiple) .Do(async e => { diff --git a/NadekoBot/Modules/Games/GamesModule.cs b/NadekoBot/Modules/Games/GamesModule.cs index 22280e00..29fea7f8 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\n**Usage**: >choose Get up;Sleep;Sleep more") + .Description("Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more") .Parameter("list", ParameterType.Unparsed) .Do(async e => { @@ -67,7 +67,7 @@ namespace NadekoBot.Modules.Games }); cgb.CreateCommand(Prefix + "rps") - .Description("Play a game of rocket paperclip scissors with Nadeko.\n**Usage**: >rps scissors") + .Description("Play a game of rocket paperclip scissors with Nadeko. | >rps scissors") .Parameter("input", ParameterType.Required) .Do(async e => { @@ -107,7 +107,7 @@ namespace NadekoBot.Modules.Games }); cgb.CreateCommand(Prefix + "linux") - .Description($"Prints a customizable Linux interjection\n**Usage**: `{Prefix}linux Spyware Windows`") + .Description($"Prints a customizable Linux interjection | `{Prefix}linux Spyware Windows`") .Parameter("gnu", ParameterType.Required) .Parameter("linux", ParameterType.Required) .Do(async e => diff --git a/NadekoBot/Modules/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs index c1157574..4d3fa441 100644 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ b/NadekoBot/Modules/Help/Commands/HelpCommand.cs @@ -5,6 +5,7 @@ using NadekoBot.Modules.Permissions.Classes; using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace NadekoBot.Classes.Help.Commands @@ -24,8 +25,13 @@ namespace NadekoBot.Classes.Help.Commands var com = NadekoBot.Client.GetService().AllCommands .FirstOrDefault(c => c.Text.ToLowerInvariant().Equals(comToFind) || c.Aliases.Select(a => a.ToLowerInvariant()).Contains(comToFind)); + + var str = ""; + var alias = com.Aliases.FirstOrDefault(); + if (alias != null) + str = $" / `{ com.Aliases.FirstOrDefault()}`"; if (com != null) - await e.Channel.SendMessage($"`Help for '{com.Text}':` {com.Description}").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 { @@ -43,7 +49,7 @@ 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` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` +######You can donate on paypal: `nadekodiscordbot@gmail.com` #NadekoBot List Of Commands Version: `{NadekoStats.Instance.BotVersion}`"; @@ -62,7 +68,7 @@ Version: `{NadekoStats.Instance.BotVersion}`"; helpstr += PrintCommandHelp(com); } helpstr = helpstr.Replace(NadekoBot.BotMention, "@BotName"); - helpstr = helpstr.Replace("\n**Usage**:", " | ").Replace("**Usage**:", " | ").Replace("**Description:**", " | ").Replace("\n|", " | \n"); + helpstr = helpstr.Replace(" |", " | ").Replace("**Usage**:", " | ").Replace("**Description:**", " | ").Replace("\n|", " | \n"); #if DEBUG File.WriteAllText("../../../commandlist.md", helpstr); #else @@ -74,7 +80,7 @@ Version: `{NadekoStats.Instance.BotVersion}`"; { cgb.CreateCommand(Module.Prefix + "h") .Alias(Module.Prefix + "help", NadekoBot.BotMention + " help", NadekoBot.BotMention + " h", "~h") - .Description("Either shows a help for a single command, or PMs you help link if no arguments are specified.\n**Usage**: '-h !m q' or just '-h' ") + .Description("Either shows a help for a single command, or PMs you help link if no arguments are specified. | '-h !m q' or just '-h' ") .Parameter("command", ParameterType.Unparsed) .Do(HelpFunc()); cgb.CreateCommand(Module.Prefix + "hgit") diff --git a/NadekoBot/Modules/Help/HelpModule.cs b/NadekoBot/Modules/Help/HelpModule.cs index 0b17e336..20e8279c 100644 --- a/NadekoBot/Modules/Help/HelpModule.cs +++ b/NadekoBot/Modules/Help/HelpModule.cs @@ -46,20 +46,25 @@ namespace NadekoBot.Modules.Help if (string.IsNullOrWhiteSpace(module)) return; var cmds = NadekoBot.Client.GetService().AllCommands - .Where(c => c.Category.ToLower() == module); + .Where(c => c.Category.ToLower() == module) + .OrderBy(c=>c.Text) + .AsEnumerable(); var cmdsArray = cmds as Command[] ?? cmds.ToArray(); if (!cmdsArray.Any()) { await e.Channel.SendMessage("That module does not exist.").ConfigureAwait(false); return; } - var i = 0; if (module != "customreactions" && module != "conversations") + { await e.Channel.SendMessage("`List Of Commands:`\n" + SearchHelper.ShowInPrettyCode(cmdsArray, el => $"{el.Text,-15}{"[" + el.Aliases.FirstOrDefault() + "]",-8}")) .ConfigureAwait(false); + } else + { await e.Channel.SendMessage("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}"))); + } await e.Channel.SendMessage($"`You can type \"{Prefix}h command_name\" to see the help about that specific command.`").ConfigureAwait(false); }); }); diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index e1cd32bb..bcd661f8 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -2,7 +2,9 @@ using Discord.Audio; using NadekoBot.Extensions; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; namespace NadekoBot.Modules.Music.Classes @@ -12,29 +14,26 @@ namespace NadekoBot.Modules.Music.Classes { Radio, Normal, - Local + Local, + Soundcloud } public enum StreamState { Resolving, Queued, - Buffering, //not using it atm Playing, Completed } public class MusicPlayer { - public static int MaximumPlaylistSize => 50; - private IAudioClient audioClient { get; set; } private readonly List playlist = new List(); public IReadOnlyCollection Playlist => playlist; - private readonly object playlistLock = new object(); - public Song CurrentSong { get; set; } = default(Song); + public Song CurrentSong { get; private set; } private CancellationTokenSource SongCancelSource { get; set; } private CancellationToken cancelToken { get; set; } @@ -51,6 +50,9 @@ namespace NadekoBot.Modules.Music.Classes public bool RepeatSong { get; private set; } = false; public bool RepeatPlaylist { get; private set; } = false; public bool Autoplay { get; set; } = false; + public uint MaxQueueSize { get; set; } = 0; + + private ConcurrentQueue actionQueue { get; set; } = new ConcurrentQueue(); public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) { @@ -66,85 +68,110 @@ namespace NadekoBot.Modules.Music.Classes Task.Run(async () => { - while (!Destroyed) + try { - try - { - if (audioClient?.State != ConnectionState.Connected) - audioClient = await PlaybackVoiceChannel.JoinAudio().ConfigureAwait(false); - } - catch - { - await Task.Delay(1000).ConfigureAwait(false); - continue; - } - CurrentSong = GetNextSong(); - var curSong = CurrentSong; - if (curSong != null) + while (!Destroyed) { try { - OnStarted(this, curSong); - await curSong.Play(audioClient, cancelToken).ConfigureAwait(false); + Action action; + if (actionQueue.TryDequeue(out action)) + { + action(); + } } - catch (OperationCanceledException) + finally { - Console.WriteLine("Song canceled"); + await Task.Delay(100).ConfigureAwait(false); } - catch (Exception ex) - { - Console.WriteLine($"Exception in PlaySong: {ex}"); - } - OnCompleted(this, curSong); - curSong = CurrentSong; //to check if its null now - if (curSong != null) - if (RepeatSong) - playlist.Insert(0, curSong); - else if (RepeatPlaylist) - playlist.Insert(playlist.Count, curSong); - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; } - await Task.Delay(1000).ConfigureAwait(false); } - }); + catch (Exception ex) + { + Console.WriteLine("Action queue crashed"); + Console.WriteLine(ex); + } + }).ConfigureAwait(false); + + var t = new Thread(new ThreadStart(async () => + { + try + { + while (!Destroyed) + { + try + { + if (audioClient?.State != ConnectionState.Connected) + { + audioClient = await PlaybackVoiceChannel.JoinAudio(); + continue; + } + + CurrentSong = GetNextSong(); + RemoveSongAt(0); + + 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; + } + OnCompleted(this, CurrentSong); + + if (RepeatPlaylist) + AddSong(CurrentSong, CurrentSong.QueuerName); + + if (RepeatSong) + AddSong(CurrentSong, 0); + + } + finally + { + await Task.Delay(300).ConfigureAwait(false); + CurrentSong = null; + } + } + } + catch (Exception ex) { + Console.WriteLine("Music thread crashed."); + Console.WriteLine(ex); + } + })); + + t.Start(); } public void Next() { - lock (playlistLock) + actionQueue.Enqueue(() => { - if (!SongCancelSource.IsCancellationRequested) - { - Paused = false; - SongCancelSource.Cancel(); - } - } + Paused = false; + SongCancelSource.Cancel(); + }); } public void Stop() { - lock (playlistLock) + actionQueue.Enqueue(() => { - playlist.Clear(); - CurrentSong = null; RepeatPlaylist = false; RepeatSong = false; + playlist.Clear(); if (!SongCancelSource.IsCancellationRequested) SongCancelSource.Cancel(); - } + }); } public void TogglePause() => Paused = !Paused; - public void Shuffle() - { - lock (playlistLock) - { - playlist.Shuffle(); - } - } - public int SetVolume(int volume) { if (volume < 0) @@ -156,57 +183,80 @@ namespace NadekoBot.Modules.Music.Classes return volume; } - private Song GetNextSong() + private Song GetNextSong() => + playlist.FirstOrDefault(); + + public void Shuffle() { - lock (playlistLock) + actionQueue.Enqueue(() => { - if (playlist.Count == 0) - return null; - var toReturn = playlist[0]; - playlist.RemoveAt(0); - return toReturn; - } + playlist.Shuffle(); + }); } - public void AddSong(Song s) + public void AddSong(Song s, string username) { if (s == null) throw new ArgumentNullException(nameof(s)); - lock (playlistLock) + ThrowIfQueueFull(); + actionQueue.Enqueue(() => { s.MusicPlayer = this; + s.QueuerName = username.TrimTo(10); playlist.Add(s); - } + }); } public void AddSong(Song s, int index) { if (s == null) throw new ArgumentNullException(nameof(s)); - lock (playlistLock) + actionQueue.Enqueue(() => { playlist.Insert(index, s); - } + }); } public void RemoveSong(Song s) { if (s == null) throw new ArgumentNullException(nameof(s)); - lock (playlistLock) + actionQueue.Enqueue(() => { playlist.Remove(s); - } + }); } public void RemoveSongAt(int index) { - lock (playlistLock) + actionQueue.Enqueue(() => { if (index < 0 || index >= playlist.Count) - throw new ArgumentException("Invalid index"); + return; playlist.RemoveAt(index); - } + }); + } + + internal void ClearQueue() + { + actionQueue.Enqueue(() => + { + playlist.Clear(); + }); + } + + public void Destroy() + { + actionQueue.Enqueue(() => + { + RepeatPlaylist = false; + RepeatSong = false; + Destroyed = true; + playlist.Clear(); + if (!SongCancelSource.IsCancellationRequested) + SongCancelSource.Cancel(); + audioClient.Disconnect(); + }); } internal Task MoveToVoiceChannel(Channel voiceChannel) @@ -217,31 +267,18 @@ namespace NadekoBot.Modules.Music.Classes return PlaybackVoiceChannel.JoinAudio(); } - internal void ClearQueue() - { - lock (playlistLock) - { - playlist.Clear(); - } - } - - public void Destroy() - { - lock (playlistLock) - { - playlist.Clear(); - Destroyed = true; - CurrentSong = null; - if (!SongCancelSource.IsCancellationRequested) - SongCancelSource.Cancel(); - audioClient.Disconnect(); - } - } - internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay; + + internal void ThrowIfQueueFull() + { + if (MaxQueueSize == 0) + return; + if (playlist.Count >= MaxQueueSize) + throw new PlaylistFullException(); + } } } diff --git a/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs b/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs new file mode 100644 index 00000000..15541d42 --- /dev/null +++ b/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Modules.Music.Classes +{ + class PlaylistFullException : Exception + { + public PlaylistFullException(string message) : base(message) + { + } + public PlaylistFullException() : base("Queue is full.") { } + } +} diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/NadekoBot/Modules/Music/Classes/Song.cs index f9c7f3ae..025863e0 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/NadekoBot/Modules/Music/Classes/Song.cs @@ -27,10 +27,11 @@ namespace NadekoBot.Modules.Music.Classes { public StreamState State { get; internal set; } public string PrettyName => - $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`"; + $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}` `by {QueuerName}`"; public SongInfo SongInfo { get; } + public string QueuerName { get; set; } - private PoopyBuffer songBuffer { get; } = new PoopyBuffer(NadekoBot.Config.BufferSize); + private PoopyBuffer songBuffer { get; set; } private bool prebufferingComplete { get; set; } = false; public MusicPlayer MusicPlayer { get; set; } @@ -136,6 +137,9 @@ 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 bufferTask = BufferSong(cancelToken).ConfigureAwait(false); var bufferAttempts = 0; const int waitPerAttempt = 500; @@ -144,7 +148,6 @@ namespace NadekoBot.Modules.Music.Classes { await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false); } - cancelToken.ThrowIfCancellationRequested(); Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); const int blockSize = 3840; var attempt = 0; @@ -244,16 +247,30 @@ namespace NadekoBot.Modules.Music.Classes } if (SoundCloud.Default.IsSoundCloudLink(query)) { - var svideo = await SoundCloud.Default.GetVideoAsync(query).ConfigureAwait(false); + var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false); return new Song(new SongInfo { Title = svideo.FullName, Provider = "SoundCloud", Uri = svideo.StreamLink, ProviderType = musicType, - Query = query, + Query = svideo.TrackLink, }); } + + if (musicType == MusicType.Soundcloud) + { + var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false); + return new Song(new SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + }); + } + var link = await SearchHelper.FindYoutubeUrlByKeywords(query).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(link)) throw new OperationCanceledException("Not a valid youtube query."); diff --git a/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/NadekoBot/Modules/Music/Classes/SoundCloud.cs index 159d0d9e..e743fa5c 100644 --- a/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/NadekoBot/Modules/Music/Classes/SoundCloud.cs @@ -1,6 +1,7 @@ using NadekoBot.Classes; using Newtonsoft.Json; using System; +using System.Linq; using System.Threading.Tasks; namespace NadekoBot.Modules.Music.Classes @@ -13,7 +14,7 @@ namespace NadekoBot.Modules.Music.Classes static SoundCloud() { } public SoundCloud() { } - public async Task GetVideoAsync(string url) + public async Task ResolveVideoAsync(string url) { if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url)); @@ -31,6 +32,22 @@ namespace NadekoBot.Modules.Music.Classes public bool IsSoundCloudLink(string url) => System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); + + internal async Task GetVideoByQueryAsync(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID)) + throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID)); + + var response = await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); + + var responseObj = JsonConvert.DeserializeObject(response).Where(s => s.Streamable).FirstOrDefault(); + if (responseObj?.Kind != "track") + throw new InvalidOperationException("Query yielded no results."); + + return responseObj; + } } public class SoundCloudVideo diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index a22bc843..ba04d598 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -31,17 +31,17 @@ namespace NadekoBot.Modules.Music { var client = NadekoBot.Client; - manager.CreateCommands(Prefix, cgb => + manager.CreateCommands("", cgb => { cgb.AddCheck(PermissionChecker.Instance); commands.ForEach(cmd => cmd.Init(cgb)); - cgb.CreateCommand("next") - .Alias("n") - .Alias("skip") - .Description("Goes to the next song in the queue. You have to be in the same voice channel as the bot.\n**Usage**: `!m n`") + cgb.CreateCommand(Prefix + "next") + .Alias(Prefix + "n") + .Alias(Prefix + "skip") + .Description($"Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `{Prefix}n`") .Do(e => { MusicPlayer musicPlayer; @@ -50,9 +50,9 @@ namespace NadekoBot.Modules.Music musicPlayer.Next(); }); - cgb.CreateCommand("stop") - .Alias("s") - .Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m s`") + cgb.CreateCommand(Prefix + "stop") + .Alias(Prefix + "s") + .Description($"Stops the music and clears the playlist. Stays in the channel. | `{Prefix}s`") .Do(e => { MusicPlayer musicPlayer; @@ -64,10 +64,10 @@ namespace NadekoBot.Modules.Music } }); - cgb.CreateCommand("destroy") - .Alias("d") + cgb.CreateCommand(Prefix + "destroy") + .Alias(Prefix + "d") .Description("Completely stops the music and unbinds the bot from the channel. " + - "(may cause weird behaviour)\n**Usage**: `!m d`") + $"(may cause weird behaviour) | `{Prefix}d`") .Do(e => { MusicPlayer musicPlayer; @@ -76,9 +76,9 @@ namespace NadekoBot.Modules.Music musicPlayer.Destroy(); }); - cgb.CreateCommand("pause") - .Alias("p") - .Description("Pauses or Unpauses the song.\n**Usage**: `!m p`") + cgb.CreateCommand(Prefix + "pause") + .Alias(Prefix + "p") + .Description($"Pauses or Unpauses the song. | `{Prefix}p`") .Do(async e => { MusicPlayer musicPlayer; @@ -92,15 +92,15 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage("🎵`Music Player unpaused.`").ConfigureAwait(false); }); - cgb.CreateCommand("queue") - .Alias("q") - .Alias("yq") + cgb.CreateCommand(Prefix + "queue") + .Alias(Prefix + "q") + .Alias(Prefix + "yq") .Description("Queue a song using keywords or a link. Bot will join your voice channel." + - "**You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`") + $"**You must be in a voice channel**. | `{Prefix}q Dream Of Venice`") .Parameter("query", ParameterType.Unparsed) .Do(async e => { - await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false); + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false); if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) { await Task.Delay(10000).ConfigureAwait(false); @@ -108,24 +108,24 @@ namespace NadekoBot.Modules.Music } }); - //cgb.CreateCommand("soundcloudqueue") - // .Alias("sq") - // .Description("Queue a soundcloud song using keywords. Bot will join your voice channel." + - // "**You must be in a voice channel**.\n**Usage**: `!m sq Dream Of Venice`") - // .Parameter("query", ParameterType.Unparsed) - // .Do(async e => - // { - // await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false); - // if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) - // { - // await Task.Delay(10000).ConfigureAwait(false); - // await e.Message.Delete().ConfigureAwait(false); - // } - // }); + cgb.CreateCommand(Prefix + "soundcloudqueue") + .Alias(Prefix + "sq") + .Description("Queue a soundcloud song using keywords. Bot will join your voice channel." + + $"**You must be in a voice channel**. | `{Prefix}sq Dream Of Venice`") + .Parameter("query", ParameterType.Unparsed) + .Do(async e => + { + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("query"), musicType: MusicType.Soundcloud).ConfigureAwait(false); + if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) + { + await Task.Delay(10000).ConfigureAwait(false); + await e.Message.Delete().ConfigureAwait(false); + } + }); - cgb.CreateCommand("listqueue") - .Alias("lq") - .Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`") + cgb.CreateCommand(Prefix + "listqueue") + .Alias(Prefix + "lq") + .Description($"Lists 15 currently queued songs per page. Default page is 1. | `{Prefix}lq` or `{Prefix}lq 2`") .Parameter("page", ParameterType.Optional) .Do(async e => { @@ -151,7 +151,7 @@ namespace NadekoBot.Modules.Music else if (musicPlayer.RepeatPlaylist) toSend += "🔁"; toSend += $" **{musicPlayer.Playlist.Count}** `tracks currently queued. Showing page {page}` "; - if (musicPlayer.Playlist.Count >= MusicPlayer.MaximumPlaylistSize) + if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) toSend += "**Song queue is full!**\n"; else toSend += "\n"; @@ -161,9 +161,9 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false); }); - cgb.CreateCommand("nowplaying") - .Alias("np") - .Description("Shows the song currently playing.\n**Usage**: `!m np`") + cgb.CreateCommand(Prefix + "nowplaying") + .Alias(Prefix + "np") + .Description($"Shows the song currently playing. | `{Prefix}np`") .Do(async e => { MusicPlayer musicPlayer; @@ -176,9 +176,9 @@ namespace NadekoBot.Modules.Music $"{currentSong.PrettyCurrentTime()}").ConfigureAwait(false); }); - cgb.CreateCommand("volume") - .Alias("vol") - .Description("Sets the music volume 0-100%\n**Usage**: `!m vol 50`") + cgb.CreateCommand(Prefix + "volume") + .Alias(Prefix + "vol") + .Description($"Sets the music volume 0-100% | `{Prefix}vol 50`") .Parameter("val", ParameterType.Required) .Do(async e => { @@ -198,10 +198,10 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage($"🎵 `Volume set to {volume}%`").ConfigureAwait(false); }); - cgb.CreateCommand("defvol") - .Alias("dv") + cgb.CreateCommand(Prefix + "defvol") + .Alias(Prefix + "dv") .Description("Sets the default music volume when music playback is started (0-100)." + - " Does not persist through restarts.\n**Usage**: `!m dv 80`") + $" Persists through restarts. | `{Prefix}dv 80`") .Parameter("val", ParameterType.Required) .Do(async e => { @@ -217,8 +217,9 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`").ConfigureAwait(false); }); - cgb.CreateCommand("mute").Alias("min") - .Description("Sets the music volume to 0%\n**Usage**: `!m min`") + cgb.CreateCommand(Prefix + "mute") + .Alias(Prefix + "min") + .Description($"Sets the music volume to 0% | `{Prefix}min`") .Do(e => { MusicPlayer musicPlayer; @@ -229,8 +230,8 @@ namespace NadekoBot.Modules.Music musicPlayer.SetVolume(0); }); - cgb.CreateCommand("max") - .Description("Sets the music volume to 100% (real max is actually 150%).\n**Usage**: `!m max`") + cgb.CreateCommand(Prefix + "max") + .Description($"Sets the music volume to 100%. | `{Prefix}max`") .Do(e => { MusicPlayer musicPlayer; @@ -241,8 +242,8 @@ namespace NadekoBot.Modules.Music musicPlayer.SetVolume(100); }); - cgb.CreateCommand("half") - .Description("Sets the music volume to 50%.\n**Usage**: `!m half`") + cgb.CreateCommand(Prefix + "half") + .Description($"Sets the music volume to 50%. | `{Prefix}half`") .Do(e => { MusicPlayer musicPlayer; @@ -253,9 +254,9 @@ namespace NadekoBot.Modules.Music musicPlayer.SetVolume(50); }); - cgb.CreateCommand("shuffle") - .Alias("sh") - .Description("Shuffles the current playlist.\n**Usage**: `!m sh`") + cgb.CreateCommand(Prefix + "shuffle") + .Alias(Prefix + "sh") + .Description($"Shuffles the current playlist. | `{Prefix}sh`") .Do(async e => { MusicPlayer musicPlayer; @@ -273,9 +274,9 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage("🎵 `Songs shuffled.`").ConfigureAwait(false); }); - cgb.CreateCommand("playlist") - .Alias("pl") - .Description("Queues up to 50 songs from a youtube playlist specified by a link, or keywords.\n**Usage**: `!m pl playlist link or name`") + cgb.CreateCommand(Prefix + "playlist") + .Alias(Prefix + "pl") + .Description($"Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `{Prefix}pl playlist link or name`") .Parameter("playlist", ParameterType.Unparsed) .Do(async e => { @@ -296,10 +297,9 @@ namespace NadekoBot.Modules.Music var ids = await SearchHelper.GetVideoIDs(plId, 500).ConfigureAwait(false); if (ids == null || ids.Count == 0) { - await e.Channel.SendMessage($"🎵`Failed to find any songs.`"); + await e.Channel.SendMessage($"🎵 `Failed to find any songs.`").ConfigureAwait(false); return; } - //todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE var idArray = ids as string[] ?? ids.ToArray(); var count = idArray.Length; var msg = @@ -308,16 +308,18 @@ namespace NadekoBot.Modules.Music { try { - await QueueSong(e.Channel, e.User.VoiceChannel, id, true).ConfigureAwait(false); + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, id, true).ConfigureAwait(false); } + catch (PlaylistFullException) + { break; } catch { } } await msg.Edit("🎵 `Playlist queue complete.`").ConfigureAwait(false); }); - cgb.CreateCommand("soundcloudpl") - .Alias("scpl") - .Description("Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony`") + cgb.CreateCommand(Prefix + "soundcloudpl") + .Alias(Prefix + "scpl") + .Description($"Queue a soundcloud playlist using a link. | `{Prefix}scpl https://soundcloud.com/saratology/sets/symphony`") .Parameter("pl", ParameterType.Unparsed) .Do(async e => { @@ -326,8 +328,8 @@ namespace NadekoBot.Modules.Music if (string.IsNullOrWhiteSpace(pl)) return; - var scvids = JObject.Parse(await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Creds.SoundCloudClientID}"))["tracks"].ToObject(); - await QueueSong(e.Channel, e.User.VoiceChannel, scvids[0].TrackLink); + var scvids = JObject.Parse(await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false))["tracks"].ToObject(); + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false); MusicPlayer mp; if (!MusicPlayers.TryGetValue(e.Server, out mp)) @@ -335,20 +337,24 @@ namespace NadekoBot.Modules.Music foreach (var svideo in scvids.Skip(1)) { - mp.AddSong(new Song(new Classes.SongInfo + try { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = MusicType.Normal, - Query = svideo.TrackLink, - })); + mp.AddSong(new Song(new Classes.SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + }), e.User.Name); + } + catch (PlaylistFullException) { break; } } }); - cgb.CreateCommand("localplaylst") - .Alias("lopl") - .Description("Queues all songs from a directory. **Bot Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") + cgb.CreateCommand(Prefix + "localplaylst") + .Alias(Prefix + "lopl") + .Description($"Queues all songs from a directory. **Bot Owner Only!** | `{Prefix}lopl C:/music/classical`") .Parameter("directory", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -362,15 +368,23 @@ namespace NadekoBot.Modules.Music .Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); foreach (var file in fileEnum) { - await QueueSong(e.Channel, e.User.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false); + try + { + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false); + } + catch (PlaylistFullException) + { + break; + } + catch { } } await e.Channel.SendMessage("🎵 `Directory queue complete.`").ConfigureAwait(false); } catch { } }); - cgb.CreateCommand("radio").Alias("ra") - .Description("Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf\n**Usage**: `!m ra radio link here`") + cgb.CreateCommand(Prefix + "radio").Alias(Prefix + "ra") + .Description($"Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `{Prefix}ra radio link here`") .Parameter("radio_link", ParameterType.Required) .Do(async e => { @@ -379,7 +393,7 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.").ConfigureAwait(false); return; } - await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("radio_link"), musicType: MusicType.Radio).ConfigureAwait(false); + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("radio_link"), musicType: MusicType.Radio).ConfigureAwait(false); if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) { await Task.Delay(10000).ConfigureAwait(false); @@ -387,9 +401,9 @@ namespace NadekoBot.Modules.Music } }); - cgb.CreateCommand("local") - .Alias("lo") - .Description("Queues a local file by specifying a full path. **Bot Owner Only!**\n**Usage**: `!m lo C:/music/mysong.mp3`") + cgb.CreateCommand(Prefix + "local") + .Alias(Prefix + "lo") + .Description($"Queues a local file by specifying a full path. **Bot Owner Only!** | `{Prefix}lo C:/music/mysong.mp3`") .Parameter("path", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -397,12 +411,12 @@ namespace NadekoBot.Modules.Music var arg = e.GetArg("path"); if (string.IsNullOrWhiteSpace(arg)) return; - await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("path"), musicType: MusicType.Local).ConfigureAwait(false); + await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("path"), musicType: MusicType.Local).ConfigureAwait(false); }); - cgb.CreateCommand("move") - .Alias("mv") - .Description("Moves the bot to your voice channel. (works only if music is already playing)\n**Usage**: `!m mv`") + cgb.CreateCommand(Prefix + "move") + .Alias(Prefix + "mv") + .Description($"Moves the bot to your voice channel. (works only if music is already playing) | `{Prefix}mv`") .Do(e => { MusicPlayer musicPlayer; @@ -412,9 +426,9 @@ namespace NadekoBot.Modules.Music musicPlayer.MoveToVoiceChannel(voiceChannel); }); - cgb.CreateCommand("remove") - .Alias("rm") - .Description("Remove a song by its # in the queue, or 'all' to remove whole queue.\n**Usage**: `!m rm 5`") + cgb.CreateCommand(Prefix + "remove") + .Alias(Prefix + "rm") + .Description($"Remove a song by its # in the queue, or 'all' to remove whole queue. | `{Prefix}rm 5`") .Parameter("num", ParameterType.Required) .Do(async e => { @@ -445,9 +459,9 @@ namespace NadekoBot.Modules.Music }); //var msRegex = new Regex(@"(?\d+)>(?\d+)", RegexOptions.Compiled); - cgb.CreateCommand("movesong") - .Alias("ms") - .Description($"Moves a song from one position to another.\n**Usage**: `{Prefix} ms` 5>3") + cgb.CreateCommand(Prefix + "movesong") + .Alias(Prefix + "ms") + .Description($"Moves a song from one position to another. | `{Prefix} ms` 5>3") .Parameter("fromto") .Do(async e => { @@ -468,7 +482,7 @@ namespace NadekoBot.Modules.Music !int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 || n1 > playlist.Count || n2 > playlist.Count) { - await e.Channel.SendMessage("`Invalid input.`"); + await e.Channel.SendMessage("`Invalid input.`").ConfigureAwait(false); return; } @@ -477,12 +491,35 @@ namespace NadekoBot.Modules.Music var nn1 = n2 < n1 ? n1 : n1 - 1; playlist.RemoveAt(nn1); - await e.Channel.SendMessage($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`"); + await e.Channel.SendMessage($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false); }); - cgb.CreateCommand("cleanup") - .Description("Cleans up hanging voice connections. **Bot Owner Only!**\n**Usage**: `!m cleanup`") + cgb.CreateCommand(Prefix + "setmaxqueue") + .Alias(Prefix + "smq") + .Description($"Sets a maximum queue size. Supply 0 or no argument to have no limit. | `{Prefix}smq` 50 or `{Prefix}smq`") + .Parameter("size", ParameterType.Unparsed) + .Do(async e => + { + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) + { + return; + } + + var sizeStr = e.GetArg("size")?.Trim(); + uint size = 0; + if (string.IsNullOrWhiteSpace(sizeStr) || !uint.TryParse(sizeStr, out size)) + { + size = 0; + } + + musicPlayer.MaxQueueSize = size; + await e.Channel.SendMessage($"🎵 `Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}`"); + }); + + cgb.CreateCommand(Prefix + "cleanup") + .Description($"Cleans up hanging voice connections. **Bot Owner Only!** | `{Prefix}cleanup`") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(e => { @@ -499,9 +536,9 @@ namespace NadekoBot.Modules.Music } }); - cgb.CreateCommand("reptcursong") - .Alias("rcs") - .Description("Toggles repeat of current song.\n**Usage**: `!m rcs`") + cgb.CreateCommand(Prefix + "reptcursong") + .Alias(Prefix + "rcs") + .Description($"Toggles repeat of current song. | `{Prefix}rcs`") .Do(async e => { MusicPlayer musicPlayer; @@ -517,9 +554,9 @@ namespace NadekoBot.Modules.Music .ConfigureAwait(false); }); - cgb.CreateCommand("rpeatplaylst") - .Alias("rpl") - .Description("Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).\n**Usage**: `!m rpl`") + cgb.CreateCommand(Prefix + "rpeatplaylst") + .Alias(Prefix + "rpl") + .Description($"Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `{Prefix}rpl`") .Do(async e => { MusicPlayer musicPlayer; @@ -529,8 +566,8 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage($"🎵🔁`Repeat playlist {(currentValue ? "enabled" : "disabled")}`").ConfigureAwait(false); }); - cgb.CreateCommand("save") - .Description("Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes.\n**Usage**: `!m save classical1`") + cgb.CreateCommand(Prefix + "save") + .Description($"Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `{Prefix}save classical1`") .Parameter("name", ParameterType.Unparsed) .Do(async e => { @@ -582,8 +619,8 @@ namespace NadekoBot.Modules.Music }); - cgb.CreateCommand("load") - .Description("Loads a playlist under a certain name. \n**Usage**: `!m load classical-1`") + cgb.CreateCommand(Prefix + "load") + .Description($"Loads a playlist under a certain name. | `{Prefix}load classical-1`") .Parameter("name", ParameterType.Unparsed) .Do(async e => { @@ -628,7 +665,11 @@ namespace NadekoBot.Modules.Music { try { - await QueueSong(textCh, voiceCh, si.Query, true, (MusicType)si.ProviderType).ConfigureAwait(false); + await QueueSong(e.User, textCh, voiceCh, si.Query, true, (MusicType)si.ProviderType).ConfigureAwait(false); + } + catch (PlaylistFullException) + { + break; } catch (Exception ex) { @@ -637,9 +678,9 @@ namespace NadekoBot.Modules.Music } }); - cgb.CreateCommand("playlists") - .Alias("pls") - .Description("Lists all playlists. Paginated. 20 per page. Default page is 0.\n**Usage**:`!m pls 1`") + cgb.CreateCommand(Prefix + "playlists") + .Alias(Prefix + "pls") + .Description($"Lists all playlists. Paginated. 20 per page. Default page is 0. |`{Prefix}pls 1`") .Parameter("num", ParameterType.Optional) .Do(e => { @@ -649,14 +690,14 @@ namespace NadekoBot.Modules.Music return; var result = DbHandler.Instance.GetPlaylistData(num); if (result.Count == 0) - e.Channel.SendMessage($"`No saved playlists found on page {num}`"); + e.Channel.SendMessage($"`No saved playlists found on page {num}`").ConfigureAwait(false); else - e.Channel.SendMessage($"```js\n--- List of saved playlists ---\n\n" + string.Join("\n", result.Select(r => $"'{r.Name}-{r.Id}' by {r.Creator} ({r.SongCnt} songs)")) + $"\n\n --- Page {num} ---```"); + e.Channel.SendMessage($"```js\n--- List of saved playlists ---\n\n" + string.Join("\n", result.Select(r => $"'{r.Name}-{r.Id}' by {r.Creator} ({r.SongCnt} songs)")) + $"\n\n --- Page {num} ---```").ConfigureAwait(false); }); - cgb.CreateCommand("deleteplaylist") - .Alias("delpls") - .Description("Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m delpls animu-5`") + cgb.CreateCommand(Prefix + "deleteplaylist") + .Alias(Prefix + "delpls") + .Description($"Deletes a saved playlist. Only if you made it or if you are the bot owner. | `{Prefix}delpls animu-5`") .Parameter("pl", ParameterType.Required) .Do(async e => { @@ -668,10 +709,10 @@ namespace NadekoBot.Modules.Music DbHandler.Instance.Delete(plnum); else DbHandler.Instance.DeleteWhere(mp => mp.Id == plnum && (long)e.User.Id == mp.CreatorId); - await e.Channel.SendMessage("`Ok.` :ok:"); + await e.Channel.SendMessage("`Ok.` :ok:").ConfigureAwait(false); }); - cgb.CreateCommand("goto") + cgb.CreateCommand(Prefix + "goto") .Description("Goes to a specific time in seconds in a song.") .Parameter("time") .Do(async e => @@ -708,8 +749,8 @@ namespace NadekoBot.Modules.Music await e.Channel.SendMessage($"`Skipped to {minutes}:{seconds}`").ConfigureAwait(false); }); - cgb.CreateCommand("getlink") - .Alias("gl") + cgb.CreateCommand(Prefix + "getlink") + .Alias(Prefix + "gl") .Description("Shows a link to the currently playing song.") .Do(async e => { @@ -719,11 +760,11 @@ namespace NadekoBot.Modules.Music var curSong = musicPlayer.CurrentSong; if (curSong == null) return; - await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>"); + await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>").ConfigureAwait(false); }); - cgb.CreateCommand("autoplay") - .Alias("ap") + cgb.CreateCommand(Prefix + "autoplay") + .Alias(Prefix + "ap") .Description("Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty)") .Do(async e => { @@ -733,14 +774,14 @@ namespace NadekoBot.Modules.Music return; if (!musicPlayer.ToggleAutoplay()) - await e.Channel.SendMessage("🎶`Autoplay disabled.`"); + await e.Channel.SendMessage("🎶`Autoplay disabled.`").ConfigureAwait(false); else - await e.Channel.SendMessage("🎶`Autoplay enabled.`"); + await e.Channel.SendMessage("🎶`Autoplay enabled.`").ConfigureAwait(false); }); }); } - public static async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) + public static async Task QueueSong(User queuer, Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) { if (voiceCh == null || voiceCh.Server != textCh.Server) { @@ -772,7 +813,7 @@ namespace NadekoBot.Modules.Music lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false); if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") { - await QueueSong(textCh, voiceCh, await SearchHelper.GetRelatedVideoId(song.SongInfo.Query), silent, musicType).ConfigureAwait(false); + await QueueSong(queuer, textCh, voiceCh, await SearchHelper.GetRelatedVideoId(song.SongInfo.Query), silent, musicType).ConfigureAwait(false); } } catch (Exception e) @@ -800,8 +841,19 @@ namespace NadekoBot.Modules.Music }; return mp; }); - var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); - musicPlayer.AddSong(resolvedSong); + Song resolvedSong; + try + { + musicPlayer.ThrowIfQueueFull(); + resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); + + musicPlayer.AddSong(resolvedSong, queuer.Name); + } + catch (PlaylistFullException) + { + await textCh.SendMessage($"🎵 `Queue is full at {musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}.` "); + throw; + } if (!silent) { var queuedMessage = await textCh.SendMessage($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count}`").ConfigureAwait(false); diff --git a/NadekoBot/Modules/NSFW/NSFWModule.cs b/NadekoBot/Modules/NSFW/NSFWModule.cs index 5ea67408..e54790e2 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 +)\n**Usage**: ~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 +) | ~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 +)\n**Usage**: ~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 +) | ~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 +)\n**Usage**: ~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 +) | ~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 +)\n**Usage**: ~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 +) | ~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.\n**Usage**: ~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. | ~e621 yuri kissing") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs b/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs index 551ad193..412ce69a 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs +++ b/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs @@ -4,6 +4,7 @@ using Discord.Commands.Permissions; using NadekoBot.Classes.JSONModels; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading.Tasks; namespace NadekoBot.Modules.Permissions.Classes @@ -13,7 +14,10 @@ namespace NadekoBot.Modules.Permissions.Classes { public static PermissionChecker Instance { get; } = new PermissionChecker(); - private ConcurrentDictionary timeBlackList { get; } = new ConcurrentDictionary(); + //key - sid:command + //value - userid + private ConcurrentDictionary commandCooldowns = new ConcurrentDictionary(); + private HashSet timeBlackList { get; } = new HashSet(); static PermissionChecker() { } private PermissionChecker() @@ -22,8 +26,8 @@ namespace NadekoBot.Modules.Permissions.Classes { while (true) { - //blacklist is cleared every 1.75 seconds. That is the most time anyone will be blocked - await Task.Delay(1750).ConfigureAwait(false); + //blacklist is cleared every 1.00 seconds. That is the most time anyone will be blocked + await Task.Delay(1000).ConfigureAwait(false); timeBlackList.Clear(); } }); @@ -46,18 +50,26 @@ namespace NadekoBot.Modules.Permissions.Classes return false; } - if (timeBlackList.ContainsKey(user)) + if (timeBlackList.Contains(user.Id)) return false; - timeBlackList.TryAdd(user, DateTime.Now); - if (!channel.IsPrivate && !channel.Server.CurrentUser.GetPermissions(channel).SendMessages) { return false; } - //{ - // user.SendMessage($"I ignored your command in {channel.Server.Name}/#{channel.Name} because i don't have permissions to write to it. Please use `;acm channel_name 0` in that server instead of muting me.").GetAwaiter().GetResult(); - //} + + timeBlackList.Add(user.Id); + + ServerPermissions perms; + PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms); + + AddUserCooldown(user.Server.Id, user.Id, command.Text.ToLower()); + if (commandCooldowns.Keys.Contains(user.Server.Id+":"+command.Text.ToLower())) + { + if(perms?.Verbose == true) + error = $"{user.Mention} You have a cooldown on that command."; + return false; + } try { @@ -75,8 +87,6 @@ namespace NadekoBot.Modules.Permissions.Classes catch { } if (user.Server.Owner.Id == user.Id || (role != null && user.HasRole(role))) return true; - ServerPermissions perms; - PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms); throw new Exception($"You don't have the necessary role (**{(perms?.PermissionsControllerRole ?? "Nadeko")}**) to change permissions."); } @@ -128,8 +138,7 @@ namespace NadekoBot.Modules.Permissions.Classes Console.WriteLine($"Exception in canrun: {ex}"); try { - ServerPermissions perms; - if (PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms) && perms.Verbose) + if (perms != null && perms.Verbose) //if verbose - print errors error = ex.Message; } @@ -140,5 +149,26 @@ namespace NadekoBot.Modules.Permissions.Classes return false; } } + + public void AddUserCooldown(ulong serverId, ulong userId, string commandName) { + commandCooldowns.TryAdd(commandName, userId); + var tosave = serverId + ":" + commandName; + Task.Run(async () => + { + ServerPermissions perms; + PermissionsHandler.PermissionsDict.TryGetValue(serverId, out perms); + int cd; + if (!perms.CommandCooldowns.TryGetValue(commandName,out cd)) { + return; + } + if (commandCooldowns.TryAdd(tosave, userId)) + { + await Task.Delay(cd * 1000); + ulong throwaway; + commandCooldowns.TryRemove(tosave, out throwaway); + } + + }); + } } } diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs b/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs index aef62ac8..c972953e 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs +++ b/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs @@ -55,9 +55,11 @@ namespace NadekoBot.Modules.Permissions.Classes if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentNullException(nameof(commandText)); + var normalizedCmdTxt = commandText.Trim().ToUpperInvariant(); + foreach (var com in NadekoBot.Client.GetService().AllCommands) { - if (com.Text.ToLower().Equals(commandText.Trim().ToLower())) + if (com.Text.ToUpperInvariant().Equals(normalizedCmdTxt) || com.Aliases.Select(c=>c.ToUpperInvariant()).Contains(normalizedCmdTxt)) return com.Text; } throw new NullReferenceException("That command does not exist."); diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs b/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs index 93fa8686..c5d2e64c 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs +++ b/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs @@ -424,6 +424,21 @@ namespace NadekoBot.Modules.Permissions.Classes Task.Run(() => WriteServerToJson(serverPerms)); } + public static void SetCommandCooldown(Server server, string commandName, int value) + { + var serverPerms = PermissionsDict.GetOrAdd(server.Id, + new ServerPermissions(server.Id, server.Name)); + if (value == 0) { + int throwaway; + serverPerms.CommandCooldowns.TryRemove(commandName, out throwaway); + } + else { + serverPerms.CommandCooldowns.AddOrUpdate(commandName, value, (str, v) => value); + } + + Task.Run(() => WriteServerToJson(serverPerms)); + } + public static void AddFilteredWord(Server server, string word) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -537,6 +552,10 @@ namespace NadekoBot.Modules.Permissions.Classes public Dictionary UserPermissions { get; set; } public Dictionary ChannelPermissions { get; set; } public Dictionary RolePermissions { get; set; } + /// + /// Dictionary of command names with their respective cooldowns + /// + public ConcurrentDictionary CommandCooldowns { get; set; } public ServerPermissions(ulong id, string name) { @@ -549,6 +568,7 @@ namespace NadekoBot.Modules.Permissions.Classes UserPermissions = new Dictionary(); ChannelPermissions = new Dictionary(); RolePermissions = new Dictionary(); + CommandCooldowns = new ConcurrentDictionary(); Words = new HashSet(); } } diff --git a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs index ebf8534a..c8e63f23 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs +++ b/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs @@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Permissions.Commands .Alias(Module.Prefix + "cfi") .Description("Enables or disables automatic deleting of invites on the channel." + "If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once." + - "\n**Usage**: ;cfi enable #general-chat") + " | ;cfi enable #general-chat") .Parameter("bool") .Parameter("channel", ParameterType.Optional) .Do(async e => @@ -95,7 +95,7 @@ namespace NadekoBot.Modules.Permissions.Commands cgb.CreateCommand(Module.Prefix + "srvrfilterinv") .Alias(Module.Prefix + "sfi") - .Description("Enables or disables automatic deleting of invites on the server.\n**Usage**: ;sfi disable") + .Description("Enables or disables automatic deleting of invites on the server. | ;sfi disable") .Parameter("bool") .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs index 5607592c..3764245d 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs +++ b/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Permissions.Commands .Alias(Module.Prefix + "cfw") .Description("Enables or disables automatic deleting of messages containing banned words on the channel." + "If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once." + - "\n**Usage**: ;cfw enable #general-chat") + " | ;cfw enable #general-chat") .Parameter("bool") .Parameter("channel", ParameterType.Optional) .Do(async e => @@ -89,7 +89,7 @@ namespace NadekoBot.Modules.Permissions.Commands cgb.CreateCommand(Module.Prefix + "addfilterword") .Alias(Module.Prefix + "afw") .Description("Adds a new word to the list of filtered words" + - "\n**Usage**: ;afw poop") + " | ;afw poop") .Parameter("word", ParameterType.Unparsed) .Do(async e => { @@ -111,7 +111,7 @@ namespace NadekoBot.Modules.Permissions.Commands cgb.CreateCommand(Module.Prefix + "rmvfilterword") .Alias(Module.Prefix + "rfw") .Description("Removes the word from the list of filtered words" + - "\n**Usage**: ;rw poop") + " | ;rw poop") .Parameter("word", ParameterType.Unparsed) .Do(async e => { @@ -133,7 +133,7 @@ namespace NadekoBot.Modules.Permissions.Commands cgb.CreateCommand(Module.Prefix + "lstfilterwords") .Alias(Module.Prefix + "lfw") .Description("Shows a list of filtered words" + - "\n**Usage**: ;lfw") + " | ;lfw") .Do(async e => { try @@ -152,7 +152,7 @@ namespace NadekoBot.Modules.Permissions.Commands cgb.CreateCommand(Module.Prefix + "srvrfilterwords") .Alias(Module.Prefix + "sfw") - .Description("Enables or disables automatic deleting of messages containing forbidden words on the server.\n**Usage**: ;sfw disable") + .Description("Enables or disables automatic deleting of messages containing forbidden words on the server. | ;sfw disable") .Parameter("bool") .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/PermissionsModule.cs b/NadekoBot/Modules/Permissions/PermissionsModule.cs index 4a0c4b30..4c13b9ff 100644 --- a/NadekoBot/Modules/Permissions/PermissionsModule.cs +++ b/NadekoBot/Modules/Permissions/PermissionsModule.cs @@ -1,5 +1,6 @@ using Discord.Commands; using Discord.Modules; +using NadekoBot.Classes; using NadekoBot.Classes.JSONModels; using NadekoBot.Extensions; using NadekoBot.Modules.Games.Commands; @@ -60,7 +61,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "rolepermscopy") .Alias(Prefix + "rpc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another.\n**Usage**:`{Prefix}rpc Some Role ~ Some other role`") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another. |`{Prefix}rpc Some Role ~ Some other role`") .Parameter("from_to", ParameterType.Unparsed) .Do(async e => { @@ -88,7 +89,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "chnlpermscopy") .Alias(Prefix + "cpc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one channel to another.\n**Usage**:`{Prefix}cpc Some Channel ~ Some other channel`") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one channel to another. |`{Prefix}cpc Some Channel ~ Some other channel`") .Parameter("from_to", ParameterType.Unparsed) .Do(async e => { @@ -116,7 +117,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "usrpermscopy") .Alias(Prefix + "upc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another.\n**Usage**:`{Prefix}upc @SomeUser ~ @SomeOtherUser`") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another. |`{Prefix}upc @SomeUser ~ @SomeOtherUser`") .Parameter("from_to", ParameterType.Unparsed) .Do(async e => { @@ -145,7 +146,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "verbose") .Alias(Prefix + "v") - .Description("Sets whether to show when a command/module is blocked.\n**Usage**: ;verbose true") + .Description("Sets whether to show when a command/module is blocked. | ;verbose true") .Parameter("arg", ParameterType.Required) .Do(async e => { @@ -168,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.\n**Usage**: ;rp AwesomeRole") + .Description("Shows banned permissions for a certain role. No argument means for everyone. | ;rp AwesomeRole") .Parameter("role", ParameterType.Unparsed) .Do(async e => { @@ -194,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.\n**Usage**: ;cp #dev") + .Description("Shows banned permissions for a certain channel. No argument means for this channel. | ;cp #dev") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -219,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.\n**Usage**: ;up Kwoth") + .Description("Shows banned permissions for a certain user. No argument means for yourself. | ;up Kwoth") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -245,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.\n**Usage**: ;sm [module_name] enable") + .Description("Sets a module's permission at the server level. | ;sm \"module name\" enable") .Do(async e => { try @@ -269,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.\n**Usage**: ;sc [command_name] disable") + .Description("Sets a command's permission at the server level. | ;sc \"command name\" disable") .Do(async e => { try @@ -294,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.\n**Usage**: ;rm [module_name] enable [role_name]") + .Description("Sets a module's permission at the role level. | ;rm \"module name\" enable MyRole") .Do(async e => { try @@ -332,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.\n**Usage**: ;rc [command_name] disable [role_name]") + .Description("Sets a command's permission at the role level. | ;rc \"command name\" disable MyRole") .Do(async e => { try @@ -370,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.\n**Usage**: ;cm [module_name] enable [channel_name]") + .Description("Sets a module's permission at the channel level. | ;cm \"module name\" enable SomeChannel") .Do(async e => { try @@ -413,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.\n**Usage**: ;cc [command_name] enable [channel_name]") + .Description("Sets a command's permission at the channel level. | ;cc \"command name\" enable SomeChannel") .Do(async e => { try @@ -451,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.\n**Usage**: ;um [module_name] enable [user_name]") + .Description("Sets a module's permission at the user level. | ;um \"module name\" enable SomeUsername") .Do(async e => { try @@ -477,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.\n**Usage**: ;uc [command_name] enable [user_name]") + .Description("Sets a command's permission at the user level. | ;uc \"command name\" enable SomeUsername") .Do(async e => { try @@ -501,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.\n**Usage**: ;asm [enable/disable]") + .Description("Sets permissions for all modules at the server level. | ;asm [enable/disable]") .Do(async e => { try @@ -527,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.\n**Usage**: ;asc [module_name] [enable/disable]") + .Description("Sets permissions for all commands from a certain module at the server level. | ;asc \"module name\" [enable/disable]") .Do(async e => { try @@ -554,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.\n**Usage**: ;acm [enable/disable] [channel_name]") + .Description("Sets permissions for all modules at the channel level. | ;acm [enable/disable] SomeChannel") .Do(async e => { try @@ -583,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.\n**Usage**: ;acc [module_name] [enable/disable] [channel_name]") + .Description("Sets permissions for all commands from a certain module at the channel level. | ;acc \"module name\" [enable/disable] SomeChannel") .Do(async e => { try @@ -610,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.\n**Usage**: ;arm [enable/disable] [role_name]") + .Description("Sets permissions for all modules at the role level. | ;arm [enable/disable] MyRole") .Do(async e => { try @@ -638,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.\n**Usage**: ;arc [module_name] [enable/disable] [role_name]") + .Description("Sets permissions for all commands from a certain module at the role level. | ;arc \"module name\" [enable/disable] MyRole") .Do(async e => { try @@ -678,7 +679,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "ubl") - .Description("Blacklists a mentioned user.\n**Usage**: ;ubl [user_mention]") + .Description("Blacklists a mentioned user. | ;ubl [user_mention]") .Parameter("user", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -694,7 +695,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "uubl") - .Description($"Unblacklists a mentioned user.\n**Usage**: {Prefix}uubl [user_mention]") + .Description($"Unblacklists a mentioned user. | {Prefix}uubl [user_mention]") .Parameter("user", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -717,7 +718,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "cbl") - .Description("Blacklists a mentioned channel (#general for example).\n**Usage**: ;cbl [channel_mention]") + .Description("Blacklists a mentioned channel (#general for example). | ;cbl #some_channel") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -732,7 +733,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "cubl") - .Description("Unblacklists a mentioned channel (#general for example).\n**Usage**: ;cubl [channel_mention]") + .Description("Unblacklists a mentioned channel (#general for example). | ;cubl #some_channel") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -747,7 +748,7 @@ namespace NadekoBot.Modules.Permissions }); cgb.CreateCommand(Prefix + "sbl") - .Description("Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY**\n**Usage**: ;sbl [servername/serverid]") + .Description("Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;sbl [servername/serverid]") .Parameter("server", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -776,6 +777,57 @@ namespace NadekoBot.Modules.Permissions await e.Channel.SendMessage($"`Sucessfully blacklisted server {server.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); }); + + cgb.CreateCommand(Prefix + "cmdcooldown") + .Alias(Prefix+ "cmdcd") + .Description($"Sets a cooldown per user for a command. Set 0 to clear. | `{Prefix}cmdcd \"some cmd\" 5`") + .Parameter("command", ParameterType.Required) + .Parameter("secs",ParameterType.Required) + .AddCheck(SimpleCheckers.ManageMessages()) + .Do(async e => + { + try + { + var command = PermissionHelper.ValidateCommand(e.GetArg("command")); + var secsStr = e.GetArg("secs").Trim(); + int secs; + if (!int.TryParse(secsStr, out secs) || secs < 0 || secs > 3600) + throw new ArgumentOutOfRangeException("secs", "Invalid second parameter. (Must be a number between 0 and 3600)"); + + + PermissionsHandler.SetCommandCooldown(e.Server, command, secs); + if(secs == 0) + await e.Channel.SendMessage($"Command **{command}** has no coooldown now.").ConfigureAwait(false); + else + await e.Channel.SendMessage($"Command **{command}** now has a **{secs} {(secs==1 ? "second" : "seconds")}** cooldown.").ConfigureAwait(false); + } + catch (ArgumentException exArg) + { + await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); + } + catch (Exception ex) + { + await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); + } + }); + + cgb.CreateCommand(Prefix + "allcmdcooldowns") + .Alias(Prefix + "acmdcds") + .Description("Shows a list of all commands and their respective cooldowns.") + .Do(async e => + { + ServerPermissions perms; + PermissionsHandler.PermissionsDict.TryGetValue(e.Server.Id, out perms); + if (perms == null) + return; + + if (!perms.CommandCooldowns.Any()) + { + await e.Channel.SendMessage("`No command cooldowns set.`").ConfigureAwait(false); + return; + } + await e.Channel.SendMessage(SearchHelper.ShowInPrettyCode(perms.CommandCooldowns.Select(c=>c.Key+ ": "+c.Value+" secs"),s=>$"{s,-30}",2)).ConfigureAwait(false); + }); }); } } diff --git a/NadekoBot/Modules/Pokemon/PokemonModule.cs b/NadekoBot/Modules/Pokemon/PokemonModule.cs index 7cc90720..ae3ae0d9 100644 --- a/NadekoBot/Modules/Pokemon/PokemonModule.cs +++ b/NadekoBot/Modules/Pokemon/PokemonModule.cs @@ -80,7 +80,7 @@ namespace NadekoBot.Modules.Pokemon commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "attack") - .Description("Attacks a target with the given move") + .Description($"Attacks a target with the given move. Use `{Prefix}movelist` to see a list of moves your type can use. | `{Prefix}attack \"vine whip\" @someguy`") .Parameter("move", ParameterType.Required) .Parameter("target", ParameterType.Unparsed) .Do(async e => @@ -210,7 +210,7 @@ namespace NadekoBot.Modules.Pokemon }); cgb.CreateCommand(Prefix + "heal") - .Description($"Heals someone. Revives those that fainted. Costs a {NadekoBot.Config.CurrencyName} \n**Usage**:{Prefix}revive @someone") + .Description($"Heals someone. Revives those who fainted. Costs a {NadekoBot.Config.CurrencyName} | {Prefix}heal @someone") .Parameter("target", ParameterType.Unparsed) .Do(async e => { @@ -242,7 +242,7 @@ namespace NadekoBot.Modules.Pokemon return; } var target = (usr.Id == e.User.Id) ? "yourself" : usr.Name; - FlowersHandler.RemoveFlowers(e.User, $"Poke-Heal {target}", amount); + await FlowersHandler.RemoveFlowers(e.User, $"Poke-Heal {target}", amount).ConfigureAwait(false); //healing targetStats.Hp = targetStats.MaxHp; if (HP < 0) @@ -263,7 +263,7 @@ namespace NadekoBot.Modules.Pokemon }); cgb.CreateCommand(Prefix + "type") - .Description($"Get the poketype of the target.\n**Usage**: {Prefix}type @someone") + .Description($"Get the poketype of the target. | {Prefix}type @someone") .Parameter("target", ParameterType.Unparsed) .Do(async e => { @@ -282,7 +282,7 @@ namespace NadekoBot.Modules.Pokemon }); cgb.CreateCommand(Prefix + "settype") - .Description($"Set your poketype. Costs a {NadekoBot.Config.CurrencyName}.\n**Usage**: {Prefix}settype fire") + .Description($"Set your poketype. Costs a {NadekoBot.Config.CurrencyName}. | {Prefix}settype fire") .Parameter("targetType", ParameterType.Unparsed) .Do(async e => { @@ -309,7 +309,7 @@ namespace NadekoBot.Modules.Pokemon await e.Channel.SendMessage($"{e.User.Mention} you don't have enough {NadekoBot.Config.CurrencyName}s! \nYou still need {amount - pts} {NadekoBot.Config.CurrencySign} to be able to do this!").ConfigureAwait(false); return; } - FlowersHandler.RemoveFlowers(e.User, $"set usertype to {targetTypeStr}", amount); + await FlowersHandler.RemoveFlowers(e.User, $"set usertype to {targetTypeStr}", amount).ConfigureAwait(false); //Actually changing the type here var preTypes = DbHandler.Instance.GetAllRows(); Dictionary Dict = preTypes.ToDictionary(x => x.UserId, y => y.Id.Value); diff --git a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs b/NadekoBot/Modules/Searches/Commands/EvalCommand.cs index 57f45342..ed469517 100644 --- a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs +++ b/NadekoBot/Modules/Searches/Commands/EvalCommand.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches.Commands { cgb.CreateCommand(Module.Prefix + "calculate") .Alias(Module.Prefix + "calc") - .Description("Evaluate a mathematical expression.\n**Usage**: ~calc 1+1") + .Description("Evaluate a mathematical expression. | ~calc 1+1") .Parameter("expression", ParameterType.Unparsed) .Do(EvalFunc()); } @@ -49,11 +49,11 @@ namespace NadekoBot.Modules.Searches.Commands string result = parser.Parse(expression).ToString(); return result; } - catch (OverflowException e) + catch (OverflowException) { return $"Overflow error on {expression}"; } - catch (FormatException e) + catch (FormatException) { return $"\"{expression}\" was not formatted correctly"; } diff --git a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/NadekoBot/Modules/Searches/Commands/LoLCommands.cs index 13f92741..bbe2eca9 100644 --- a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/LoLCommands.cs @@ -75,7 +75,7 @@ namespace NadekoBot.Modules.Searches.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "lolchamp") - .Description("Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role.\n**Usage**:~lolchamp Riven or ~lolchamp Annie sup") + .Description("Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |~lolchamp Riven or ~lolchamp Annie sup") .Parameter("champ", ParameterType.Required) .Parameter("position", ParameterType.Unparsed) .Do(async e => diff --git a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs index ebbdbf34..5eff7c78 100644 --- a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs @@ -25,7 +25,7 @@ namespace NadekoBot.Modules.Searches.Commands string.Join("\n", JsonConvert.DeserializeObject>(await SearchHelper.GetResponseStringAsync("http://memegen.link/templates/")) .Select(kvp => Path.GetFileName(kvp.Value)) .GroupBy(item => (i++) / 4) - .Select(ig => string.Join("", ig.Select(el => $"{el,-17}")))) + .Select(ig => string.Concat(ig.Select(el => $"{el,-17}")))) + $"\n```").ConfigureAwait(false); }); diff --git a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs index 8af28d0f..451e9a13 100644 --- a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "osu") - .Description("Shows osu stats for a player.\n**Usage**: `~osu Name` or `~osu Name taiko`") + .Description("Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`") .Parameter("usr", ParameterType.Required) .Parameter("mode", ParameterType.Unparsed) .Do(async e => @@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Searches.Commands }); cgb.CreateCommand(Module.Prefix + "osu b") - .Description("Shows information about an osu beatmap.\n**Usage**:~osu b https://osu.ppy.sh/s/127712") + .Description("Shows information about an osu beatmap. |~osu b https://osu.ppy.sh/s/127712") .Parameter("map", ParameterType.Unparsed) .Do(async e => { @@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Searches.Commands }); cgb.CreateCommand(Module.Prefix + "osu top5") - .Description("Displays a user's top 5 plays. \n**Usage**:~osu top5 Name") + .Description("Displays a user's top 5 plays. |~osu top5 Name") .Parameter("usr", ParameterType.Required) .Parameter("mode", ParameterType.Unparsed) .Do(async e => diff --git a/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs b/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs index 6bdcf00b..a171abe9 100644 --- a/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs @@ -42,12 +42,12 @@ namespace NadekoBot.Modules.Searches.Commands }); cgb.CreateCommand(Prefix + "pokemonability") - .Alias(Prefix + "pokab") + .Alias(Prefix + "pokeab") .Description("Searches for a pokemon ability.") .Parameter("abil", ParameterType.Unparsed) .Do(async e => { - var ab = e.GetArg("abil")?.Trim().ToUpperInvariant(); + var ab = e.GetArg("abil")?.Trim().ToUpperInvariant().Replace(" ", ""); if (string.IsNullOrWhiteSpace(ab)) return; foreach (var kvp in pokemonAbilities) diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs index 5895c932..d5c6c755 100644 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs @@ -122,7 +122,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "hitbox") .Alias(Module.Prefix + "hb") .Description("Notifies this channel when a certain user starts streaming." + - "\n**Usage**: ~hitbox SomeStreamer") + " | ~hitbox SomeStreamer") .Parameter("username", ParameterType.Unparsed) .AddCheck(SimpleCheckers.ManageServer()) .Do(TrackStream(StreamNotificationConfig.StreamType.Hitbox)); @@ -130,7 +130,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "twitch") .Alias(Module.Prefix + "tw") .Description("Notifies this channel when a certain user starts streaming." + - "\n**Usage**: ~twitch SomeStreamer") + " | ~twitch SomeStreamer") .AddCheck(SimpleCheckers.ManageServer()) .Parameter("username", ParameterType.Unparsed) .Do(TrackStream(StreamNotificationConfig.StreamType.Twitch)); @@ -138,7 +138,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "beam") .Alias(Module.Prefix + "bm") .Description("Notifies this channel when a certain user starts streaming." + - "\n**Usage**: ~beam SomeStreamer") + " | ~beam SomeStreamer") .AddCheck(SimpleCheckers.ManageServer()) .Parameter("username", ParameterType.Unparsed) .Do(TrackStream(StreamNotificationConfig.StreamType.Beam)); @@ -146,7 +146,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "checkhitbox") .Alias(Module.Prefix + "chhb") .Description("Checks if a certain user is streaming on the hitbox platform." + - "\n**Usage**: ~chhb SomeStreamer") + " | ~chhb SomeStreamer") .Parameter("username", ParameterType.Unparsed) .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => @@ -175,7 +175,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "checktwitch") .Alias(Module.Prefix + "chtw") .Description("Checks if a certain user is streaming on the twitch platform." + - "\n**Usage**: ~chtw SomeStreamer") + " | ~chtw SomeStreamer") .AddCheck(SimpleCheckers.ManageServer()) .Parameter("username", ParameterType.Unparsed) .Do(async e => @@ -204,7 +204,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "checkbeam") .Alias(Module.Prefix + "chbm") .Description("Checks if a certain user is streaming on the beam platform." + - "\n**Usage**: ~chbm SomeStreamer") + " | ~chbm SomeStreamer") .AddCheck(SimpleCheckers.ManageServer()) .Parameter("username", ParameterType.Unparsed) .Do(async e => @@ -233,7 +233,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "removestream") .Alias(Module.Prefix + "rms") .Description("Removes notifications of a certain streamer on this channel." + - "\n**Usage**: ~rms SomeGuy") + " | ~rms SomeGuy") .AddCheck(SimpleCheckers.ManageServer()) .Parameter("username", ParameterType.Unparsed) .Do(async e => @@ -261,7 +261,7 @@ namespace NadekoBot.Modules.Searches.Commands cgb.CreateCommand(Module.Prefix + "liststreams") .Alias(Module.Prefix + "ls") .Description("Lists all streams you are following on this server." + - "\n**Usage**: ~ls") + " | ~ls") .Do(async e => { diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index 8b03984d..1a2ed20b 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -15,6 +15,7 @@ using System.Drawing; using System.IO; using System.Linq; using System.Net.Http; +using System.Web; namespace NadekoBot.Modules.Searches { @@ -47,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.\n**Usage**: {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 => @@ -157,7 +158,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "i") - .Description("Pulls the first image found using a search parameter. Use ~ir for different results.\n**Usage**: ~i cute kitten") + .Description("Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -183,7 +184,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "ir") - .Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten") + .Description("Pulls a random image using a search parameter. | ~ir cute kitten") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -219,8 +220,21 @@ $@"🌍 **Weather for** 【{obj["target"]}】 .ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "google") + .Alias(Prefix + "g") + .Description("Get a google search link for some terms.") + .Parameter("terms", ParameterType.Unparsed) + .Do(async e => + { + var terms = e.GetArg("terms")?.Trim(); + if (string.IsNullOrWhiteSpace(terms)) + return; + await e.Channel.SendMessage($"https://google.com/search?q={ HttpUtility.UrlEncode(terms).Replace(' ', '+') }") + .ConfigureAwait(false); + }); + cgb.CreateCommand(Prefix + "hs") - .Description("Searches for a Hearthstone card and shows its image. Takes a while to complete.\n**Usage**:~hs Ysera") + .Description("Searches for a Hearthstone card and shows its image. Takes a while to complete. |~hs Ysera") .Parameter("name", ParameterType.Unparsed) .Do(async e => { @@ -261,7 +275,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "ud") - .Description("Searches Urban Dictionary for a word.\n**Usage**:~ud Pineapple") + .Description("Searches Urban Dictionary for a word. |~ud Pineapple") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -290,7 +304,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); // thanks to Blaubeerwald cgb.CreateCommand(Prefix + "#") - .Description("Searches Tagdef.com for a hashtag.\n**Usage**:~# ff") + .Description("Searches Tagdef.com for a hashtag. |~# ff") .Parameter("query", ParameterType.Unparsed) .Do(async e => { @@ -364,15 +378,15 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "magicitem") - .Alias(Prefix + "mi") - .Description("Shows a random magicitem from ") - .Do(async e => - { - var magicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); - var item = magicItems[rng.Next(0, magicItems.Count)].ToString(); + .Alias(Prefix + "mi") + .Description("Shows a random magicitem from ") + .Do(async e => + { + var magicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); + var item = magicItems[rng.Next(0, magicItems.Count)].ToString(); - await e.Channel.SendMessage(item).ConfigureAwait(false); - }); + await e.Channel.SendMessage(item).ConfigureAwait(false); + }); cgb.CreateCommand(Prefix + "revav") .Description("Returns a google reverse image search for someone's avatar.") @@ -404,7 +418,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 +)\n**Usage**: ~safebooru yuri+kissing") + .Description("Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~safebooru yuri+kissing") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -431,7 +445,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 }); cgb.CreateCommand(Prefix + "clr") - .Description("Shows you what color corresponds to that hex.\n**Usage**: `~clr 00ff00`") + .Description("Shows you what color corresponds to that hex. | `~clr 00ff00`") .Parameter("color", ParameterType.Unparsed) .Do(async e => { @@ -480,7 +494,7 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "av") .Alias(Prefix + "avatar") .Parameter("mention", ParameterType.Required) - .Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X") + .Description("Shows a mentioned person's avatar. | ~av @X") .Do(async e => { var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs index 1743af45..b1a10a8c 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs @@ -60,7 +60,7 @@ namespace NadekoBot.Modules.Translator.Helpers text = await http.GetStringAsync(url).ConfigureAwait(false); } - return JArray.Parse(text)[0][0][0].ToString(); + return (string.Concat(JArray.Parse(text)[0].Select(x => x[0]))); } #endregion diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index e4512980..9663c7bc 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.\n**Usage**: {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 fc30deed..5a98fe40 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." + - "\n**Usage**: bind [board_id]") + " | bind [board_id]") .Parameter("board_id", Discord.Commands.ParameterType.Required) .Do(async e => { diff --git a/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 4447537a..7d14a16f 100644 --- a/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -18,7 +18,7 @@ namespace NadekoBot.Modules.Utility.Commands { cgb.CreateCommand(Module.Prefix + "serverinfo") .Alias(Module.Prefix + "sinfo") - .Description($"Shows info about the server the bot is on. If no channel is supplied, it defaults to current one.\n**Usage**:{Module.Prefix}sinfo Some Server") + .Description($"Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. |{Module.Prefix}sinfo Some Server") .Parameter("server", ParameterType.Optional) .Do(async e => { @@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Utility.Commands cgb.CreateCommand(Module.Prefix + "channelinfo") .Alias(Module.Prefix + "cinfo") - .Description($"Shows info about the channel. If no channel is supplied, it defaults to current one.\n**Usage**:{Module.Prefix}cinfo #some-channel") + .Description($"Shows info about the channel. If no channel is supplied, it defaults to current one. |{Module.Prefix}cinfo #some-channel") .Parameter("channel", ParameterType.Optional) .Do(async e => { @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Utility.Commands cgb.CreateCommand(Module.Prefix + "userinfo") .Alias(Module.Prefix + "uinfo") - .Description($"Shows info about the user. If no user is supplied, it defaults a user running the command.\n**Usage**:{Module.Prefix}uinfo @SomeUser") + .Description($"Shows info about the user. If no user is supplied, it defaults a user running the command. |{Module.Prefix}uinfo @SomeUser") .Parameter("user", ParameterType.Optional) .Do(async e => { diff --git a/NadekoBot/Modules/Utility/Commands/Remind.cs b/NadekoBot/Modules/Utility/Commands/Remind.cs index 0ce58d6f..dabd4158 100644 --- a/NadekoBot/Modules/Utility/Commands/Remind.cs +++ b/NadekoBot/Modules/Utility/Commands/Remind.cs @@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Utility.Commands .Description("Sends a message to you or a channel after certain amount of time. " + "First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. " + "Third argument is a (multiword)message. " + - "\n**Usage**: `.remind me 1d5h Do something` or `.remind #general Start now!`") + " | `.remind me 1d5h Do something` or `.remind #general Start now!`") .Parameter("meorchannel", ParameterType.Required) .Parameter("time", ParameterType.Required) .Parameter("message", ParameterType.Unparsed) diff --git a/NadekoBot/Modules/Utility/UtilityModule.cs b/NadekoBot/Modules/Utility/UtilityModule.cs index 6d510a25..380e5acf 100644 --- a/NadekoBot/Modules/Utility/UtilityModule.cs +++ b/NadekoBot/Modules/Utility/UtilityModule.cs @@ -48,7 +48,7 @@ namespace NadekoBot.Modules.Utility if (arr.Length == 0) await e.Channel.SendMessage("Nobody. (not 100% sure)").ConfigureAwait(false); else - await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Join("", ig.Select(el => $" {el,-35}")))) + "\n```").ConfigureAwait(false); + await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $" {el,-35}")))) + "\n```").ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "inrole") @@ -99,7 +99,7 @@ namespace NadekoBot.Modules.Utility .Description("Shows some basic stats for Nadeko.") .Do(async e => { - await e.Channel.SendMessage(await NadekoStats.Instance.GetStats()); + await e.Channel.SendMessage(await NadekoStats.Instance.GetStats()).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "dysyd") diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs index df3c8f7d..4a091e3e 100644 --- a/NadekoBot/NadekoBot.cs +++ b/NadekoBot/NadekoBot.cs @@ -41,8 +41,9 @@ namespace NadekoBot public static LocalizedStrings Locale { get; set; } = new LocalizedStrings(); public static string BotMention { get; set; } = ""; public static bool Ready { get; set; } = false; + public static Action OnReady { get; set; } = delegate { }; - private static Channel OwnerPrivateChannel { get; set; } + private static List OwnerPrivateChannels { get; set; } private static void Main() { @@ -196,7 +197,7 @@ namespace NadekoBot return; } #if NADEKO_RELEASE - await Task.Delay(100000).ConfigureAwait(false); + await Task.Delay(90000).ConfigureAwait(false); #else await Task.Delay(1000).ConfigureAwait(false); #endif @@ -205,15 +206,19 @@ namespace NadekoBot Console.WriteLine(await NadekoStats.Instance.GetStats().ConfigureAwait(false)); Console.WriteLine("-----------------"); - try - { - OwnerPrivateChannel = await Client.CreatePrivateChannel(Creds.OwnerIds[0]).ConfigureAwait(false); - } - catch - { - Console.WriteLine("Failed creating private channel with the first owner listed in credentials.json"); - } + OwnerPrivateChannels = new List(Creds.OwnerIds.Length); + foreach (var id in Creds.OwnerIds) + { + try + { + OwnerPrivateChannels.Add(await Client.CreatePrivateChannel(id).ConfigureAwait(false)); + } + catch + { + Console.WriteLine($"Failed creating private channel with the owner {id} listed in credentials.json"); + } + } Client.ClientAPI.SendingRequest += (s, e) => { var request = e.Request as Discord.API.Client.Rest.SendMessageRequest; @@ -225,6 +230,7 @@ namespace NadekoBot }; PermissionsHandler.Initialize(); NadekoBot.Ready = true; + NadekoBot.OnReady(); }); Console.WriteLine("Exiting..."); Console.ReadKey(); @@ -234,8 +240,18 @@ namespace NadekoBot public static async Task SendMessageToOwner(string message) { - if (Config.ForwardMessages && OwnerPrivateChannel != null) - await OwnerPrivateChannel.SendMessage(message).ConfigureAwait(false); + if (Config.ForwardMessages && OwnerPrivateChannels.Any()) + if (Config.ForwardToAllOwners) + OwnerPrivateChannels.ForEach(async c => + { + try { await c.SendMessage(message).ConfigureAwait(false); } catch { } + }); + else + { + var c = OwnerPrivateChannels.FirstOrDefault(); + if (c != null) + await c.SendMessage(message).ConfigureAwait(false); + } } private static bool repliedRecently = false; @@ -248,8 +264,8 @@ namespace NadekoBot if (ConfigHandler.IsBlackListed(e)) return; - if (Config.ForwardMessages && !NadekoBot.Creds.OwnerIds.Contains(e.User.Id) && OwnerPrivateChannel != null) - await OwnerPrivateChannel.SendMessage(e.User + ": ```\n" + e.Message.Text + "\n```").ConfigureAwait(false); + if (Config.ForwardMessages && !NadekoBot.Creds.OwnerIds.Contains(e.User.Id) && OwnerPrivateChannels.Any()) + await SendMessageToOwner(e.User + ": ```\n" + e.Message.Text + "\n```").ConfigureAwait(false); if (repliedRecently) return; @@ -263,5 +279,3 @@ namespace NadekoBot } } } - -//95520984584429568 meany diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index 84efa690..cd467b7f 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -40,10 +40,12 @@ full false bin\Debug\ - TRACE;DEBUG;__DEMO__,__DEMO_EXPERIMENTAL__ + TRACE;DEBUG prompt 4 true + + AnyCPU @@ -74,6 +76,46 @@ MinimumRecommendedRules.ruleset true + + true + bin\x64\Debug\ + TRACE;DEBUG + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x64\PRIVATE\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\Release\ + TRACE;NADEKO_RELEASE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + ..\packages\VideoLibrary.1.3.3\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\libvideo.dll @@ -144,6 +186,7 @@ + diff --git a/NadekoBot/_Models/JSONModels/Configuration.cs b/NadekoBot/_Models/JSONModels/Configuration.cs index ad75f35d..295134fe 100644 --- a/NadekoBot/_Models/JSONModels/Configuration.cs +++ b/NadekoBot/_Models/JSONModels/Configuration.cs @@ -3,25 +3,14 @@ using NadekoBot.Extensions; using Newtonsoft.Json; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization; namespace NadekoBot.Classes.JSONModels { public class Configuration { - public bool DontJoinServers { get; set; } = false; - public bool ForwardMessages { get; set; } = true; - public bool IsRotatingStatus { get; set; } = false; - public int BufferSize { get; set; } = 4.MiB(); - [JsonIgnore] - public List Quotes { get; set; } = new List(); - - [JsonIgnore] - public List PokemonTypes { get; set; } = new List(); - - public string RemindMessageFormat { get; set; } = "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗"; - - public Dictionary> CustomReactions { get; set; } = new Dictionary>() + public static readonly Dictionary> DefaultCustomReactions = new Dictionary> { {@"\o\", new List() { "/o/" } }, @@ -93,6 +82,22 @@ namespace NadekoBot.Classes.JSONModels } } }; + public bool DontJoinServers { get; set; } = false; + public bool ForwardMessages { get; set; } = true; + public bool ForwardToAllOwners { get; set; } = false; + public bool IsRotatingStatus { get; set; } = false; + public int BufferSize { get; set; } = 4.MiB(); + + public List Quotes { get; set; } = new List(); + + [JsonIgnore] + public List PokemonTypes { get; set; } = new List(); + + public string RemindMessageFormat { get; set; } = "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗"; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public Dictionary> CustomReactions { get; set; } + public List RotatingStatuses { get; set; } = new List(); public CommandPrefixesModel CommandPrefixes { get; set; } = new CommandPrefixesModel(); public HashSet ServerBlacklist { get; set; } = new HashSet(); @@ -104,6 +109,22 @@ namespace NadekoBot.Classes.JSONModels 143515953525817344 }; + [OnDeserialized] + internal void OnDeserialized(StreamingContext context) + { + if (CustomReactions == null) + { + CustomReactions = DefaultCustomReactions; + } + } + [OnSerializing] + internal void OnSerializing(StreamingContext context) + { + if (CustomReactions == null) + { + CustomReactions = DefaultCustomReactions; + } + } public string[] _8BallResponses { get; set; } = { @@ -154,7 +175,7 @@ Nadeko Support Server: "; public string Conversations { get; set; } = "<@{0}>"; public string ClashOfClans { get; set; } = ","; public string Help { get; set; } = "-"; - public string Music { get; set; } = "!m"; + public string Music { get; set; } = "!!"; public string Trello { get; set; } = "trello "; public string Games { get; set; } = ">"; public string Gambling { get; set; } = "$"; diff --git a/NadekoBot/_Models/JSONModels/_JSONModels.cs b/NadekoBot/_Models/JSONModels/_JSONModels.cs index ae7241bd..c60d32d8 100644 --- a/NadekoBot/_Models/JSONModels/_JSONModels.cs +++ b/NadekoBot/_Models/JSONModels/_JSONModels.cs @@ -7,7 +7,7 @@ namespace NadekoBot.Classes.JSONModels public class Credentials { public string Token { get; set; } = ""; - public string ClientId { get; set; } = "116275390695079945"; + public string ClientId { get; set; } = "170254782546575360"; public ulong BotId { get; set; } = 1231231231231; public ulong[] OwnerIds { get; set; } = { 123123123123, 5675675679845 }; public string GoogleAPIKey { get; set; } = ""; diff --git a/NadekoBot/bin/Debug/credentials_example.json b/NadekoBot/bin/Debug/credentials_example.json index 245b1141..e5c9f2dd 100644 --- a/NadekoBot/bin/Debug/credentials_example.json +++ b/NadekoBot/bin/Debug/credentials_example.json @@ -1,6 +1,6 @@ { "Token": "", - "ClientId": "116275390695079945", + "ClientId": "170254782546575360", "BotId": 1231231231231, "OwnerIds": [ 123123123123, diff --git a/NadekoBot/bin/Debug/data/config_example.json b/NadekoBot/bin/Debug/data/config_example.json index bd094371..1ae8a26e 100644 --- a/NadekoBot/bin/Debug/data/config_example.json +++ b/NadekoBot/bin/Debug/data/config_example.json @@ -1,8 +1,10 @@ { "DontJoinServers": false, "ForwardMessages": true, + "ForwardToAllOwners": false, "IsRotatingStatus": false, "BufferSize": 4194304, + "Quotes": [], "RemindMessageFormat": "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗", "CustomReactions": { "\\o\\": [ @@ -81,7 +83,7 @@ "Conversations": "<@{0}>", "ClashOfClans": ",", "Help": "-", - "Music": "!m", + "Music": "!!", "Trello": "trello ", "Games": ">", "Gambling": "$", diff --git a/NadekoBot/resources/images/coins/heads.png b/NadekoBot/resources/images/coins/heads.png index 4e056a19..95fb7336 100644 Binary files a/NadekoBot/resources/images/coins/heads.png and b/NadekoBot/resources/images/coins/heads.png differ diff --git a/NadekoBot/resources/images/coins/tails.png b/NadekoBot/resources/images/coins/tails.png index 95c65a1d..c3cc8d19 100644 Binary files a/NadekoBot/resources/images/coins/tails.png and b/NadekoBot/resources/images/coins/tails.png differ diff --git a/README.md b/README.md index 36b09bd8..4992761a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) +[![Discord](https://discordapp.com/api/servers/117523346618318850/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) # NadekoBot ## [Click here to invite nadeko to your discord server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 78a9225b..00000000 --- a/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("45b2545d-c612-4919-b34c-d65ea1371c51")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/TestCards.cs b/Tests/TestCards.cs deleted file mode 100644 index f346fc37..00000000 --- a/Tests/TestCards.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using static NadekoBot.Modules.Gambling.Helpers.Cards; - -namespace Tests -{ - [TestClass] - public class TestCards - { - [TestMethod] - public void TestHandValues() - { - var setting1 = new List { - new Card(CardSuit.Clubs,10), - new Card(CardSuit.Clubs,10), - new Card(CardSuit.Clubs,10), - new Card(CardSuit.Clubs,11), - new Card(CardSuit.Diamonds,12), - }; - var result1 = "Three Of A Kind"; - - var setting2 = new List { - new Card(CardSuit.Clubs,1), - new Card(CardSuit.Hearts,2), - new Card(CardSuit.Clubs,3), - new Card(CardSuit.Spades,4), - new Card(CardSuit.Diamonds,5), - }; - var result2 = "Straight"; - - var setting3 = new List { - new Card(CardSuit.Diamonds,10), - new Card(CardSuit.Diamonds,11), - new Card(CardSuit.Diamonds,12), - new Card(CardSuit.Diamonds,13), - new Card(CardSuit.Diamonds,1), - }; - var result3 = "Royal Flush"; - - Assert.AreEqual(GetHandValue(setting1), result1); - Assert.AreEqual(GetHandValue(setting2), result2); - Assert.AreEqual(GetHandValue(setting3), result3); - } - } -} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj deleted file mode 100644 index 2a99410a..00000000 --- a/Tests/Tests.csproj +++ /dev/null @@ -1,90 +0,0 @@ - - - - Debug - AnyCPU - {45B2545D-C612-4919-B34C-D65EA1371C51} - Library - Properties - Tests - Tests - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - {27a886f5-cdda-4f4a-81ee-6dafcce9de46} - NadekoBot - - - - - - - False - - - False - - - False - - - False - - - - - - - - diff --git a/commandlist.md b/commandlist.md index 68146587..50cb13cc 100644 --- a/commandlist.md +++ b/commandlist.md @@ -1,10 +1,10 @@ ######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` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` +######You can donate on paypal: `nadekodiscordbot@gmail.com` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6029.36463` +Version: `NadekoBot v0.9.6048.2992` ### Help -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `-h`, `-help`, `@BotName help`, `@BotName h`, `~h` | Either shows a help for a single command, or PMs you help link if no arguments are specified. | '-h !m q' or just '-h' `-hgit` | Generates the commandlist.md file. **Bot Owner Only!** @@ -14,7 +14,7 @@ Command and aliases | Description | Usage `-commands`, `.commands` | List all of the bot's commands from a certain module. ### Administration -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `.grdel` | Toggles automatic deletion of greet and bye messages. `.greet` | Toggles anouncements on the current channel when someone joins the server. @@ -25,6 +25,7 @@ Command and aliases | Description | Usage `.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. `.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. `.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** +`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. `.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. `.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. `.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. @@ -42,12 +43,13 @@ Command and aliases | Description | Usage `.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. | .asar Gamer `.rsar` | Removes a specified role from the list of self-assignable roles. `.lsar` | Lists all self-assignable roles. +`.togglexclsar`, `.tesar` | toggle whether the self-assigned roles should be exclusive `.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer `.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer `.addcustreact`, `.acr` | Add a custom reaction. Guide here: **Bot Owner Only!** | .acr "hello" I love saying hello to %user% -`.listcustreact`, `.lcr` | Lists all current custom reactions (paginated with 30 commands per page). | .lcr 1 +`.listcustreact`, `.lcr` | Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. | .lcr 1 `.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. | .scr %mention% bb -`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` +`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` `.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index) `.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.aar Role Name` to enable `.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331` @@ -86,11 +88,10 @@ Command and aliases | Description | Usage `.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 -`.leave` | Leaves a server with a supplied ID. | `.leave 493243292839` -`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150` +`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150` ### Utility -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` `.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** @@ -108,7 +109,7 @@ Command and aliases | Description | Usage `.roles` | List all roles on this server or a single user if specified. ### Permissions -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | ;cfi enable #general-chat `;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | ;sfi disable @@ -126,59 +127,62 @@ Command and aliases | Description | Usage `;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 [role_name] -`;rolecmd`, `;rc` | Sets a command's permission at the role level. | ;rc [command_name] disable [role_name] -`;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | ;cm [module_name] enable [channel_name] -`;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | ;cc [command_name] enable [channel_name] -`;usrmdl`, `;um` | Sets a module's permission at the user level. | ;um [module_name] enable [user_name] -`;usrcmd`, `;uc` | Sets a command's permission at the user level. | ;uc [command_name] enable [user_name] +`;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] [channel_name] -`;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | ;acc [module_name] [enable/disable] [channel_name] -`;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | ;arm [enable/disable] [role_name] -`;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | ;arc [module_name] [enable/disable] [role_name] +`;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 [channel_mention] -`;cubl` | Unblacklists a mentioned channel (#general for example). | ;cubl [channel_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 +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 `..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 uptime` | Shows how long Nadeko has been running for. `@BotName die` | Works only for the owner. Shuts the bot down. `@BotName do you love me` | Replies with positive answer only to the bot owner. `@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. `@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | @NadekoBot fire [x] -`@BotName slm` | Shows the message where you were last mentioned in this channel (checks last 10k messages) `@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** `@BotName ab` | Try to get 'abalabahaha' ### Gambling -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | $draw [x] `$shuffle`, `$sh` | Reshuffles all cards back into the deck. `$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` +`$betflip`, `$bf` | Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `$bf 5 heads` or `$bf 3 t` `$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. `$$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone` `$give` | Give someone a certain amount of NadekoFlowers `$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!** +`$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` | ### Games -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t nohint` or `>t 5 nohint` `>tl` | Shows a current trivia leaderboard. @@ -190,7 +194,7 @@ Command and aliases | Description | Usage `>pollend` | Stops active poll on this server and prints the results in this channel. `>pick` | Picks a flower planted in this channel. `>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. Requires Manage Messages permission. | `>gc` +`>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. @@ -198,42 +202,44 @@ Command and aliases | Description | Usage `>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` ### Music -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- -`!m next`, `!m n`, `!m skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n` -`!m stop`, `!m s` | Stops the music and clears the playlist. Stays in the channel. | `!m s` -`!m destroy`, `!m d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` -`!m pause`, `!m p` | Pauses or Unpauses the song. | `!m p` -`!m queue`, `!m q`, `!m yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!m q Dream Of Venice` -`!m listqueue`, `!m lq` | Lists 15 currently queued songs per page. Default page is 1. | `!m lq` or `!m lq 2` -`!m nowplaying`, `!m np` | Shows the song currently playing. | `!m np` -`!m volume`, `!m vol` | Sets the music volume 0-100% | `!m vol 50` -`!m defvol`, `!m dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80` -`!m mute`, `!m min` | Sets the music volume to 0% | `!m min` -`!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` -`!m half` | Sets the music volume to 50%. | `!m half` -`!m shuffle`, `!m sh` | Shuffles the current playlist. | `!m sh` -`!m playlist`, `!m pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` -`!m soundcloudpl`, `!m scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony` -`!m localplaylst`, `!m lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m lopl C:/music/classical` -`!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf | `!m ra radio link here` -`!m local`, `!m lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3` -`!m move`, `!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv` -`!m remove`, `!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5` -`!m movesong`, `!m ms` | Moves a song from one position to another. | `!m ms` 5>3 -`!m cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup` -`!m reptcursong`, `!m rcs` | Toggles repeat of current song. | `!m rcs` -`!m rpeatplaylst`, `!m rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl` -`!m save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1` -`!m load` | Loads a playlist under a certain name. | `!m load classical-1` -`!m playlists`, `!m pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` -`!m deleteplaylist`, `!m delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m delpls animu-5` -`!m goto` | Goes to a specific time in seconds in a song. -`!m getlink`, `!m gl` | Shows a link to the currently playing song. -`!m autoplay`, `!m ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) +`!!next`, `!!n`, `!!skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!!n` +`!!stop`, `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s` +`!!destroy`, `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d` +`!!pause`, `!!p` | Pauses or Unpauses the song. | `!!p` +`!!queue`, `!!q`, `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice` +`!!soundcloudqueue`, `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice` +`!!listqueue`, `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2` +`!!nowplaying`, `!!np` | Shows the song currently playing. | `!!np` +`!!volume`, `!!vol` | Sets the music volume 0-100% | `!!vol 50` +`!!defvol`, `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80` +`!!mute`, `!!min` | Sets the music volume to 0% | `!!min` +`!!max` | Sets the music volume to 100%. | `!!max` +`!!half` | Sets the music volume to 50%. | `!!half` +`!!shuffle`, `!!sh` | Shuffles the current playlist. | `!!sh` +`!!playlist`, `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name` +`!!soundcloudpl`, `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl https://soundcloud.com/saratology/sets/symphony` +`!!localplaylst`, `!!lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!!lopl C:/music/classical` +`!!radio`, `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` +`!!local`, `!!lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!!lo C:/music/mysong.mp3` +`!!move`, `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` +`!!remove`, `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` +`!!movesong`, `!!ms` | Moves a song from one position to another. | `!! ms` 5>3 +`!!setmaxqueue`, `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq` 50 or `!!smq` +`!!cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!!cleanup` +`!!reptcursong`, `!!rcs` | Toggles repeat of current song. | `!!rcs` +`!!rpeatplaylst`, `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl` +`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1` +`!!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. +`!!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) ### Searches -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `~lolchamp` | Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. | ~lolchamp Riven or ~lolchamp Annie sup `~lolban` | Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time. @@ -253,9 +259,9 @@ Command and aliases | Description | Usage `~osu b` | Shows information about an osu beatmap. | ~osu b https://osu.ppy.sh/s/127712 `~osu top5` | Displays a user's top 5 plays. | ~osu top5 Name `~pokemon`, `~poke` | Searches for a pokemon. -`~pokemonability`, `~pokab` | Searches for a pokemon ability. +`~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"` +`~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 `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. @@ -265,6 +271,7 @@ Command and aliases | Description | Usage `~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 @@ -283,7 +290,7 @@ Command and aliases | Description | Usage `~av`, `~avatar` | Shows a mentioned person's avatar. | ~av @X ### NSFW -Command and aliases | Description | Usage +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 @@ -295,52 +302,55 @@ Command and aliases | Description | Usage `~butts`, `~ass`, `~butt` | Real adult content. ### ClashOfClans -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | ,cw 15 The Enemy Clan `,startwar`, `,sw` | Starts a war with a given number. `,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | ,lw [war_number] or ,lw `,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | ,call [war_number] [base_number] [optional_other_name] -`,claimfinish`, `,cf` | Finish your claim if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] +`,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] `,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number] ### Pokegame -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- -`>attack` | Attacks a target with the given move +`>attack` | Attacks a target with the given move. Use `>movelist` to see a list of moves your type can use. | `>attack "vine whip" @someguy` `>movelist`, `>ml` | Lists the moves you are able to use -`>heal` | Heals someone. Revives those that fainted. Costs a NadekoFlower | >revive @someone +`>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | >heal @someone `>type` | Get the poketype of the target. | >type @someone `>settype` | Set your poketype. Costs a NadekoFlower. | >settype fire ### Translator -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `~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 -Command and aliases | Description | Usage +Command and aliases | Description | Usage ----------------|--------------|------- `\o\` | Custom reaction. | \o\ `/o/` | Custom reaction. | /o/ `moveto` | Custom reaction. | moveto `comeatmebro` | Custom reaction. | comeatmebro `e` | Custom reaction. | e -`@BotName insult`, `<@!119777021319577610> insult` | Custom reaction. | %mention% insult -`@BotName praise`, `<@!119777021319577610> praise` | Custom reaction. | %mention% praise -`@BotName pat`, `<@!119777021319577610> pat` | Custom reaction. | %mention% pat -`@BotName cry`, `<@!119777021319577610> cry` | Custom reaction. | %mention% cry -`@BotName are you real?`, `<@!119777021319577610> are you real?` | Custom reaction. | %mention% are you real? -`@BotName are you there?`, `<@!119777021319577610> are you there?` | Custom reaction. | %mention% are you there? -`@BotName draw`, `<@!119777021319577610> draw` | Custom reaction. | %mention% draw -`@BotName bb`, `<@!119777021319577610> bb` | Custom reaction. | %mention% bb -`@BotName call`, `<@!119777021319577610> call` | Custom reaction. | %mention% call -`@BotName disguise`, `<@!119777021319577610> disguise` | Custom reaction. | %mention% disguise +`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | %mention% insult +`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | %mention% praise +`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | %mention% pat +`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | %mention% cry +`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | %mention% are you real? +`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | %mention% are you there? +`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | %mention% draw +`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | %mention% bb +`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | %mention% call +`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | %mention% disguise +`~hentai` | Custom reaction. | ~hentai ### Trello -Command and aliases | Description | Usage +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 unbind` | Unbinds a bot from the channel and board. diff --git a/discord.net b/discord.net index 80f9d6f2..3e519b5e 160000 --- a/discord.net +++ b/discord.net @@ -1 +1 @@ -Subproject commit 80f9d6f2de25355a245bf93d4019d16e3fd033ca +Subproject commit 3e519b5e0b33175e5a5ca247322b7082de484e15 diff --git a/riot.txt b/riot.txt deleted file mode 100644 index 5359981d..00000000 --- a/riot.txt +++ /dev/null @@ -1 +0,0 @@ -fb079285-44f2-47d3-b55c-d748701c36ee \ No newline at end of file