From 50f8cec33eb5a6a1b8877eadacd25bbfac4a5e85 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 29 Jun 2016 19:17:37 +0200 Subject: [PATCH 01/33] Moved `~av` from conversations to searches --- NadekoBot/Modules/Conversations/Conversations.cs | 15 --------------- NadekoBot/Modules/Searches/SearchesModule.cs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs index bb928110..c15f29d9 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/NadekoBot/Modules/Conversations/Conversations.cs @@ -255,21 +255,6 @@ namespace NadekoBot.Modules.Conversations await e.Channel.SendMessage(construct).ConfigureAwait(false); }); - cgb.CreateCommand("av") - .Alias("avatar") - .Parameter("mention", ParameterType.Required) - .Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X") - .Do(async e => - { - var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false); - }); - }); } diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index b65394c4..e8d57934 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -475,6 +475,21 @@ $@"🌍 **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") + .Do(async e => + { + var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); + if (usr == null) + { + await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); + return; + } + await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false); + }); + }); } } From 144da5a0db62459a8fe852fb65b1189e47a45944 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 29 Jun 2016 19:25:40 +0200 Subject: [PATCH 02/33] fixed $leaderboard? --- NadekoBot/Classes/DBHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NadekoBot/Classes/DBHandler.cs b/NadekoBot/Classes/DBHandler.cs index afbfb765..e7d3d838 100644 --- a/NadekoBot/Classes/DBHandler.cs +++ b/NadekoBot/Classes/DBHandler.cs @@ -198,7 +198,7 @@ Limit 20 OFFSET ?", num * 20); { using (var conn = new SQLiteConnection(FilePath)) { - return conn.Table().Take(n).ToList().OrderBy(cs => -cs.Value); + return conn.Table().OrderBy(cs => -cs.Value).Take(n).ToList(); } } } From 51d6fd88ed50060cf5c24c472919b6e3571e4b45 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 29 Jun 2016 19:28:32 +0200 Subject: [PATCH 03/33] Deleted copyme command, its annoying. --- .../Conversations/Commands/CopyCommand.cs | 62 ------------------- .../Modules/Conversations/Conversations.cs | 2 - 2 files changed, 64 deletions(-) delete mode 100644 NadekoBot/Modules/Conversations/Commands/CopyCommand.cs diff --git a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs b/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs deleted file mode 100644 index 48ef01f4..00000000 --- a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Discord.Commands; -using NadekoBot.Modules; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.Conversations.Commands -{ - internal class CopyCommand : DiscordCommand - { - private readonly HashSet CopiedUsers = new HashSet(); - - public CopyCommand(DiscordModule module) : base(module) - { - NadekoBot.Client.MessageReceived += Client_MessageReceived; - } - - private async void Client_MessageReceived(object sender, Discord.MessageEventArgs e) - { - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - try - { - if (string.IsNullOrWhiteSpace(e.Message.Text)) - return; - if (CopiedUsers.Contains(e.User.Id)) - { - await e.Channel.SendMessage(e.Message.Text).ConfigureAwait(false); - } - } - catch { } - } - - public Func DoFunc() => async e => - { - if (CopiedUsers.Contains(e.User.Id)) return; - - CopiedUsers.Add(e.User.Id); - await e.Channel.SendMessage(" I'll start copying you now.").ConfigureAwait(false); - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand("copyme") - .Alias("cm") - .Description("Nadeko starts copying everything you say. Disable with cs") - .Do(DoFunc()); - - cgb.CreateCommand("cs") - .Alias("copystop") - .Description("Nadeko stops copying you") - .Do(StopCopy()); - } - - private Func StopCopy() => async e => - { - if (!CopiedUsers.Contains(e.User.Id)) return; - - CopiedUsers.Remove(e.User.Id); - await e.Channel.SendMessage(" I wont copy anymore.").ConfigureAwait(false); - }; - } -} diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs index c15f29d9..606084b1 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/NadekoBot/Modules/Conversations/Conversations.cs @@ -1,7 +1,6 @@ using Discord; using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.Conversations.Commands; using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Conversations.Commands; @@ -19,7 +18,6 @@ namespace NadekoBot.Modules.Conversations private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; public Conversations() { - commands.Add(new CopyCommand(this)); commands.Add(new RipCommand(this)); } From ed59d11d715688537c45d634cf2132591352755a Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 29 Jun 2016 21:02:43 +0200 Subject: [PATCH 04/33] added ~memegen and ~memelist commands --- .../Searches/Commands/MemegenCommands.cs | 46 +++++++++++++++++++ NadekoBot/Modules/Searches/SearchesModule.cs | 1 + NadekoBot/NadekoBot.csproj | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 NadekoBot/Modules/Searches/Commands/MemegenCommands.cs diff --git a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs new file mode 100644 index 00000000..ebbdbf34 --- /dev/null +++ b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs @@ -0,0 +1,46 @@ +using Discord.Commands; +using NadekoBot.Classes; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace NadekoBot.Modules.Searches.Commands +{ + class MemegenCommands : DiscordCommand + { + public MemegenCommands(DiscordModule module) : base(module) + { + } + + internal override void Init(CommandGroupBuilder cgb) + { + cgb.CreateCommand(Prefix + "memelist") + .Description("Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/") + .Do(async e => + { + int i = 0; + await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + + 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}")))) + + $"\n```").ConfigureAwait(false); + }); + + cgb.CreateCommand(Prefix + "memegen") + .Description("Generates a meme from memelist with top and bottom text. | `~memegen biw \"gets iced coffee\" \"in the winter\"`") + .Parameter("meme", ParameterType.Required) + .Parameter("toptext", ParameterType.Required) + .Parameter("bottext", ParameterType.Required) + .Do(async e => + { + var meme = e.GetArg("meme"); + var top = Uri.EscapeDataString(e.GetArg("toptext").Replace(' ', '-')); + var bot = Uri.EscapeDataString(e.GetArg("bottext").Replace(' ', '-')); + await e.Channel.SendMessage($"http://memegen.link/{meme}/{top}/{bot}.jpg"); + }); + } + } +} diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index e8d57934..11333f09 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -31,6 +31,7 @@ namespace NadekoBot.Modules.Searches commands.Add(new CalcCommand(this)); commands.Add(new OsuCommands(this)); commands.Add(new PokemonSearchCommands(this)); + commands.Add(new MemegenCommands(this)); rng = new Random(); } diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index 3d57402e..a47af49b 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -149,6 +149,7 @@ + @@ -210,7 +211,6 @@ - From 49792fe336fd271620a6e416644259a14f3964b7 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Thu, 30 Jun 2016 00:31:17 +0200 Subject: [PATCH 05/33] Update ComprehensiveGuide.md --- ComprehensiveGuide.md | 47 ------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index 0b1e80ff..a66c3d5f 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -47,50 +47,3 @@ ________________________________________________________________________________ - On the left tab, access Credentials. There will be a line saying "If you wish to skip this step and create an API key, client ID or service account." Click on API Key, and then Server Key in the new window that appears. Enter in a name for the server key. A new window will appear with your Google API key. Copy the key. - Open up credentials.json. For "GoogleAPIKey", fill in with the new key. - Go to (https://soundcloud.com/you/apps/new). Enter a name for the app and create it. You will see a page with the title of your app, and a field labeled Client ID. Copy the ID. In credentials.json, fill in "SoundcloudClientID" with the copied ID. - -________________________________________________________________________________ - -#### Setting Up NadekoBot Permissions -###### NadekoBot's permissions can be set up to be very specific through commands in the Permissions module. -Each command or module can be turned on or off at: -- a user level (so specific users can or cannot use a command/module) -- a role level (so only certain roles have access to certain commands/module) -- a channel level (so certain commands can be limited to certain channels, which can prevent music / trivia / NSFW spam in serious channels) -- a server level. - -Use .modules to see a list of modules (sets of commands). -Use .commands [module_name] to see a list of commands in a certain module. - -Permissions use a semicolon as the prefix, so always start the command with a ;. - -Follow the semicolon with the letter of the level which you want to edit. -- "u" for Users. -- "r" for Roles. -- "c" for Channels. -- "s" for Servers. - -Follow the level with whether you want to edit the permissions of a command or a module. -- "c" for Command. -- "m" for Module. - -Follow with a space and then the command or module name (surround the command with quotation marks if there is a space within the command, for example "!m q" or "!m n"). - -Follow that with another space and, to enable it, type one of the following: [1, true, t, enable], or to disable it, one of the following: [0, false, f, disable]. - -Follow that with another space and the name of the user, role, channel. (depending on the first letter you picked) - -###### Examples -- **;rm NSFW 0 [Role_Name]** Disables the NSFW module for the role, . -- **;cc "!m n" 0 [Channel_Name]** Disables skipping to the next song in the channel, . -- **;uc "!m q" 1 [User_Name]** Enables queuing of songs for the user, . -- **;sm Gambling 0** Disables gambling in the server. - -Check permissions by using the letter of the level you want to check followed by a p, and then the name of the level in which you want to check. If there is no name, it will default to yourself for users, the @everyone role for roles, and the channel in which the command is sent for channels. - -###### Examples -- ;cp [Channel_Name] -- ;rp [Role_Name] - -Insert an **a** before the level to edit the permission for all commands / modules for all users / roles / channels / server. - -Reference the Help command (-h) for more Permissions related commands. From 85ff050061a5cc4de6a56d08e7eddd479e716d47 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Thu, 30 Jun 2016 01:05:37 +0200 Subject: [PATCH 06/33] Soundcloud playlist support (!m scpl) - Better late than never. closes #123 --- .../Modules/Music/Classes/MusicControls.cs | 1 + NadekoBot/Modules/Music/Classes/Song.cs | 8 ++++- NadekoBot/Modules/Music/Classes/SoundCloud.cs | 17 ++++++---- NadekoBot/Modules/Music/MusicModule.cs | 34 +++++++++++++++++-- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index 62eabc33..deb9bc2a 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -174,6 +174,7 @@ namespace NadekoBot.Modules.Music.Classes throw new ArgumentNullException(nameof(s)); lock (playlistLock) { + s.MusicPlayer = this; playlist.Add(s); } } diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/NadekoBot/Modules/Music/Classes/Song.cs index 520ab3d8..f9c7f3ae 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/NadekoBot/Modules/Music/Classes/Song.cs @@ -54,7 +54,7 @@ namespace NadekoBot.Modules.Music.Classes } } - private Song(SongInfo songInfo) + public Song(SongInfo songInfo) { this.SongInfo = songInfo; } @@ -67,6 +67,12 @@ namespace NadekoBot.Modules.Music.Classes return s; } + public Song SetMusicPlayer(MusicPlayer mp) + { + this.MusicPlayer = mp; + return this; + } + private Task BufferSong(CancellationToken cancelToken) => Task.Factory.StartNew(async () => { diff --git a/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/NadekoBot/Modules/Music/Classes/SoundCloud.cs index 00216fbb..159d0d9e 100644 --- a/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/NadekoBot/Modules/Music/Classes/SoundCloud.cs @@ -1,4 +1,5 @@ using NadekoBot.Classes; +using Newtonsoft.Json; using System; using System.Threading.Tasks; @@ -34,18 +35,22 @@ namespace NadekoBot.Modules.Music.Classes public class SoundCloudVideo { - public string Kind = ""; - public long Id = 0; - public SoundCloudUser User = new SoundCloudUser(); - public string Title = ""; + public string Kind { get; set; } = ""; + public long Id { get; set; } = 0; + public SoundCloudUser User { get; set; } = new SoundCloudUser(); + public string Title { get; set; } = ""; + [JsonIgnore] public string FullName => User.Name + " - " + Title; - public bool Streamable = false; + public bool Streamable { get; set; } = false; + [JsonProperty("permalink_url")] + public string TrackLink { get; set; } = ""; + [JsonIgnore] public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}"; } public class SoundCloudUser { [Newtonsoft.Json.JsonProperty("username")] - public string Name; + public string Name { get; set; } } /* {"kind":"track", diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index bf88a5a6..ef482532 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -6,6 +6,7 @@ using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Music.Classes; using NadekoBot.Modules.Permissions.Classes; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -302,6 +303,37 @@ namespace NadekoBot.Modules.Music 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`") + .Parameter("pl", ParameterType.Unparsed) + .Do(async e => + { + var pl = e.GetArg("pl")?.Trim(); + + 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); + + MusicPlayer mp; + if (!MusicPlayers.TryGetValue(e.Server, out mp)) + return; + + foreach (var svideo in scvids.Skip(1)) + { + mp.AddSong(new Song(new Classes.SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + })); + } + }); + cgb.CreateCommand("localplaylst") .Alias("lopl") .Description("Queues all songs from a directory. **Bot Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") @@ -757,8 +789,6 @@ namespace NadekoBot.Modules.Music return mp; }); var resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); - resolvedSong.MusicPlayer = musicPlayer; - musicPlayer.AddSong(resolvedSong); if (!silent) { From 61f0f27a0cd2805c797473803e497a75c69f40f5 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Thu, 30 Jun 2016 04:42:54 +0200 Subject: [PATCH 07/33] removed some unnecessary task.runs --- NadekoBot/Modules/Music/MusicModule.cs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index ef482532..2d0dbd86 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -53,30 +53,24 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand("stop") .Alias("s") .Description("Stops the music and clears the playlist. Stays in the channel.\n**Usage**: `!m s`") - .Do(async e => + .Do(e => { - await Task.Run(() => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - musicPlayer.Stop(); - }).ConfigureAwait(false); + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + musicPlayer.Stop(); }); cgb.CreateCommand("destroy") .Alias("d") .Description("Completely stops the music and unbinds the bot from the channel. " + "(may cause weird behaviour)\n**Usage**: `!m d`") - .Do(async e => + .Do(e => { - await Task.Run(() => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - musicPlayer.Destroy(); - }).ConfigureAwait(false); + MusicPlayer musicPlayer; + if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + musicPlayer.Destroy(); }); cgb.CreateCommand("pause") From 77d468ab5bd2fe9cbc5ee4e8f32f0289ea0d3661 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 1 Jul 2016 16:56:18 +0200 Subject: [PATCH 08/33] fixed ~ir, fixes #371 --- NadekoBot.sln | 16 +++---- NadekoBot/Modules/Searches/SearchesModule.cs | 47 ++++++++++---------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/NadekoBot.sln b/NadekoBot.sln index fd630f8d..096b6db8 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -41,32 +41,32 @@ Global {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {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}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {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}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU {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}.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}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {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}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU {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}.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}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {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}.Release|Any CPU.ActiveCfg = Release|Any CPU {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU {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}.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}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU + {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}.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 diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index 11333f09..8b03984d 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -185,29 +185,30 @@ $@"🌍 **Weather for** 【{obj["target"]}】 cgb.CreateCommand(Prefix + "ir") .Description("Pulls a random image using a search parameter.\n**Usage**: ~ir cute kitten") .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("query"))) - return; - try - { - var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=50&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; - var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - var items = obj["items"] as JArray; - await e.Channel.SendMessage(items[rng.Next(0, items.Count)]["link"].ToString()).ConfigureAwait(false); - } - catch (HttpRequestException exception) - { - if (exception.Message.Contains("403 (Forbidden)")) - { - await e.Channel.SendMessage("Daily limit reached!"); - } - else - { - await e.Channel.SendMessage("Something went wrong."); - } - } - }); + .Do(async e => + { + if (string.IsNullOrWhiteSpace(e.GetArg("query"))) + return; + try + { + var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; + var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); + var items = obj["items"] as JArray; + await e.Channel.SendMessage(items[0]["link"].ToString()).ConfigureAwait(false); + } + catch (HttpRequestException exception) + { + if (exception.Message.Contains("403 (Forbidden)")) + { + await e.Channel.SendMessage("Daily limit reached!"); + } + else + { + await e.Channel.SendMessage("Something went wrong."); + } + } + }); + cgb.CreateCommand(Prefix + "lmgtfy") .Description("Google something for an idiot.") .Parameter("ffs", ParameterType.Unparsed) From 4c5b5348babcfa4fe57508b087bcfb91f62bc48d Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 1 Jul 2016 23:01:11 +0200 Subject: [PATCH 09/33] Added wiki instead of main github page --- NadekoBot/Modules/Help/Commands/HelpCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NadekoBot/Modules/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs index 512799b6..c1157574 100644 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ b/NadekoBot/Modules/Help/Commands/HelpCommand.cs @@ -86,7 +86,7 @@ Version: `{NadekoStats.Instance.BotVersion}`"; .Description("Sends a readme and a guide links to the channel.") .Do(async e => await e.Channel.SendMessage( -@"**FULL README**: +@"**Wiki with all info**: **WINDOWS SETUP GUIDE**: From b41a78f67404cb71df454bc36c16d60cf81a1591 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 1 Jul 2016 23:01:44 +0200 Subject: [PATCH 10/33] abstracted showinprettycode out --- NadekoBot/Classes/SearchHelper.cs | 8 ++++++++ NadekoBot/Modules/Help/HelpModule.cs | 7 +++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/NadekoBot/Classes/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs index 9525ec63..42f70398 100644 --- a/NadekoBot/Classes/SearchHelper.cs +++ b/NadekoBot/Classes/SearchHelper.cs @@ -377,5 +377,13 @@ namespace NadekoBot.Classes return url; } } + + public static string ShowInPrettyCode(IEnumerable items, Func howToPrint, int cols = 3) + { + var i = 0; + return "```xl\n" + string.Join("\n", items.GroupBy(item => (i++) / cols) + .Select(ig => string.Join("", ig.Select(el => howToPrint(el))))) + + $"\n```"; + } } } diff --git a/NadekoBot/Modules/Help/HelpModule.cs b/NadekoBot/Modules/Help/HelpModule.cs index 1b8ad4ec..0b17e336 100644 --- a/NadekoBot/Modules/Help/HelpModule.cs +++ b/NadekoBot/Modules/Help/HelpModule.cs @@ -1,5 +1,6 @@ using Discord.Commands; using Discord.Modules; +using NadekoBot.Classes; using NadekoBot.Classes.Help.Commands; using NadekoBot.Extensions; using NadekoBot.Modules.Permissions.Classes; @@ -54,10 +55,8 @@ namespace NadekoBot.Modules.Help } var i = 0; if (module != "customreactions" && module != "conversations") - await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + - string.Join("\n", cmdsArray.GroupBy(item => (i++) / 3) - .Select(ig => string.Join("", ig.Select(el => $"{el.Text,-15}" + $"{"[" + el.Aliases.FirstOrDefault() + "]",-8}")))) - + $"\n```") + 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}"))); From 59eef96009b1df1f5e262f84898da3380268f530 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 2 Jul 2016 00:11:20 +0200 Subject: [PATCH 11/33] .ecr added (edit custom reaction), .lcr improved, .scr added #367 #368 --- .../Commands/CustomReactionsCommands.cs | 94 +++++++++++++++++-- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index 3809df90..baf177b6 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.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -45,14 +46,88 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "listcustreact") .Alias(Prefix + "lcr") - .Description($"Lists all current custom reactions (paginated with 5 commands per page).\n**Usage**:{Prefix}lcr 1") + .Description($"Lists all current custom reactions (paginated with 30 commands per page).\n**Usage**:{Prefix}lcr 1") .Parameter("num", ParameterType.Required) .Do(async e => { int num; - if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) return; - string result = GetCustomsOnPage(num - 1); //People prefer starting with 1 - await e.Channel.SendMessage(result).ConfigureAwait(false); + if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) num = 1; + var cmds = GetCustomsOnPage(num - 1); + if (!cmds.Any()) + { + await e.Channel.SendMessage(""); + } + else + { + string result = SearchHelper.ShowInPrettyCode(cmds, s => $"{s,-25}"); //People prefer starting with 1 + await e.Channel.SendMessage($"`Showing page {num}:`\n" + result).ConfigureAwait(false); + } + }); + + cgb.CreateCommand(Prefix + "showcustreact") + .Alias(Prefix + "scr") + .Description($"Shows all possible responses from a single custom reaction.\n**Usage**:{Prefix}scr %mention% bb") + .Parameter("name", ParameterType.Unparsed) + .Do(async e => + { + var name = e.GetArg("name")?.Trim(); + if (string.IsNullOrWhiteSpace(name)) + return; + if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) + { + await e.Channel.SendMessage("`Can't find that custom reaction.`").ConfigureAwait(false); + return; + } + var items = NadekoBot.Config.CustomReactions[name]; + var message = new StringBuilder($"Responses for {Format.Bold(name)}:\n"); + var last = items.Last(); + + int i = 1; + foreach (var reaction in items) + { + message.AppendLine($"[{i++}] " + Format.Code(reaction)); + } + await e.Channel.SendMessage(message.ToString()); + }); + + cgb.CreateCommand(Prefix + "editcustreact") + .Alias(Prefix + "ecr") + .Description("Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message | `.ecr \"%mention% disguise\" 2 Test 123`") + .Parameter("name", ParameterType.Required) + .Parameter("index", ParameterType.Required) + .Parameter("message", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) + .Do(async e => + { + var name = e.GetArg("name")?.Trim(); + if (string.IsNullOrWhiteSpace(name)) + return; + var indexstr = e.GetArg("index")?.Trim(); + if (string.IsNullOrWhiteSpace(indexstr)) + return; + var msg = e.GetArg("message")?.Trim(); + if (string.IsNullOrWhiteSpace(msg)) + return; + + + + if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) + { + await e.Channel.SendMessage("`Could not find given commandname`").ConfigureAwait(false); + return; + } + + int index; + if (!int.TryParse(indexstr, out index) || index < 1 || index > NadekoBot.Config.CustomReactions[name].Count) + { + await e.Channel.SendMessage("`Invalid index.`").ConfigureAwait(false); + return; + } + index = index - 1; + NadekoBot.Config.CustomReactions[name][index] = msg; + + await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); + await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "delcustreact") @@ -99,19 +174,21 @@ namespace NadekoBot.Modules.Administration.Commands }); } - private readonly int ItemsPerPage = 5; + private readonly int ItemsPerPage = 30; - private string GetCustomsOnPage(int page) + private IEnumerable GetCustomsOnPage(int page) { var items = NadekoBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage); if (!items.Any()) { - return $"No items on page {page + 1}."; + return Enumerable.Empty(); } + return items.Select(kvp => kvp.Key); + /* var message = new StringBuilder($"--- Custom reactions - page {page + 1} ---\n"); foreach (var cr in items) { - message.Append($"{ Format.Code(cr.Key)}\n"); + message.Append($"{Format.Code(cr.Key)}\n"); int i = 1; var last = cr.Value.Last(); foreach (var reaction in cr.Value) @@ -123,6 +200,7 @@ namespace NadekoBot.Modules.Administration.Commands } } return message.ToString() + "\n"; + */ } } } From dd349376a22b85ce49ca3c1087e834acf9a45c27 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 2 Jul 2016 02:20:33 +0200 Subject: [PATCH 12/33] .savechat added, added .ToStream extension for strings --- NadekoBot/Classes/Extensions.cs | 14 +++++++-- .../Administration/AdministrationModule.cs | 30 +++++++++++++++++++ .../Commands/CustomReactionsCommands.cs | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs index 93527acd..1b162209 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/NadekoBot/Classes/Extensions.cs @@ -215,7 +215,7 @@ namespace NadekoBot.Extensions sb.Append($"{ Format.Code(item.Key)}\n"); sb.AppendLine(" `└─`" + Format.Bold(item.Value)); } - + } else { @@ -224,7 +224,7 @@ namespace NadekoBot.Extensions sb.Append($"{ Format.Code(item.ToString())} \n"); } } - + return sb.ToString(); } /// @@ -352,5 +352,15 @@ namespace NadekoBot.Extensions await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false); public static string Unmention(this string str) => str.Replace("@", "ම"); + + public static Stream ToStream(this string str) + { + var sw = new StreamWriter(new MemoryStream()); + sw.Write(str); + sw.Flush(); + sw.BaseStream.Position = 0; + return sw.BaseStream; + } + } } diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index 8689ea4a..8095ad0a 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -6,8 +6,11 @@ using NadekoBot.DataModels; using NadekoBot.Extensions; using NadekoBot.Modules.Administration.Commands; using NadekoBot.Modules.Permissions.Classes; +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; namespace NadekoBot.Modules.Administration @@ -902,6 +905,33 @@ namespace NadekoBot.Modules.Administration 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) + .AddCheck(SimpleCheckers.OwnerOnly()) + .Do(async e => + { + var cntstr = e.GetArg("cnt")?.Trim(); + int cnt; + if (!int.TryParse(cntstr, out cnt)) + return; + ulong? lastmsgId = null; + var sb = new StringBuilder(); + var msgs = new List(cnt); + while (cnt > 0) + { + var dlcnt = cnt < 100 ? cnt : 100; + + var dledMsgs = await e.Channel.DownloadMessages(dlcnt, lastmsgId); + if (!dledMsgs.Any()) + break; + msgs.AddRange(dledMsgs); + 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()); + }); + }); } } diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index baf177b6..4aba4b10 100644 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "editcustreact") .Alias(Prefix + "ecr") - .Description("Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message | `.ecr \"%mention% disguise\" 2 Test 123`") + .Description("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`") .Parameter("name", ParameterType.Required) .Parameter("index", ParameterType.Required) .Parameter("message", ParameterType.Unparsed) From fe528f37ccd3b2621f1d5ece2a9b5492f3cdbda2 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 2 Jul 2016 10:40:49 +0200 Subject: [PATCH 13/33] added ~checkbeam, ~checktwitch and ~checkhitbox, for instant check if some streamer is online. --- .../Searches/Commands/StreamNotifications.cs | 95 ++++++++++++++++++- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs index ffa38e5c..5895c932 100644 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs @@ -73,7 +73,7 @@ namespace NadekoBot.Modules.Searches.Commands checkTimer.Start(); } - private async Task> GetStreamStatus(StreamNotificationConfig stream) + private async Task> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true) { bool isLive; string response; @@ -83,7 +83,7 @@ namespace NadekoBot.Modules.Searches.Commands { case StreamNotificationConfig.StreamType.Hitbox: var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}"; - if (cachedStatuses.TryGetValue(hitboxUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Searches.Commands return result; case StreamNotificationConfig.StreamType.Twitch: var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}"; - if (cachedStatuses.TryGetValue(twitchUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Searches.Commands return result; case StreamNotificationConfig.StreamType.Beam: var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}"; - if (cachedStatuses.TryGetValue(beamUrl, out result)) + if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result)) return result; response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false); data = JObject.Parse(response); @@ -143,6 +143,93 @@ namespace NadekoBot.Modules.Searches.Commands .Parameter("username", ParameterType.Unparsed) .Do(TrackStream(StreamNotificationConfig.StreamType.Beam)); + 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") + .Parameter("username", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Hitbox + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + + 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") + .AddCheck(SimpleCheckers.ManageServer()) + .Parameter("username", ParameterType.Unparsed) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Twitch + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + + 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") + .AddCheck(SimpleCheckers.ManageServer()) + .Parameter("username", ParameterType.Unparsed) + .Do(async e => + { + var stream = e.GetArg("username")?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new StreamNotificationConfig + { + Username = stream, + Type = StreamNotificationConfig.StreamType.Beam + })); + if (streamStatus.Item1) + { + await e.Channel.SendMessage($"`Streamer {streamStatus.Item2} is online.`"); + } + } + catch + { + await e.Channel.SendMessage("No channel found."); + } + }); + cgb.CreateCommand(Module.Prefix + "removestream") .Alias(Module.Prefix + "rms") .Description("Removes notifications of a certain streamer on this channel." + From 9fec22a307e4cd73a1232955e50e38594e797817 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 2 Jul 2016 22:38:07 +0200 Subject: [PATCH 14/33] fixed playing rotate , thx baron von chukkelchode --- NadekoBot/Modules/Administration/Commands/PlayingRotate.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs index e3589342..aa7570cb 100644 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs @@ -51,11 +51,9 @@ namespace NadekoBot.Modules.Administration.Commands { if (PlayingPlaceholders.Count == 0 || NadekoBot.Config.RotatingStatuses.Count == 0 - || i >= PlayingPlaceholders.Count || i >= NadekoBot.Config.RotatingStatuses.Count) { - i = -1; - return; + i = 0; } status = NadekoBot.Config.RotatingStatuses[i]; status = PlayingPlaceholders.Aggregate(status, From 1b3b65817e923c14eb57e87964e72466c611e664 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 2 Jul 2016 22:42:45 +0200 Subject: [PATCH 15/33] drawing 5 cards now mentions the person who invoked the command, thx Pajdo Warrior --- NadekoBot/Modules/Gambling/DrawCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NadekoBot/Modules/Gambling/DrawCommand.cs b/NadekoBot/Modules/Gambling/DrawCommand.cs index 2d3116ee..0f7d92e9 100644 --- a/NadekoBot/Modules/Gambling/DrawCommand.cs +++ b/NadekoBot/Modules/Gambling/DrawCommand.cs @@ -79,7 +79,7 @@ namespace NadekoBot.Modules.Gambling await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false); if (cardObjects.Count == 5) { - await e.Channel.SendMessage(Cards.GetHandValue(cardObjects)).ConfigureAwait(false); + await e.Channel.SendMessage($"{e.User.Mention} `{Cards.GetHandValue(cardObjects)}`").ConfigureAwait(false); } } catch (Exception ex) From 947472a77bc6daa1579c97fffed5ca4e3025715b Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 3 Jul 2016 00:40:06 +0200 Subject: [PATCH 16/33] Currency generation added (>gc) command. --- NadekoBot/Classes/ServerSpecificConfig.cs | 17 ++++++ NadekoBot/Modules/Games/Commands/PlantPick.cs | 57 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index e7e7437e..b8c2cc9d 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/NadekoBot/Classes/ServerSpecificConfig.cs @@ -108,6 +108,22 @@ namespace NadekoBot.Classes } } + [JsonIgnore] + private ObservableCollection generateCurrencyChannels; + + public ObservableCollection GenerateCurrencyChannels { + get { return generateCurrencyChannels; } + set { + generateCurrencyChannels = value; + if (value != null) + generateCurrencyChannels.CollectionChanged += (s, e) => + { + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + }; + } + } + [JsonIgnore] private ObservableCollection observingStreams; @@ -150,6 +166,7 @@ namespace NadekoBot.Classes { ListOfSelfAssignableRoles = new ObservableCollection(); ObservingStreams = new ObservableCollection(); + GenerateCurrencyChannels = new ObservableCollection(); } public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs index 86db0d1f..cba74cc7 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/NadekoBot/Modules/Games/Commands/PlantPick.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; namespace NadekoBot.Modules.Games.Commands @@ -18,11 +19,31 @@ namespace NadekoBot.Modules.Games.Commands /// class PlantPick : DiscordCommand { + + private Random rng; public PlantPick(DiscordModule module) : base(module) { - + NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; + rng = new Random(); } + + 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)) + { + 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; }); + } + } + } //channelid/messageid pair ConcurrentDictionary plantedFlowerChannels = new ConcurrentDictionary(); @@ -65,8 +86,7 @@ namespace NadekoBot.Modules.Games.Commands return; } - var rng = new Random(); - var file = Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + var file = GetRandomCurrencyImagePath(); Message msg; //todo send message after, not in lock if (file == null) @@ -80,6 +100,37 @@ namespace NadekoBot.Modules.Games.Commands 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}. | `gc`") + .Do(async e => + { + var config = SpecificConfigurations.Default.Of(e.Server.Id); + if (config.GenerateCurrencyChannels.Remove(e.Channel.Id)) + { + 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.`"); + } + }); + } + + private string GetRandomCurrencyImagePath() => + Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + + int GetRandomNumber() + { + using (RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider()) + { + byte[] rno = new byte[4]; + rg.GetBytes(rno); + int randomvalue = BitConverter.ToInt32(rno, 0); + return randomvalue; + } } } } From a39f542cad1b1a616e010a2fa9f79648b62a23d0 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 3 Jul 2016 00:48:55 +0200 Subject: [PATCH 17/33] Added manage messages requirement for >gc --- NadekoBot/Modules/Games/Commands/PlantPick.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs index cba74cc7..cbc4b01a 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/NadekoBot/Modules/Games/Commands/PlantPick.cs @@ -1,6 +1,7 @@ using Discord; using Discord.Commands; using NadekoBot.Classes; +using NadekoBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; using System.IO; @@ -103,7 +104,8 @@ namespace NadekoBot.Modules.Games.Commands 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}. | `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`") + .AddCheck(SimpleCheckers.ManageMessages()) .Do(async e => { var config = SpecificConfigurations.Default.Of(e.Server.Id); From 29daf48e4fb036eadd0e3c6b45e15651c72f401c Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 3 Jul 2016 05:26:14 +0200 Subject: [PATCH 18/33] converted logserver, userpresences and voicechannellog to use Ids instead of objects. (step 1 for persisting restarts) --- .../Administration/Commands/LogCommand.cs | 108 +++++++++++++----- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 7b7c1a48..41bbdcd5 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -13,9 +13,12 @@ namespace NadekoBot.Modules.Administration.Commands internal class LogCommand : DiscordCommand { - private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); - private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); - private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); + //server-channel + private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); + //server-channel + private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); + //channel-channel + private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; @@ -58,8 +61,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (e.Before.Name != e.After.Name) await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Name Changed** `#{e.Before.Name}` (*{e.After.Id}*) @@ -76,8 +82,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"❗`{prettyCurrentTime}`❗`Channel Deleted:` #{e.Channel.Name} (*{e.Channel.Id}*)").ConfigureAwait(false); } @@ -88,8 +97,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`🆕`Channel Created:` #{e.Channel.Mention} (*{e.Channel.Id}*)").ConfigureAwait(false); } @@ -100,8 +112,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`♻`User was unbanned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -112,8 +127,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`✅`User joined:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -124,8 +142,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"`{prettyCurrentTime}`❗`User left:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -136,8 +157,11 @@ namespace NadekoBot.Modules.Administration.Commands { try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); } @@ -146,14 +170,16 @@ namespace NadekoBot.Modules.Administration.Commands public Func DoFunc() => async e => { - Channel ch; - if (!logs.TryRemove(e.Server, out ch)) + ulong chId; + if (!logs.TryRemove(e.Server.Id, out chId)) { - logs.TryAdd(e.Server, e.Channel); + logs.TryAdd(e.Server.Id, e.Channel.Id); await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); return; } - + Channel ch; + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) + return; await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); }; @@ -163,8 +189,11 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (!string.IsNullOrWhiteSpace(e.Message.Text)) { @@ -188,8 +217,11 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; if (!string.IsNullOrWhiteSpace(e.Message.Text)) { @@ -212,8 +244,11 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; await ch.SendMessage( $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` @@ -227,17 +262,25 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` { try { - Channel ch; - if (loggingPresences.TryGetValue(e.Server, out ch)) - if (e.Before.Status != e.After.Status) + ulong chId; + if (!loggingPresences.TryGetValue(e.Server.Id, out chId)) + { + Channel ch; + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null) { - await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); + if (e.Before.Status != e.After.Status) + { + await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); + } } + } } catch { } try { + ulong notifyChBeforeId; + ulong notifyChAfterId; Channel notifyChBefore = null; Channel notifyChAfter = null; var beforeVch = e.Before.VoiceChannel; @@ -246,11 +289,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` var notifyJoin = false; if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure. { - if (beforeVch != null && voiceChannelLog.TryGetValue(beforeVch, out notifyChBefore)) + if (beforeVch != null && voiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) { notifyLeave = true; } - if (afterVch != null && voiceChannelLog.TryGetValue(afterVch, out notifyChAfter)) + if (afterVch != null && voiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) { notifyJoin = true; } @@ -272,8 +315,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` try { + ulong chId; + if (!logs.TryGetValue(e.Server.Id, out chId)) + return; Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; string str = $"🕔`{prettyCurrentTime}`"; if (e.Before.Name != e.After.Name) @@ -338,10 +384,10 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { - Channel ch; - if (!loggingPresences.TryRemove(e.Server, out ch)) + ulong chId; + if (!loggingPresences.TryRemove(e.Server.Id, out chId)) { - loggingPresences.TryAdd(e.Server, e.Channel); + loggingPresences.TryAdd(e.Server.Id, e.Channel.Id); await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } @@ -360,7 +406,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` { foreach (var voiceChannel in e.Server.VoiceChannels) { - voiceChannelLog.TryAdd(voiceChannel, e.Channel); + voiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); } await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false); return; @@ -371,10 +417,10 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` await e.Channel.SendMessage("💢 You are not in a voice channel right now. If you are, please rejoin it.").ConfigureAwait(false); return; } - Channel throwaway; - if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway)) + ulong throwaway; + if (!voiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) { - voiceChannelLog.TryAdd(e.User.VoiceChannel, e.Channel); + voiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); } else From b30a8041232115c98a9b233b444e9a363e302e30 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 3 Jul 2016 22:30:35 +0200 Subject: [PATCH 19/33] Fixed translate --- .../Translator/Helpers/GoogleTranslator.cs | 102 ++---------------- .../Modules/Translator/TranslateCommand.cs | 10 +- 2 files changed, 18 insertions(+), 94 deletions(-) diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs index 34ea9112..f9687033 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs @@ -2,6 +2,7 @@ // License: Code Project Open License // http://www.codeproject.com/info/cpol10.aspx +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; @@ -27,23 +28,6 @@ namespace NadekoBot.Modules.Translator.Helpers } } - /// - /// Gets the time taken to perform the translation. - /// - public TimeSpan TranslationTime { - get; - private set; - } - - /// - /// Gets the url used to speak the translation. - /// - /// The url used to speak the translation. - public string TranslationSpeechUrl { - get; - private set; - } - /// /// Gets the error. /// @@ -69,86 +53,22 @@ namespace NadekoBot.Modules.Translator.Helpers string targetLanguage) { // Initialize - this.Error = null; - this.TranslationSpeechUrl = null; - this.TranslationTime = TimeSpan.Zero; DateTime tmStart = DateTime.Now; - string translation = string.Empty; string text = string.Empty; - try + + // Download translation + string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", + GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), + GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), + HttpUtility.UrlEncode(sourceText)); + using (WebClient wc = new WebClient()) { - // Download translation - string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", - GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), - GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), - HttpUtility.UrlEncode(sourceText)); - using (WebClient wc = new WebClient()) - { - wc.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); - text = wc.DownloadString(url); - } - - // Get translated text - // Get phrase collection - // string text = File.ReadAllText(outputFile); - int index = text.IndexOf(string.Format(",,\"{0}\"", GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage))); - if (index == -1) - { - // Translation of single word - int startQuote = text.IndexOf('\"'); - if (startQuote != -1) - { - int endQuote = text.IndexOf('\"', startQuote + 1); - if (endQuote != -1) - { - translation = text.Substring(startQuote + 1, endQuote - startQuote - 1); - } - } - else - { - // Translation of phrase - text = text.Substring(0, index); - text = text.Replace("],[", ","); - text = text.Replace("]", string.Empty); - text = text.Replace("[", string.Empty); - text = text.Replace("\",\"", "\""); - - // Get translated phrases - string[] phrases = text.Split(new[] { '\"' }, StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; (i < phrases.Count()); i += 2) - { - string translatedPhrase = phrases[i]; - if (translatedPhrase.StartsWith(",,")) - { - i--; - continue; - } - translation += translatedPhrase + " "; - } - } - - // Fix up translation - translation = translation.Trim(); - translation = translation.Replace(" ?", "?"); - translation = translation.Replace(" !", "!"); - translation = translation.Replace(" ,", ","); - translation = translation.Replace(" .", "."); - translation = translation.Replace(" ;", ";"); - - // And translation speech URL - this.TranslationSpeechUrl = string.Format("https://translate.googleapis.com/translate_tts?ie=UTF-8&q={0}&tl={1}&total=1&idx=0&textlen={2}&client=gtx", - HttpUtility.UrlEncode(translation), GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), translation.Length); - } - } - catch (Exception ex) - { - this.Error = ex; + wc.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); + text = wc.DownloadString(url); } - // Return result - this.TranslationTime = DateTime.Now - tmStart; - return translation; + return JArray.Parse(text)[0][0][0].ToString(); } #endregion diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index 29ac4036..f5b70d79 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/NadekoBot/Modules/Translator/TranslateCommand.cs @@ -27,13 +27,17 @@ namespace NadekoBot.Modules.Translator await e.Channel.SendIsTyping().ConfigureAwait(false); string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0]; string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1]; + var text = e.GetArg("text")?.Trim(); + if (string.IsNullOrWhiteSpace(text)) + return; - string translation = t.Translate(e.GetArg("text"), from, to); + string translation = t.Translate(text, from, to); await e.Channel.SendMessage(translation).ConfigureAwait(false); } - catch + catch (Exception ex) { - await e.Channel.SendMessage("Bad input format, or sth went wrong...").ConfigureAwait(false); + Console.WriteLine(ex); + await e.Channel.SendMessage("Bad input format, or something went wrong...").ConfigureAwait(false); } }; From 1b96e520e7edfd48b4a18920df79b64c60165dec Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 3 Jul 2016 22:44:13 +0200 Subject: [PATCH 20/33] plz no webclient in 2016 --- .../Modules/Translator/Helpers/GoogleTranslator.cs | 11 ++++++----- NadekoBot/Modules/Translator/TranslateCommand.cs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs index f9687033..a855b4d8 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs @@ -6,7 +6,8 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; -using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using System.Web; namespace NadekoBot.Modules.Translator.Helpers @@ -47,7 +48,7 @@ namespace NadekoBot.Modules.Translator.Helpers /// The source language. /// The target language. /// The translation. - public string Translate + public async Task Translate (string sourceText, string sourceLanguage, string targetLanguage) @@ -62,10 +63,10 @@ namespace NadekoBot.Modules.Translator.Helpers GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), HttpUtility.UrlEncode(sourceText)); - using (WebClient wc = new WebClient()) + using (HttpClient http = new HttpClient()) { - wc.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); - text = wc.DownloadString(url); + http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); + text = await http.GetStringAsync(url); } return JArray.Parse(text)[0][0][0].ToString(); diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index f5b70d79..5c43b8b9 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/NadekoBot/Modules/Translator/TranslateCommand.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Translator if (string.IsNullOrWhiteSpace(text)) return; - string translation = t.Translate(text, from, to); + string translation = await t.Translate(text, from, to); await e.Channel.SendMessage(translation).ConfigureAwait(false); } catch (Exception ex) From 7695112232f9a10073093a529c1e472400425aad Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Mon, 4 Jul 2016 08:46:26 +0200 Subject: [PATCH 21/33] random stuff --- .../lib/ScaredFingers.UnitsConversion.dll | Bin 0 -> 32768 bytes .../lib/ScaredFingers.UnitsConversion.pdb | Bin 0 -> 79360 bytes .../Translator/Helpers/GoogleTranslator.cs | 11 +---------- .../Modules/Translator/TranslateCommand.cs | 2 +- NadekoBot/NadekoBot.csproj | 3 ++- 5 files changed, 4 insertions(+), 12 deletions(-) create mode 100644 NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll create mode 100644 NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb diff --git a/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll new file mode 100644 index 0000000000000000000000000000000000000000..8b1218117d7450c617d8d8bd2b094b529eda113e GIT binary patch literal 32768 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�jYm1P|5f+{k| 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 p); } } - - /// - /// Gets the error. - /// - public Exception Error { - get; - private set; - } - #endregion #region Public methods @@ -66,7 +57,7 @@ namespace NadekoBot.Modules.Translator.Helpers using (HttpClient http = new HttpClient()) { http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); - text = await http.GetStringAsync(url); + text = await http.GetStringAsync(url).ConfigureAwait(false); } return JArray.Parse(text)[0][0][0].ToString(); diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs index 5c43b8b9..e4512980 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/NadekoBot/Modules/Translator/TranslateCommand.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Translator if (string.IsNullOrWhiteSpace(text)) return; - string translation = await t.Translate(text, from, to); + string translation = await t.Translate(text, from, to).ConfigureAwait(false); await e.Channel.SendMessage(translation).ConfigureAwait(false); } catch (Exception ex) diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index a47af49b..84efa690 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -134,6 +134,7 @@ + @@ -496,7 +497,7 @@ - + From 6d641431abc27377f6e2ad74c5bf213e7154b8a0 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Mon, 4 Jul 2016 08:47:29 +0200 Subject: [PATCH 22/33] voicepresence, logserver and userpresence persist restarts now. ObservableConcurrentDictionary implementation added. #104 --- .../Classes/ObservableConcurrentDictionary.cs | 204 ++++++++++++++++++ NadekoBot/Classes/ServerSpecificConfig.cs | 47 +++- .../Administration/Commands/LogCommand.cs | 106 +++++---- NadekoBot/Modules/Music/MusicModule.cs | 15 ++ .../lib/ScaredFingers.UnitsConversion.dll | Bin 32768 -> 0 bytes .../lib/ScaredFingers.UnitsConversion.pdb | Bin 79360 -> 0 bytes 6 files changed, 310 insertions(+), 62 deletions(-) create mode 100644 NadekoBot/Classes/ObservableConcurrentDictionary.cs delete mode 100644 NadekoBot/lib/ScaredFingers.UnitsConversion.dll delete mode 100644 NadekoBot/lib/ScaredFingers.UnitsConversion.pdb diff --git a/NadekoBot/Classes/ObservableConcurrentDictionary.cs b/NadekoBot/Classes/ObservableConcurrentDictionary.cs new file mode 100644 index 00000000..cd364328 --- /dev/null +++ b/NadekoBot/Classes/ObservableConcurrentDictionary.cs @@ -0,0 +1,204 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: ObservableConcurrentDictionary.cs +// +//-------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace System.Collections.Concurrent +{ + /// + /// Provides a thread-safe dictionary for use with data binding. + /// + /// Specifies the type of the keys in this collection. + /// Specifies the type of the values in this collection. + [DebuggerDisplay("Count={Count}")] + public class ObservableConcurrentDictionary : + ICollection>, IDictionary, + INotifyCollectionChanged, INotifyPropertyChanged + { + private readonly SynchronizationContext _context; + private readonly ConcurrentDictionary _dictionary; + + /// + /// Initializes an instance of the ObservableConcurrentDictionary class. + /// + public ObservableConcurrentDictionary() + { + _context = AsyncOperationManager.SynchronizationContext; + _dictionary = new ConcurrentDictionary(); + } + + /// Event raised when the collection changes. + public event NotifyCollectionChangedEventHandler CollectionChanged; + /// Event raised when a property on the collection changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary. + /// + private void NotifyObserversOfChange() + { + var collectionHandler = CollectionChanged; + var propertyHandler = PropertyChanged; + if (collectionHandler != null || propertyHandler != null) + { + _context.Post(s => + { + if (collectionHandler != null) + { + collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + if (propertyHandler != null) + { + propertyHandler(this, new PropertyChangedEventArgs("Count")); + propertyHandler(this, new PropertyChangedEventArgs("Keys")); + propertyHandler(this, new PropertyChangedEventArgs("Values")); + } + }, null); + } + } + + /// Attempts to add an item to the dictionary, notifying observers of any changes. + /// The item to be added. + /// Whether the add was successful. + private bool TryAddWithNotification(KeyValuePair item) + { + return TryAddWithNotification(item.Key, item.Value); + } + + /// Attempts to add an item to the dictionary, notifying observers of any changes. + /// The key of the item to be added. + /// The value of the item to be added. + /// Whether the add was successful. + private bool TryAddWithNotification(TKey key, TValue value) + { + bool result = _dictionary.TryAdd(key, value); + if (result) NotifyObserversOfChange(); + return result; + } + + /// Attempts to remove an item from the dictionary, notifying observers of any changes. + /// The key of the item to be removed. + /// The value of the item removed. + /// Whether the removal was successful. + private bool TryRemoveWithNotification(TKey key, out TValue value) + { + bool result = _dictionary.TryRemove(key, out value); + if (result) NotifyObserversOfChange(); + return result; + } + + /// Attempts to add or update an item in the dictionary, notifying observers of any changes. + /// The key of the item to be updated. + /// The new value to set for the item. + /// Whether the update was successful. + private void UpdateWithNotification(TKey key, TValue value) + { + _dictionary[key] = value; + NotifyObserversOfChange(); + } + + #region ICollection> Members + void ICollection>.Add(KeyValuePair item) + { + TryAddWithNotification(item); + } + + void ICollection>.Clear() + { + ((ICollection>)_dictionary).Clear(); + NotifyObserversOfChange(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_dictionary).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_dictionary).CopyTo(array, arrayIndex); + } + + int ICollection>.Count { + get { return ((ICollection>)_dictionary).Count; } + } + + bool ICollection>.IsReadOnly { + get { return ((ICollection>)_dictionary).IsReadOnly; } + } + + bool ICollection>.Remove(KeyValuePair item) + { + TValue temp; + return TryRemoveWithNotification(item.Key, out temp); + } + #endregion + + #region IEnumerable> Members + IEnumerator> IEnumerable>.GetEnumerator() + { + return ((ICollection>)_dictionary).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((ICollection>)_dictionary).GetEnumerator(); + } + #endregion + + #region IDictionary Members + public void Add(TKey key, TValue value) + { + TryAddWithNotification(key, value); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection Keys { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + TValue temp; + return TryRemoveWithNotification(key, out temp); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return _dictionary.TryGetValue(key, out value); + } + + public bool TryAdd(TKey key, TValue value) + { + return TryAddWithNotification(key, value); + } + + public ICollection Values { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] { + get { return _dictionary[key]; } + set { UpdateWithNotification(key, value); } + } + + public bool TryRemove(TKey key, out TValue value) + { + return TryRemoveWithNotification(key, out value); + } + #endregion + } +} \ No newline at end of file diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs index b8c2cc9d..bbc4eadd 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/NadekoBot/Classes/ServerSpecificConfig.cs @@ -82,6 +82,45 @@ namespace NadekoBot.Classes } } + [JsonProperty("LogChannel")] + private ulong? logServerChannel = null; + [JsonIgnore] + public ulong? LogServerChannel { + get { return logServerChannel; } + set { + logServerChannel = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + + [JsonProperty("LogPresenceChannel")] + private ulong? logPresenceChannel = null; + [JsonIgnore] + public ulong? LogPresenceChannel { + get { return logPresenceChannel; } + set { + logPresenceChannel = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + + [JsonIgnore] + private ObservableConcurrentDictionary voiceChannelLog; + public ObservableConcurrentDictionary VoiceChannelLog { + get { return voiceChannelLog; } + set { + voiceChannelLog = value; + if (value != null) + voiceChannelLog.CollectionChanged += (s, e) => + { + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + }; + } + } + [JsonIgnore] private ObservableCollection listOfSelfAssignableRoles; public ObservableCollection ListOfSelfAssignableRoles { @@ -110,7 +149,6 @@ namespace NadekoBot.Classes [JsonIgnore] private ObservableCollection generateCurrencyChannels; - public ObservableCollection GenerateCurrencyChannels { get { return generateCurrencyChannels; } set { @@ -124,9 +162,6 @@ namespace NadekoBot.Classes } } - [JsonIgnore] - private ObservableCollection observingStreams; - [JsonIgnore] private bool autoDeleteMessagesOnCommand = false; public bool AutoDeleteMessagesOnCommand { @@ -138,6 +173,9 @@ namespace NadekoBot.Classes } } + + [JsonIgnore] + private ObservableCollection observingStreams; public ObservableCollection ObservingStreams { get { return observingStreams; } set { @@ -167,6 +205,7 @@ namespace NadekoBot.Classes ListOfSelfAssignableRoles = new ObservableCollection(); ObservingStreams = new ObservableCollection(); GenerateCurrencyChannels = new ObservableCollection(); + VoiceChannelLog = new ObservableConcurrentDictionary(); } public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 41bbdcd5..3cd92485 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -4,22 +4,12 @@ using NadekoBot.Classes; using NadekoBot.Extensions; using NadekoBot.Modules.Permissions.Classes; using System; -using System.Collections.Concurrent; using System.Linq; -using System.Threading.Tasks; namespace NadekoBot.Modules.Administration.Commands { internal class LogCommand : DiscordCommand { - - //server-channel - private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); - //server-channel - private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); - //channel-channel - private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); - private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; public LogCommand(DiscordModule module) : base(module) @@ -61,8 +51,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -82,8 +72,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -97,8 +87,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -112,8 +102,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -127,8 +117,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -142,8 +132,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -157,8 +147,8 @@ namespace NadekoBot.Modules.Administration.Commands { try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -168,29 +158,14 @@ namespace NadekoBot.Modules.Administration.Commands catch { } } - public Func DoFunc() => async e => - { - ulong chId; - if (!logs.TryRemove(e.Server.Id, out chId)) - { - logs.TryAdd(e.Server.Id, e.Channel.Id); - await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); - return; - } - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); - }; - private async void MsgRecivd(object sender, MessageEventArgs e) { try { if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -217,8 +192,8 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -244,8 +219,8 @@ namespace NadekoBot.Modules.Administration.Commands { if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) return; - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null || e.Channel.Id == chId) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -260,10 +235,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` } private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) { + var config = SpecificConfigurations.Default.Of(e.Server.Id); try { - ulong chId; - if (!loggingPresences.TryGetValue(e.Server.Id, out chId)) + var chId = config.LogServerChannel; + if (chId != null) { Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null) @@ -289,11 +265,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` var notifyJoin = false; if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure. { - if (beforeVch != null && voiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) + if (beforeVch != null && config.VoiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) { notifyLeave = true; } - if (afterVch != null && voiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) + if (afterVch != null && config.VoiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) { notifyJoin = true; } @@ -315,8 +291,8 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` try { - ulong chId; - if (!logs.TryGetValue(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) return; Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) @@ -377,17 +353,30 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .Description("Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!**") .AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.ManageServer()) - .Do(DoFunc()); + .Do(async e => + { + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) + { + SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; + await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); + return; + } + Channel ch; + if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) + return; + await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); + }); cgb.CreateCommand(Module.Prefix + "userpresence") .Description("Starts logging to this channel when someone from the server goes online/offline/idle.") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { - ulong chId; - if (!loggingPresences.TryRemove(e.Server.Id, out chId)) + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + if (chId == null) { - loggingPresences.TryAdd(e.Server.Id, e.Channel.Id); + SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } @@ -402,11 +391,12 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .Do(async e => { + var config = SpecificConfigurations.Default.Of(e.Server.Id); if (e.GetArg("all")?.ToLower() == "all") { foreach (var voiceChannel in e.Server.VoiceChannels) { - voiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); + config.VoiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); } await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false); return; @@ -418,9 +408,9 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` return; } ulong throwaway; - if (!voiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) + if (!config.VoiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) { - voiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); + config.VoiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); } else diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index 2d0dbd86..f2e2071a 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -105,6 +105,21 @@ 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("listqueue") .Alias("lq") .Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`") diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/lib/ScaredFingers.UnitsConversion.dll deleted file mode 100644 index 8b1218117d7450c617d8d8bd2b094b529eda113e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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�jYm1P|5f+{k| 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 Date: Mon, 4 Jul 2016 09:16:01 +0200 Subject: [PATCH 23/33] fixed osu desc, fixed userpresence disable --- NadekoBot/Modules/Administration/Commands/LogCommand.cs | 2 +- NadekoBot/Modules/Searches/Commands/OsuCommands.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 3cd92485..8eac1b93 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -380,7 +380,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } - + SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = null; await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false); }); diff --git a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs index a9d92d9f..8af28d0f 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 `") + .Description("Shows osu stats for a player.\n**Usage**: `~osu Name` or `~osu Name taiko`") .Parameter("usr", ParameterType.Required) .Parameter("mode", ParameterType.Unparsed) .Do(async e => From 92f4ba260bd7307a0dff2e4569f41f03a4be388b Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Mon, 4 Jul 2016 09:16:27 +0200 Subject: [PATCH 24/33] Commandlist updated for 0.99.8 --- commandlist.md | 129 +++++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/commandlist.md b/commandlist.md index 3c581b68..4e449351 100644 --- a/commandlist.md +++ b/commandlist.md @@ -2,7 +2,7 @@ ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6015.37609` +Version: `NadekoBot v0.9.6029.16666` ### Help Command and aliases | Description | Usage ----------------|--------------|------- @@ -44,16 +44,16 @@ Command and aliases | Description | Usage `.lsar` | Lists all self-assignable roles. `.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 -`.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!** -`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server -`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel -`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser `.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 5 commands per page). | .lcr 1 +`.listcustreact`, `.lcr` | Lists all current custom reactions (paginated with 30 commands per page). | .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` `.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` +`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. +`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. +`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. `.restart` | Restarts the bot. Might not work. `.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest `.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin @@ -61,7 +61,6 @@ Command and aliases | Description | Usage `.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User `.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role` `.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55` -`.roles` | List all roles on this server or a single user if specified. `.ban`, `.b` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic. `.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | .sb "@some Guy" Your behaviour is toxic. `.kick`, `.k` | Kicks a mentioned user. @@ -75,30 +74,38 @@ Command and aliases | Description | Usage `.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. `.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` `.setchanlname`, `.schn` | Changed the name of the current channel. -`.userid`, `.uid` | Shows user ID. -`.channelid`, `.cid` | Shows current channel ID. -`.serverid`, `.sid` | Shows current server ID. -`.stats` | Shows some basic stats for Nadeko. -`.dysyd` | Shows some basic stats for Nadeko. `.heap` | Shows allocated memory - **Bot Owner Only!** `.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** `.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** -`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. **Bot Owner Only!** +`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg` `.setgame` | Sets the bots game. **Bot Owner Only!** -`.checkmyperms` | Checks your userspecific permissions on this channel. -`.commsuser` | Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Bot Owner Only!** -`.commsserver` | Sets a server for through-bot communication. **Bot Owner Only!** -`.commschannel` | Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Bot Owner Only!** -`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | .send Message text multi word! +`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!` `.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. -`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. `.unstuck` | Clears the message queue. **Bot Owner Only!** `.donators` | List of lovely people who donated to keep this project alive. `.donadd` | Add a donator to the database. `.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | .announce Useless spam -`.whoplays` | Shows a list of users who are playing the specified game. `.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` + +### Utility +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!** +`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server +`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel +`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser +`.whoplays` | Shows a list of users who are playing the specified game. +`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. +`.checkmyperms` | Checks your userspecific permissions on this channel. +`.stats` | Shows some basic stats for Nadeko. +`.dysyd` | Shows some basic stats for Nadeko. +`.userid`, `.uid` | Shows user ID. +`.channelid`, `.cid` | Shows current channel ID. +`.serverid`, `.sid` | Shows current server ID. +`.roles` | List all roles on this server or a single user if specified. ### Permissions Command and aliases | Description | Usage @@ -145,8 +152,6 @@ 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 copyme`, `@BotName cm` | Nadeko starts copying everything you say. Disable with cs -`@BotName cs`, `@BotName copystop` | Nadeko stops copying you `@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. @@ -156,7 +161,6 @@ Command and aliases | Description | Usage `@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' -`@BotName av`, `@BotName avatar` | Shows a mentioned person's avatar. | ~av @X ### Gambling Command and aliases | Description | Usage @@ -186,6 +190,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` `>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. @@ -195,34 +200,37 @@ Command and aliases | Description | Usage ### Music 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 up to 15 currently queued songs. | `!m lq` -`!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 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 goto` | Goes to a specific time in seconds in a song. -`!m getlink`, `!m gl` | Shows a link to the currently playing song. +`! next`, `! n`, `! skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n` +`! stop`, `! s` | Stops the music and clears the playlist. Stays in the channel. | `!m s` +`! destroy`, `! d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` +`! pause`, `! p` | Pauses or Unpauses the song. | `!m 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**. | `!m q Dream Of Venice` +`! listqueue`, `! lq` | Lists 15 currently queued songs per page. Default page is 1. | `!m lq` or `!m lq 2` +`! nowplaying`, `! np` | Shows the song currently playing. | `!m np` +`! volume`, `! vol` | Sets the music volume 0-100% | `!m vol 50` +`! defvol`, `! dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80` +`! mute`, `! min` | Sets the music volume to 0% | `!m min` +`! max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` +`! half` | Sets the music volume to 50%. | `!m half` +`! shuffle`, `! sh` | Shuffles the current playlist. | `!m sh` +`! playlist`, `! pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` +`! soundcloudpl`, `! scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony` +`! localplaylst`, `! lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m 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 | `!m ra radio link here` +`! local`, `! lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3` +`! move`, `! mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv` +`! remove`, `! rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5` +`! movesong`, `! ms` | Moves a song from one position to another. | `! ms` 5>3 +`! cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup` +`! reptcursong`, `! rcs` | Toggles repeat of current song. | `!m rcs` +`! rpeatplaylst`, `! rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl` +`! save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1` +`! load` | Loads a playlist under a certain name. | `!m load classical-1` +`! playlists`, `! pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` +`! deleteplaylist`, `! delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m 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 @@ -232,13 +240,22 @@ Command and aliases | Description | Usage `~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | ~hitbox SomeStreamer `~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | ~twitch SomeStreamer `~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | ~beam SomeStreamer +`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | ~chhb SomeStreamer +`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | ~chtw SomeStreamer +`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | ~chbm SomeStreamer `~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | ~rms SomeGuy `~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls `~convert` | Convert quantities from>to. Like `~convert m>km 1000` `~convertlist` | List of the convertable dimensions and currencies. `~wowjoke` | Get one of Kwoth's penultimate WoW jokes. `~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1 -`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. +`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` +`~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. +`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ +`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | ~we Moscow RF `~yt` | Searches youtubes and shows the first result `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. @@ -248,10 +265,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. -`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera -`~osu u` | Shows osu stats for a player. Optional mode. | ~osu u rrtyui std -`~osu b` | Shows osu stats for a beatmap. | ~osu b https://osu.ppy.sh/b/992685 -`~osu top5` | Shows an osu player's top 5 plays. Optional mode. | ~osu top5 Dusk ctb +`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera `~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple `~#` | Searches Tagdef.com for a hashtag. | ~# ff `~quote` | Shows a random quote. @@ -266,6 +280,7 @@ Command and aliases | Description | Usage `~wiki` | Gives you back a wikipedia link `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. +`~av`, `~avatar` | Shows a mentioned person's avatar. | ~av @X ### NSFW Command and aliases | Description | Usage From 1cbb213706560d74859fe0756bae59f06d2cedd0 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Mon, 4 Jul 2016 09:59:26 +0200 Subject: [PATCH 25/33] fixed $lb --- NadekoBot/Classes/DBHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NadekoBot/Classes/DBHandler.cs b/NadekoBot/Classes/DBHandler.cs index e7d3d838..9fb664f9 100644 --- a/NadekoBot/Classes/DBHandler.cs +++ b/NadekoBot/Classes/DBHandler.cs @@ -198,7 +198,7 @@ Limit 20 OFFSET ?", num * 20); { using (var conn = new SQLiteConnection(FilePath)) { - return conn.Table().OrderBy(cs => -cs.Value).Take(n).ToList(); + return conn.Table().OrderByDescending(cs => cs.Value).Take(n).ToList(); } } } From a4810308a1590114f210667ba6c18f2dd5660835 Mon Sep 17 00:00:00 2001 From: Midnight-Myth Date: Mon, 4 Jul 2016 13:57:51 +0200 Subject: [PATCH 26/33] Fixed userpresence and logserver --- .../Modules/Administration/Commands/LogCommand.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 8eac1b93..8f51c9e5 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -238,7 +238,7 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` var config = SpecificConfigurations.Default.Of(e.Server.Id); try { - var chId = config.LogServerChannel; + var chId = config.LogPresenceChannel; if (chId != null) { Channel ch; @@ -365,6 +365,8 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` Channel ch; if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) return; + + SpecificConfigurations.Default.Of (e.Server.Id).LogServerChannel = null; await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); }); @@ -373,14 +375,14 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; + var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel; if (chId == null) { - SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; + SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = e.Channel.Id; await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); return; } - SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = null; + SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = null; await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false); }); From b83e1d74f861b68f425a5d9b281de486013ccbf5 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Mon, 4 Jul 2016 20:16:12 +0200 Subject: [PATCH 27/33] autoplay is canceled on `!m s`, fixed music commandlist --- .../Administration/AdministrationModule.cs | 2 +- .../Modules/Music/Classes/MusicControls.cs | 2 +- NadekoBot/Modules/Music/MusicModule.cs | 3 + commandlist.md | 66 +++++++++---------- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index 8095ad0a..36538813 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -79,7 +79,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "restart") - .Description("Restarts the bot. Might not work.") + .Description("Restarts the bot. Might not work. **Bot Owner Only**") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/NadekoBot/Modules/Music/Classes/MusicControls.cs index deb9bc2a..e1cd32bb 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Music.Classes private bool Destroyed { get; set; } = false; public bool RepeatSong { get; private set; } = false; public bool RepeatPlaylist { get; private set; } = false; - public bool Autoplay { get; private set; } = false; + public bool Autoplay { get; set; } = false; public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) { diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index f2e2071a..a22bc843 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -58,7 +58,10 @@ namespace NadekoBot.Modules.Music MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + { + musicPlayer.Autoplay = false; musicPlayer.Stop(); + } }); cgb.CreateCommand("destroy") diff --git a/commandlist.md b/commandlist.md index 4e449351..68146587 100644 --- a/commandlist.md +++ b/commandlist.md @@ -2,7 +2,7 @@ ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6029.16666` +Version: `NadekoBot v0.9.6029.36463` ### Help Command and aliases | Description | Usage ----------------|--------------|------- @@ -54,7 +54,7 @@ Command and aliases | Description | Usage `.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. `.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. `.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. -`.restart` | Restarts the bot. Might not work. +`.restart` | Restarts the bot. Might not work. **Bot Owner Only** `.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest `.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin `.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` @@ -200,37 +200,37 @@ Command and aliases | Description | Usage ### Music Command and aliases | Description | Usage ----------------|--------------|------- -`! next`, `! n`, `! skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n` -`! stop`, `! s` | Stops the music and clears the playlist. Stays in the channel. | `!m s` -`! destroy`, `! d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` -`! pause`, `! p` | Pauses or Unpauses the song. | `!m 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**. | `!m q Dream Of Venice` -`! listqueue`, `! lq` | Lists 15 currently queued songs per page. Default page is 1. | `!m lq` or `!m lq 2` -`! nowplaying`, `! np` | Shows the song currently playing. | `!m np` -`! volume`, `! vol` | Sets the music volume 0-100% | `!m vol 50` -`! defvol`, `! dv` | Sets the default music volume when music playback is started (0-100). Does not persist through restarts. | `!m dv 80` -`! mute`, `! min` | Sets the music volume to 0% | `!m min` -`! max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` -`! half` | Sets the music volume to 50%. | `!m half` -`! shuffle`, `! sh` | Shuffles the current playlist. | `!m sh` -`! playlist`, `! pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` -`! soundcloudpl`, `! scpl` | Queue a soundcloud playlist using a link. | `!m scpl https://soundcloud.com/saratology/sets/symphony` -`! localplaylst`, `! lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!m 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 | `!m ra radio link here` -`! local`, `! lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!m lo C:/music/mysong.mp3` -`! move`, `! mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv` -`! remove`, `! rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5` -`! movesong`, `! ms` | Moves a song from one position to another. | `! ms` 5>3 -`! cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!m cleanup` -`! reptcursong`, `! rcs` | Toggles repeat of current song. | `!m rcs` -`! rpeatplaylst`, `! rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl` -`! save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1` -`! load` | Loads a playlist under a certain name. | `!m load classical-1` -`! playlists`, `! pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` -`! deleteplaylist`, `! delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!m 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) +`!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) ### Searches Command and aliases | Description | Usage From 3f2b98f3b09dc346bb5d30bdee8fa097143dbf21 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 5 Jul 2016 02:05:53 +0200 Subject: [PATCH 28/33] fixed `!m dv` description --- NadekoBot/Modules/Music/MusicModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index a22bc843..59c86031 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -201,7 +201,7 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand("defvol") .Alias("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.\n**Usage**: `!m dv 80`") .Parameter("val", ParameterType.Required) .Do(async e => { From 2c3462e8f97b38086b214976faf1ce43205c0908 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 5 Jul 2016 02:07:24 +0200 Subject: [PATCH 29/33] commandlist updated --- commandlist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commandlist.md b/commandlist.md index 68146587..a6d2e202 100644 --- a/commandlist.md +++ b/commandlist.md @@ -2,7 +2,7 @@ ######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` #NadekoBot List Of Commands -Version: `NadekoBot v0.9.6029.36463` +Version: `NadekoBot v0.9.6030.3793` ### Help Command and aliases | Description | Usage ----------------|--------------|------- @@ -208,7 +208,7 @@ Command and aliases | Description | Usage `!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 defvol`, `!m dv` | Sets the default music volume when music playback is started (0-100). Persists 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` From 3548b4ecbf647907c75163edf8639c087e6df4f1 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 12 Jul 2016 05:00:14 +0200 Subject: [PATCH 30/33] Update linuxsetup with correct certmgr command instead of mozroots --- LinuxSetup.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/LinuxSetup.md b/LinuxSetup.md index 14152c90..2126fe44 100644 --- a/LinuxSetup.md +++ b/LinuxSetup.md @@ -121,12 +121,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)** From a3754f8210c994a824b263a1a8db43a810e2dcc7 Mon Sep 17 00:00:00 2001 From: miraai Date: Tue, 12 Jul 2016 21:19:33 +0200 Subject: [PATCH 31/33] Update ComprehensiveGuide.md --- ComprehensiveGuide.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index a66c3d5f..2bf7fe74 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -47,3 +47,4 @@ ________________________________________________________________________________ - On the left tab, access Credentials. There will be a line saying "If you wish to skip this step and create an API key, client ID or service account." Click on API Key, and then Server Key in the new window that appears. Enter in a name for the server key. A new window will appear with your Google API key. Copy the key. - Open up credentials.json. For "GoogleAPIKey", fill in with the new key. - Go to (https://soundcloud.com/you/apps/new). Enter a name for the app and create it. You will see a page with the title of your app, and a field labeled Client ID. Copy the ID. In credentials.json, fill in "SoundcloudClientID" with the copied ID. +- Restart your computer. From 31b94c01cb490fd174a3309e3d083ecfed0b2f76 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 16 Jul 2016 22:41:40 +0200 Subject: [PATCH 32/33] awesome discord flair added o.o thx volt --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 36b09bd8..ab320d32 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/81384788765712384/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) From 2358eddbaab96ed8cd4c7de77b23b98a73439b38 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 16 Jul 2016 22:42:29 +0200 Subject: [PATCH 33/33] woopsy daisy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab320d32..4992761a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) -[![Discord](https://discordapp.com/api/servers/81384788765712384/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) +[![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)