From a7fc482ff7d32eb37024a11d760ec5f55e4454e7 Mon Sep 17 00:00:00 2001 From: samvaio Date: Wed, 28 Dec 2016 21:43:38 +0530 Subject: [PATCH 01/74] Songs Thumbnail fixed --- src/NadekoBot/Modules/Music/Classes/Song.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs index c7dfeaf0..4405f4e0 100644 --- a/src/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Music.Classes private string PrettyTotalTime { get { if (TotalTime == TimeSpan.Zero) - return "(?)"; + return "-"; else if (TotalTime == TimeSpan.MaxValue) return "∞"; else @@ -68,17 +68,18 @@ namespace NadekoBot.Modules.Music.Classes public string Thumbnail { get { - switch (SongInfo.ProviderType) + switch (SongInfo.Provider) { - case MusicType.Radio: + + case "YouTube": //todo have videoid in songinfo from the start var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); return $"https://img.youtube.com/vi/{ videoId }/0.jpg"; - case MusicType.Normal: + case "Radio Stream": return $"https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links - case MusicType.Local: + case "Local File": return $"https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links - case MusicType.Soundcloud: + case "SoundCloud": return SongInfo.AlbumArt; default: return ""; From 0ef9e356e5fd4957b909ef695b68463500cc1f3f Mon Sep 17 00:00:00 2001 From: samvaio Date: Wed, 28 Dec 2016 21:46:36 +0530 Subject: [PATCH 02/74] Google Search fixed - All searches url now uses shorturl to fix the issue with links having more than 250+ characters - Title, Texts and terms trimmed to fit in embeds --- src/NadekoBot/Modules/Searches/Searches.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 20cd6361..83f55bfe 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -266,7 +266,7 @@ namespace NadekoBot.Modules.Searches var embed = new EmbedBuilder() .WithOkColor() - .WithAuthor(eab => eab.WithName("Search For: " + terms) + .WithAuthor(eab => eab.WithName("Search For: " + terms.TrimTo(50)) .WithUrl(fullQueryLink) .WithIconUrl("http://i.imgur.com/G46fm8J.png")) .WithTitle(umsg.Author.Mention) @@ -274,7 +274,8 @@ namespace NadekoBot.Modules.Searches string desc = ""; foreach (GoogleSearchResult res in results) { - desc += $"[{Format.Bold(res.Title)}]({res.Link})\n{res.Text}\n\n"; + var shortlinks = await NadekoBot.Google.ShortenUrl(res.Link).ConfigureAwait(false); + desc += $"[{Format.Bold(res.Title.TrimTo(70))}]({shortlinks})\n{res.Text.TrimTo(150)}\n\n"; } await channel.EmbedAsync(embed.WithDescription(desc).Build()).ConfigureAwait(false); } From c4354112c625dbf47bba1c886c387f1b6c477204 Mon Sep 17 00:00:00 2001 From: samvaio Date: Thu, 29 Dec 2016 12:04:12 +0530 Subject: [PATCH 03/74] Revert "Google Search fixed" This reverts commit 0ef9e356e5fd4957b909ef695b68463500cc1f3f. --- src/NadekoBot/Modules/Searches/Searches.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 83f55bfe..20cd6361 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -266,7 +266,7 @@ namespace NadekoBot.Modules.Searches var embed = new EmbedBuilder() .WithOkColor() - .WithAuthor(eab => eab.WithName("Search For: " + terms.TrimTo(50)) + .WithAuthor(eab => eab.WithName("Search For: " + terms) .WithUrl(fullQueryLink) .WithIconUrl("http://i.imgur.com/G46fm8J.png")) .WithTitle(umsg.Author.Mention) @@ -274,8 +274,7 @@ namespace NadekoBot.Modules.Searches string desc = ""; foreach (GoogleSearchResult res in results) { - var shortlinks = await NadekoBot.Google.ShortenUrl(res.Link).ConfigureAwait(false); - desc += $"[{Format.Bold(res.Title.TrimTo(70))}]({shortlinks})\n{res.Text.TrimTo(150)}\n\n"; + desc += $"[{Format.Bold(res.Title)}]({res.Link})\n{res.Text}\n\n"; } await channel.EmbedAsync(embed.WithDescription(desc).Build()).ConfigureAwait(false); } From b150bc9a85eeaef37a550a60f8c60d45d7e2829f Mon Sep 17 00:00:00 2001 From: samvaio Date: Thu, 29 Dec 2016 19:18:57 +0530 Subject: [PATCH 04/74] ups --- src/NadekoBot/Modules/Music/Classes/Song.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs index 4405f4e0..6a614eec 100644 --- a/src/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Music.Classes private string PrettyTotalTime { get { if (TotalTime == TimeSpan.Zero) - return "-"; + return "(?)"; else if (TotalTime == TimeSpan.MaxValue) return "∞"; else @@ -68,18 +68,17 @@ namespace NadekoBot.Modules.Music.Classes public string Thumbnail { get { - switch (SongInfo.Provider) + switch (SongInfo.ProviderType) { - - case "YouTube": + case MusicType.Radio: + return $"https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links + case MusicType.Normal: //todo have videoid in songinfo from the start var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+"); return $"https://img.youtube.com/vi/{ videoId }/0.jpg"; - case "Radio Stream": - return $"https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links - case "Local File": + case MusicType.Local: return $"https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links - case "SoundCloud": + case MusicType.Soundcloud: return SongInfo.AlbumArt; default: return ""; From 29da8e53593392f54dd438c93e6e67fe5a1ea93d Mon Sep 17 00:00:00 2001 From: The Oddball Date: Fri, 13 Jan 2017 08:17:39 -0600 Subject: [PATCH 05/74] Update Latest.bat --- scripts/Latest.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Latest.bat b/scripts/Latest.bat index 2ee46775..8d59ec86 100644 --- a/scripts/Latest.bat +++ b/scripts/Latest.bat @@ -109,5 +109,5 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) RMDIR /S /Q "%installtemp%" >nul 2>&1 ECHO. ECHO Installation complete, press any key to close this window! - PAUSE >nul 2>&1 + timeout /t 5 del Latest.bat From 22bc4a6c000472bf7e6bece90eb2b92ee5d4da07 Mon Sep 17 00:00:00 2001 From: The Oddball Date: Fri, 13 Jan 2017 08:18:03 -0600 Subject: [PATCH 06/74] Update Stable.bat --- scripts/Stable.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Stable.bat b/scripts/Stable.bat index bef9df85..a2016889 100644 --- a/scripts/Stable.bat +++ b/scripts/Stable.bat @@ -109,5 +109,5 @@ IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) RMDIR /S /Q "%installtemp%" >nul 2>&1 ECHO. ECHO Installation complete, press any key to close this window! - PAUSE >nul 2>&1 + timeout /t 5 del Stable.bat From f98c7d109b95bf240eac2d52f2b74ba8340bc82d Mon Sep 17 00:00:00 2001 From: Kwoth Date: Fri, 13 Jan 2017 16:31:33 +0100 Subject: [PATCH 07/74] Fixed current time > 1hr --- src/NadekoBot/Modules/Music/Classes/Song.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs index 0ee23fd4..04e49a70 100644 --- a/src/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -47,13 +47,23 @@ namespace NadekoBot.Modules.Music.Classes public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime; - public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({songUrl})**"; + public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({songUrl})**"; public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}"; public string PrettyFullName => $"{PrettyName}\n\t\t`{PrettyTotalTime} | {PrettyProvider} | {QueuerName}`"; - public string PrettyCurrentTime => CurrentTime.ToString(@"mm\:ss"); + public string PrettyCurrentTime { + get { + var time = CurrentTime.ToString(@"mm\:ss"); + var hrs = (int)CurrentTime.TotalHours; + + if (hrs > 0) + return hrs + ":" + time; + else + return time; + } + } private string PrettyTotalTime { get { From ddd48a1d4cc6edcd4663a719a1a0f5fc6cc6a182 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Fri, 13 Jan 2017 16:37:01 +0100 Subject: [PATCH 08/74] Fixed youtube regex if it contains something after the id --- src/NadekoBot/Services/Impl/GoogleApiService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index eaa066ff..7da7ad7b 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -51,7 +51,7 @@ namespace NadekoBot.Services.Impl return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId); } - private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); + private readonly Regex YtVideoIdRegex = new Regex(@"(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)(?[a-zA-Z0-9_-]{6,11})", RegexOptions.Compiled); public async Task> GetRelatedVideosAsync(string id, int count = 1) { From b2e6d6729e459fbbddbc6c4737098c5ddcfba3a4 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 14:07:07 +0100 Subject: [PATCH 09/74] fixed default values in currency multipliers --- .../20170112185538_currency-modifications.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs index bff8c726..ad0af9e3 100644 --- a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs +++ b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs @@ -12,37 +12,37 @@ namespace NadekoBot.Migrations name: "BetflipMultiplier", table: "BotConfig", nullable: false, - defaultValue: 0f); + defaultValue: 1.8f); migrationBuilder.AddColumn( name: "Betroll100Multiplier", table: "BotConfig", nullable: false, - defaultValue: 0f); + defaultValue: 10f); migrationBuilder.AddColumn( name: "Betroll67Multiplier", table: "BotConfig", nullable: false, - defaultValue: 0f); + defaultValue: 2f); migrationBuilder.AddColumn( name: "Betroll91Multiplier", table: "BotConfig", nullable: false, - defaultValue: 0f); + defaultValue: 3f); migrationBuilder.AddColumn( name: "CurrencyDropAmount", table: "BotConfig", nullable: false, - defaultValue: 0); + defaultValue: 1); migrationBuilder.AddColumn( name: "MinimumBetAmount", table: "BotConfig", nullable: false, - defaultValue: 0); + defaultValue: 3); migrationBuilder.AddColumn( name: "TriviaCurrencyReward", From 3d71e550ce4d681f826cbac6b6f0fbcf3c1fd8b8 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 17:37:11 +0100 Subject: [PATCH 10/74] Slight changes. Couldn't get commandprices to work for now --- .../NadekoSqliteContextModelSnapshot.cs | 2 - .../Commands/CommandCostCommands.cs | 101 ++++++++++++++++++ .../Resources/CommandStrings.Designer.cs | 54 ++++++++++ src/NadekoBot/Resources/CommandStrings.resx | 18 ++++ src/NadekoBot/Services/CommandHandler.cs | 35 +++--- .../Services/Database/Models/BotConfig.cs | 5 + .../Services/Database/Models/CommandCost.cs | 27 +++++ .../Services/Database/Models/CommandPrice.cs | 13 --- .../Services/Database/NadekoContext.cs | 5 + .../Repositories/Impl/BotConfigRepository.cs | 1 + 10 files changed, 234 insertions(+), 27 deletions(-) create mode 100644 src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs create mode 100644 src/NadekoBot/Services/Database/Models/CommandCost.cs diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 4d94b193..91f7b6fc 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -4,8 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using NadekoBot.Services.Database; -using NadekoBot.Services.Database.Models; -using NadekoBot.Modules.Music.Classes; namespace NadekoBot.Migrations { diff --git a/src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs new file mode 100644 index 00000000..e0ef1a29 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Commands/CommandCostCommands.cs @@ -0,0 +1,101 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Permissions +{ + public partial class Permissions + { + [Group] + public class CommandCostCommands : ModuleBase + { + private static readonly ConcurrentDictionary _commandCosts = new ConcurrentDictionary(); + public static IReadOnlyDictionary CommandCosts => _commandCosts; + + static CommandCostCommands() + { + //_commandCosts = new ConcurrentDictionary(NadekoBot.BotConfig.CommandCosts.ToDictionary( + // x => x.CommandName.Trim().ToUpperInvariant(), + // x => x.Cost)); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task CmdCosts(int page = 1) + { + var prices = _commandCosts.ToList(); + + if (!prices.Any()) + { + await Context.Channel.SendConfirmAsync("No costs set.").ConfigureAwait(false); + return; + } + + await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => { + var embed = new EmbedBuilder().WithOkColor() + .WithTitle("Command Costs"); + var current = prices.Skip((curPage - 1) * 9) + .Take(9); + foreach (var price in current) + { + embed.AddField(efb => efb.WithName(price.Key).WithValue(price.Value.ToString()).WithIsInline(true)); + } + return embed; + }, prices.Count / 9).ConfigureAwait(false); + } + + //[NadekoCommand, Usage, Description, Aliases] + //public async Task CommandCost(int cost, CommandInfo cmd) + //{ + // if (cost < 0) + // return; + + // var cmdName = cmd.Aliases.First().ToLowerInvariant(); + + // var cmdPrice = new CommandCost() + // { + // CommandName = cmdName, + // Cost = cost + // }; + + // using (var uow = DbHandler.UnitOfWork()) + // { + // var bc = uow.BotConfig.GetOrCreate(); + + // if (cost != 0) + // { + // var elem = bc.CommandCosts.Where(cc => cc.CommandName == cmdPrice.CommandName).FirstOrDefault(); + // if (elem == null) + // bc.CommandCosts.Add(cmdPrice); + // else + // elem.Cost = cost; + + // _commandCosts.AddOrUpdate(cmdName, cost, (key, old) => cost); + // } + // else + // { + // bc.CommandCosts.RemoveAt(bc.CommandCosts.IndexOf(cmdPrice)); + // int throwaway; + // _commandCosts.TryRemove(cmdName, out throwaway); + // } + + // await uow.CompleteAsync().ConfigureAwait(false); + // } + + // if (cost == 0) + // await Context.Channel.SendConfirmAsync($"Removed the cost from the {Format.Bold(cmd.Name)} command.").ConfigureAwait(false); + // else + // await Context.Channel.SendConfirmAsync($"{Format.Bold(cmd.Name)} now costs {cost}{NadekoBot.BotConfig.CurrencySign} to run.").ConfigureAwait(false); + //} + } + } +} diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index e58dbbbb..0d4f9d0a 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -1760,6 +1760,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to cmdcosts. + /// + public static string cmdcosts_cmd { + get { + return ResourceManager.GetString("cmdcosts_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a list of command costs. Paginated with 9 command per page.. + /// + public static string cmdcosts_desc { + get { + return ResourceManager.GetString("cmdcosts_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cmdcosts` or `{0}cmdcosts 2`. + /// + public static string cmdcosts_usage { + get { + return ResourceManager.GetString("cmdcosts_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to color clr. /// @@ -1787,6 +1814,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to commandcost cmdcost. + /// + public static string commandcost_cmd { + get { + return ResourceManager.GetString("commandcost_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a price for a command. Running that command will take currency from users. Set 0 to remove the price.. + /// + public static string commandcost_desc { + get { + return ResourceManager.GetString("commandcost_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cmdcost 0 !!q` or `{0}cmdcost 1 >8ball`. + /// + public static string commandcost_usage { + get { + return ResourceManager.GetString("commandcost_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to commands cmds. /// diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index d2ef3388..5599995f 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -2925,4 +2925,22 @@ `{0}antispamignore` + + cmdcosts + + + Shows a list of command costs. Paginated with 9 command per page. + + + `{0}cmdcosts` or `{0}cmdcosts 2` + + + commandcost cmdcost + + + Sets a price for a command. Running that command will take currency from users. Set 0 to remove the price. + + + `{0}cmdcost 0 !!q` or `{0}cmdcost 1 >8ball` + \ No newline at end of file diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 98665bf0..d0dc776b 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -32,9 +32,9 @@ namespace NadekoBot.Services { public const int GlobalCommandsCooldown = 1500; - private ShardedDiscordClient _client; - private CommandService _commandService; - private Logger _log; + private readonly ShardedDiscordClient _client; + private readonly CommandService _commandService; + private readonly Logger _log; private List ownerChannels { get; set; } @@ -100,8 +100,8 @@ namespace NadekoBot.Services BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) || BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id); - - private async Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw) + const float oneThousandth = 1.0f / 1000; + private async Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks) { await CommandExecuted(usrMsg, exec.CommandInfo).ConfigureAwait(false); _log.Info("Command Executed after {4}s\n\t" + @@ -113,10 +113,10 @@ namespace NadekoBot.Services (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} usrMsg.Content, // {3} - sw.Elapsed.TotalSeconds); + ticks * oneThousandth); } - private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, Stopwatch sw) + private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks) { _log.Warn("Command Errored after {5}s\n\t" + "User: {0}\n\t" + @@ -129,7 +129,7 @@ namespace NadekoBot.Services (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} usrMsg.Content,// {3} exec.Result.ErrorReason, // {4} - sw.Elapsed.TotalSeconds // {5} + ticks * oneThousandth // {5} ); } @@ -187,6 +187,8 @@ namespace NadekoBot.Services if (msg.Author.IsBot || !NadekoBot.Ready) //no bots, wait until bot connected and initialized return; + var execTime = Environment.TickCount; + var usrMsg = msg as SocketUserMessage; if (usrMsg == null) //has to be an user message, not system/other messages. return; @@ -226,17 +228,16 @@ namespace NadekoBot.Services string messageContent = usrMsg.Content; // execute the command and measure the time it took - var sw = Stopwatch.StartNew(); var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); - sw.Stop(); + execTime = Environment.TickCount - execTime; if (exec.Result.IsSuccess) { - await LogSuccessfulExecution(usrMsg, exec, channel, sw).ConfigureAwait(false); + await LogSuccessfulExecution(usrMsg, exec, channel, execTime).ConfigureAwait(false); } else if (!exec.Result.IsSuccess && exec.Result.Error != CommandError.UnknownCommand) { - LogErroredExecution(usrMsg, exec, channel, sw); + LogErroredExecution(usrMsg, exec, channel, execTime); if (guild != null && exec.CommandInfo != null && exec.Result.Error == CommandError.Exception) { if (exec.PermissionCache != null && exec.PermissionCache.Verbose) @@ -354,6 +355,16 @@ namespace NadekoBot.Services return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands.")); } } + + int price; + if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0) + { + var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false); + if (!success) + { + return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command.")); + } + } } diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 38faf9ba..080770ec 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -31,6 +31,11 @@ namespace NadekoBot.Services.Database.Models public float Betroll67Multiplier { get; set; } = 2; public float Betroll91Multiplier { get; set; } = 3; public float Betroll100Multiplier { get; set; } = 10; + //public HashSet CommandCosts { get; set; } = new HashSet(); + + /// + /// I messed up, don't use + /// public HashSet CommandPrices { get; set; } = new HashSet(); diff --git a/src/NadekoBot/Services/Database/Models/CommandCost.cs b/src/NadekoBot/Services/Database/Models/CommandCost.cs new file mode 100644 index 00000000..da5a255e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/CommandCost.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class CommandCost : DbEntity + { + public int Cost { get; set; } + public string CommandName { get; set; } + + public override int GetHashCode() => + CommandName.GetHashCode(); + + public override bool Equals(object obj) + { + var instance = obj as CommandCost; + + if (instance == null) + return false; + + return instance.CommandName == CommandName; + } + } +} diff --git a/src/NadekoBot/Services/Database/Models/CommandPrice.cs b/src/NadekoBot/Services/Database/Models/CommandPrice.cs index d8b21d76..8c7ea739 100644 --- a/src/NadekoBot/Services/Database/Models/CommandPrice.cs +++ b/src/NadekoBot/Services/Database/Models/CommandPrice.cs @@ -11,18 +11,5 @@ namespace NadekoBot.Services.Database.Models public int Price { get; set; } //this is unique public string CommandName { get; set; } - - public override int GetHashCode() => - CommandName.GetHashCode(); - - public override bool Equals(object obj) - { - var instance = obj as CommandPrice; - - if (instance == null) - return false; - - return instance.CommandName == CommandName; - } } } diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index c2696b93..b49439f6 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -235,9 +235,14 @@ namespace NadekoBot.Services.Database #endregion #region CommandPrice + //well, i failed modelBuilder.Entity() .HasIndex(cp => cp.Price) .IsUnique(); + + //modelBuilder.Entity() + // .HasIndex(cp => cp.CommandName) + // .IsUnique(); #endregion } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs index e1ab3893..2aa0bf83 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs @@ -17,6 +17,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Include(bc => bc.Blacklist) .Include(bc => bc.EightBallResponses) .Include(bc => bc.ModulePrefixes) + //.Include(bc => bc.CommandCosts) .FirstOrDefault(); if (config == null) From 8c1d603fa7f24a21316e9776b4f42b0b9bbb9ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 14 Jan 2017 17:45:59 +0100 Subject: [PATCH 11/74] Update Linux Guide.md --- docs/guides/Linux Guide.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/guides/Linux Guide.md b/docs/guides/Linux Guide.md index 506704d0..5fee13a3 100644 --- a/docs/guides/Linux Guide.md +++ b/docs/guides/Linux Guide.md @@ -28,15 +28,21 @@ If you entered your Droplets IP address correctly, it should show **login as:** ![img1](https://cdn.discordapp.com/attachments/251504306010849280/251504416019054592/git.gif) +Ubuntu: + `sudo apt-get install git -y` +CentOS: + +`yum -y install git` + **NOTE:** If the command is not being initiated, hit **Enter** ####Installing .NET Core SDK ![img2](https://cdn.discordapp.com/attachments/251504306010849280/251504746987388938/dotnet.gif) -Go to [this link](https://www.microsoft.com/net/core#ubuntu) provided by microsoft for instructions on how to get the most up to date version of the dotnet core sdk! +Go to [this link](https://www.microsoft.com/net/core#ubuntu) (for Ubuntu) or to [this link](https://www.microsoft.com/net/core#linuxcentos) (for CentOS) provided by microsoft for instructions on how to get the most up to date version of the dotnet core sdk! Make sure that you're on the correct page for your distribution of linux as the guides are different for the various distributions We'll go over the steps here for Ubuntu 16.04 anyway (these will **only** work on Ubuntu 16.04), accurate as of 25/11/2016 @@ -53,14 +59,29 @@ sudo apt-get update && sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 - ![img3](https://cdn.discordapp.com/attachments/251504306010849280/251505294654308353/libopus.gif) +Ubuntu: + `sudo apt-get install libopus0 opus-tools libopus-dev libsodium-dev -y` +CentOS: + +`yum -y install opus opus-devel` + ####Installing FFMPEG ![img4](https://cdn.discordapp.com/attachments/251504306010849280/251505443111829505/ffmpeg.gif) +Ubuntu: + `apt-get install ffmpeg -y` +Centos: + +``` +yum -y install http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm epel-release +yum -y install ffmpeg +``` + **NOTE:** If you are running **UBUNTU 14.04**, you must run these first: ``` @@ -84,8 +105,14 @@ sudo apt-get update && sudo apt-get install ffmpeg -y ![img5](https://cdn.discordapp.com/attachments/251504306010849280/251505519758409728/tmux.gif) +Ubuntu: + `sudo apt-get install tmux -y` +Centos: + +`yum -y install tmux` + ####Getting NadekoBot Use the following command to get and run `linuxAIO.sh`: From e4bcb5a71376f0715e8408e7c66955e31859e624 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 17:59:35 +0100 Subject: [PATCH 12/74] If announcement table can't be found, migration will skip it --- .../Administration/Commands/Migration.cs | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration.cs b/src/NadekoBot/Modules/Administration/Commands/Migration.cs index 433cfc7b..3a917698 100644 --- a/src/NadekoBot/Modules/Administration/Commands/Migration.cs +++ b/src/NadekoBot/Modules/Administration/Commands/Migration.cs @@ -89,54 +89,66 @@ namespace NadekoBot.Modules.Administration db.Open(); var com = db.CreateCommand(); - com.CommandText = "SELECT * FROM Announcement"; - - var reader = com.ExecuteReader(); var i = 0; - while (reader.Read()) + try { - var gid = (ulong)(long)reader["ServerId"]; - var greet = (long)reader["Greet"] == 1; - var greetDM = (long)reader["GreetPM"] == 1; - var greetChannel = (ulong)(long)reader["GreetChannelId"]; - var greetMsg = (string)reader["GreetText"]; - var bye = (long)reader["Bye"] == 1; - var byeDM = (long)reader["ByePM"] == 1; - var byeChannel = (ulong)(long)reader["ByeChannelId"]; - var byeMsg = (string)reader["ByeText"]; - var grdel = false; - var byedel = grdel; - var gc = uow.GuildConfigs.For(gid, set => set); + com.CommandText = "SELECT * FROM Announcement"; - if (greetDM) - gc.SendDmGreetMessage = greet; - else - gc.SendChannelGreetMessage = greet; - gc.GreetMessageChannelId = greetChannel; - gc.ChannelGreetMessageText = greetMsg; + var reader = com.ExecuteReader(); + while (reader.Read()) + { + var gid = (ulong)(long)reader["ServerId"]; + var greet = (long)reader["Greet"] == 1; + var greetDM = (long)reader["GreetPM"] == 1; + var greetChannel = (ulong)(long)reader["GreetChannelId"]; + var greetMsg = (string)reader["GreetText"]; + var bye = (long)reader["Bye"] == 1; + var byeDM = (long)reader["ByePM"] == 1; + var byeChannel = (ulong)(long)reader["ByeChannelId"]; + var byeMsg = (string)reader["ByeText"]; + var grdel = false; + var byedel = grdel; + var gc = uow.GuildConfigs.For(gid, set => set); - gc.SendChannelByeMessage = bye; - gc.ByeMessageChannelId = byeChannel; - gc.ChannelByeMessageText = byeMsg; + if (greetDM) + gc.SendDmGreetMessage = greet; + else + gc.SendChannelGreetMessage = greet; + gc.GreetMessageChannelId = greetChannel; + gc.ChannelGreetMessageText = greetMsg; - gc.AutoDeleteGreetMessagesTimer = gc.AutoDeleteByeMessagesTimer = grdel ? 30 : 0; - _log.Info(++i); + gc.SendChannelByeMessage = bye; + gc.ByeMessageChannelId = byeChannel; + gc.ChannelByeMessageText = byeMsg; + + gc.AutoDeleteGreetMessagesTimer = gc.AutoDeleteByeMessagesTimer = grdel ? 30 : 0; + _log.Info(++i); + } + } + catch { + _log.Warn("Greet/bye messages won't be migrated"); } - var com2 = db.CreateCommand(); com.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId"; i = 0; - var reader2 = com.ExecuteReader(); - while (reader2.Read()) + try { - _log.Info(++i); - var curr = new Currency() + var reader2 = com.ExecuteReader(); + while (reader2.Read()) { - Amount = (long)reader2["Value"], - UserId = (ulong)(long)reader2["UserId"] - }; - uow.Currency.Add(curr); + _log.Info(++i); + var curr = new Currency() + { + Amount = (long)reader2["Value"], + UserId = (ulong)(long)reader2["UserId"] + }; + uow.Currency.Add(curr); + } + } + catch + { + _log.Warn("Currency won't be migrated"); } db.Close(); try { File.Move("data/nadekobot.sqlite", "data/DELETE_ME_nadekobot.sqlite"); } catch { } From 659ba913a163e4cadb098fcac21af761b7f1d6fd Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 18:25:08 +0100 Subject: [PATCH 13/74] fix #967 !!ap fix, !!lq fix (it shows info in footer again) --- .../Modules/Music/Classes/MusicControls.cs | 13 +++++++++---- src/NadekoBot/Modules/Music/Music.cs | 6 +++--- src/NadekoBot/_Extensions/Extensions.cs | 19 +++++++++++++++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs index 1e822f0a..b705a388 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -140,9 +140,15 @@ namespace NadekoBot.Modules.Music.Classes RemoveSongAt(index, true); OnStarted(this, CurrentSong); - await CurrentSong.Play(audioClient, cancelToken); - - OnCompleted(this, CurrentSong); + try + { + await CurrentSong.Play(audioClient, cancelToken); + } + catch(OperationCanceledException) + { + OnCompleted(this, CurrentSong); + } + if (RepeatPlaylist) AddSong(CurrentSong, CurrentSong.QueuerName); @@ -151,7 +157,6 @@ namespace NadekoBot.Modules.Music.Classes AddSong(CurrentSong, 0); } - catch (OperationCanceledException) { } catch (Exception ex) { Console.WriteLine("Music thread almost crashed."); diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 381177a2..efdd9e6a 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -192,7 +192,7 @@ namespace NadekoBot.Modules.Music int startAt = itemsPerPage * (curPage - 1); var number = 0 + startAt; var embed = new EmbedBuilder() - .WithAuthor(eab => eab.WithName($"Player Queue") + .WithAuthor(eab => eab.WithName($"Player Queue - Page {curPage}/{lastPage + 1}") .WithMusicIcon()) .WithDescription(string.Join("\n", musicPlayer.Playlist .Skip(startAt) @@ -217,7 +217,7 @@ namespace NadekoBot.Modules.Music } return embed; }; - await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage).ConfigureAwait(false); + await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage, false).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -814,7 +814,7 @@ namespace NadekoBot.Modules.Music .WithFooter(ef => ef.WithText(song.PrettyInfo))) .ConfigureAwait(false); - if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") + if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.ProviderType == MusicType.Normal) { await QueueSong(await queuer.Guild.GetCurrentUserAsync(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); } diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 310b5974..634dd34a 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -24,10 +24,13 @@ namespace NadekoBot.Extensions /// /// danny kamisama /// - public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func pageFunc, int? lastPage = null) + public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) { lastPage += 1; - var embed = pageFunc(currentPage).AddPaginatedFooter(currentPage, lastPage); + var embed = pageFunc(currentPage); + + if(addPaginatedFooter) + embed.AddPaginatedFooter(currentPage, lastPage); var msg = await channel.EmbedAsync(embed) as IUserMessage; @@ -47,12 +50,20 @@ namespace NadekoBot.Extensions { if (currentPage == 1) return; - await msg.ModifyAsync(x => x.Embed = pageFunc(--currentPage).AddPaginatedFooter(currentPage, lastPage).Build()).ConfigureAwait(false); + var toSend = pageFunc(--currentPage); + if (addPaginatedFooter) + toSend.AddPaginatedFooter(currentPage, lastPage); + await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); } else if (r.Emoji.Name == arrow_right) { if (lastPage == null || lastPage > currentPage) - await msg.ModifyAsync(x => x.Embed = pageFunc(++currentPage).AddPaginatedFooter(currentPage, lastPage).Build()).ConfigureAwait(false); + { + var toSend = pageFunc(++currentPage); + if (addPaginatedFooter) + toSend.AddPaginatedFooter(currentPage, lastPage); + await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false); + } } } catch (Exception ex) { Console.WriteLine(ex); } From ae90282070e7602d949ffc4b2f39a27b1e7a2cd7 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 19:57:38 +0100 Subject: [PATCH 14/74] fixed .setstream --- src/NadekoBot/ShardedDiscordClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/ShardedDiscordClient.cs b/src/NadekoBot/ShardedDiscordClient.cs index a59dece7..5d6e9fa3 100644 --- a/src/NadekoBot/ShardedDiscordClient.cs +++ b/src/NadekoBot/ShardedDiscordClient.cs @@ -178,7 +178,7 @@ namespace NadekoBot public Task SetGame(string game) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(game))); - public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.NotStreaming))); + public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.Twitch))); public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status)))); From 88eabf6d907dbb3e0ab40d067f7161f84db6efd7 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 14 Jan 2017 23:23:11 +0100 Subject: [PATCH 15/74] Better implementation of .se --- src/NadekoBot/Modules/Utility/Utility.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 5ffae5fc..bc413919 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -290,14 +290,12 @@ namespace NadekoBot.Modules.Utility ); } - private Regex emojiFinder { get; } = new Regex(@"<:(?.+?):(?\d*)>", RegexOptions.Compiled); [NadekoCommand, Usage, Description, Aliases] public async Task Showemojis([Remainder] string emojis) { - var matches = emojiFinder.Matches(emojis); + var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emoji)t.Value); - var result = string.Join("\n", matches.Cast() - .Select(m => $"**Name:** {m.Groups["name"]} **Link:** http://discordapp.com/api/emojis/{m.Groups["id"]}.png")); + var result = string.Join("\n", tags.Select(m => $"**Name:** {m} **Link:** {m.Url}")); if (string.IsNullOrWhiteSpace(result)) await Context.Channel.SendErrorAsync("No special emojis found."); From 3926bc707b4def335fc50b80f627f765a863aeab Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 02:08:14 +0100 Subject: [PATCH 16/74] ready to use new client --- .../Administration/Commands/LogCommand.cs | 4 +-- .../Commands/PlayingRotateCommands.cs | 2 +- .../Administration/Commands/SelfCommands.cs | 29 +++++++++++++++---- .../Modules/CustomReactions/Extensions.cs | 2 +- .../Games/Commands/CleverBotCommands.cs | 2 +- src/NadekoBot/Modules/Games/Games.cs | 2 +- src/NadekoBot/Modules/Help/Help.cs | 2 +- .../Commands/CrossServerTextChannel.cs | 2 +- src/NadekoBot/Modules/Utility/Utility.cs | 2 +- src/NadekoBot/NadekoBot.cs | 15 ++++++++-- src/NadekoBot/Services/CommandHandler.cs | 4 +-- src/NadekoBot/Services/Impl/StatsService.cs | 28 +++++++++--------- src/NadekoBot/ShardedDiscordClient.cs | 19 ++---------- src/NadekoBot/_Extensions/Extensions.cs | 2 +- 14 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 7215e321..31dfbdc8 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -25,7 +25,7 @@ namespace NadekoBot.Modules.Administration { private const string clockEmojiUrl = "https://cdn.discordapp.com/attachments/155726317222887425/258309524966866945/clock.png"; - private static ShardedDiscordClient _client { get; } + private static DiscordShardedClient _client { get; } private static Logger _log { get; } private static string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; @@ -81,7 +81,7 @@ namespace NadekoBot.Modules.Administration _client.UserPresenceUpdated += _client_UserPresenceUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; - _client.GuildUserUpdated += _client_GuildUserUpdated; + _client.GuildMemberUpdated += _client_GuildUserUpdated; #if !GLOBAL_NADEKO _client.UserUpdated += _client_UserUpdated; #endif diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index e454809a..f8817db9 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(status)) continue; PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); - await NadekoBot.Client.SetGame(status); + await NadekoBot.Client.SetGameAsync(status).ConfigureAwait(false); } } catch (Exception ex) diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs index 753f85f5..a087e6f2 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Administration await Context.Channel.SendErrorAsync("⚠️ Cannot find that server").ConfigureAwait(false); return; } - if (server.OwnerId != NadekoBot.Client.CurrentUser().Id) + if (server.OwnerId != NadekoBot.Client.CurrentUser.Id) { await server.LeaveAsync().ConfigureAwait(false); await Context.Channel.SendConfirmAsync("✅ Left server " + server.Name).ConfigureAwait(false); @@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(newName)) return; - await NadekoBot.Client.CurrentUser().ModifyAsync(u => u.Username = newName).ConfigureAwait(false); + await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false); await Context.Channel.SendConfirmAsync($"Bot name changed to **{newName}**").ConfigureAwait(false); } @@ -66,7 +66,7 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task SetStatus([Remainder] SettableUserStatus status) { - await NadekoBot.Client.SetStatus(status); + await NadekoBot.Client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false); await Context.Channel.SendConfirmAsync($"Bot status changed to **{status}**").ConfigureAwait(false); } @@ -86,7 +86,7 @@ namespace NadekoBot.Modules.Administration await sr.CopyToAsync(imgStream); imgStream.Position = 0; - await NadekoBot.Client.CurrentUser().ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false); + await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false); } } @@ -97,7 +97,7 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task SetGame([Remainder] string game = null) { - await NadekoBot.Client.SetGame(game).ConfigureAwait(false); + await NadekoBot.Client.SetGameAsync(game).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("👾 **New game set.**").ConfigureAwait(false); } @@ -108,7 +108,7 @@ namespace NadekoBot.Modules.Administration { name = name ?? ""; - await NadekoBot.Client.SetStream(name, url).ConfigureAwait(false); + await NadekoBot.Client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("ℹ️ **New stream set.**").ConfigureAwait(false); } @@ -169,6 +169,23 @@ namespace NadekoBot.Modules.Administration await Context.Channel.SendConfirmAsync("🆗").ConfigureAwait(false); } + + private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) + { + switch (sus) + { + case SettableUserStatus.Online: + return UserStatus.Online; + case SettableUserStatus.Invisible: + return UserStatus.Invisible; + case SettableUserStatus.Idle: + return UserStatus.AFK; + case SettableUserStatus.Dnd: + return UserStatus.DoNotDisturb; + } + + return UserStatus.Online; + } } } } diff --git a/src/NadekoBot/Modules/CustomReactions/Extensions.cs b/src/NadekoBot/Modules/CustomReactions/Extensions.cs index c6fe16c7..a894ad2e 100644 --- a/src/NadekoBot/Modules/CustomReactions/Extensions.cs +++ b/src/NadekoBot/Modules/CustomReactions/Extensions.cs @@ -18,7 +18,7 @@ namespace NadekoBot.Modules.CustomReactions public static Dictionary> placeholders = new Dictionary>() { - {"%mention%", (ctx) => { return $"<@{NadekoBot.Client.CurrentUser().Id}>"; } }, + {"%mention%", (ctx) => { return $"<@{NadekoBot.Client.CurrentUser.Id}>"; } }, {"%user%", (ctx) => { return ctx.Author.Mention; } }, {"%rnduser%", (ctx) => { var ch = ctx.Channel as ITextChannel; diff --git a/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs b/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs index 8197035b..b6544256 100644 --- a/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs @@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Games if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out cleverbot)) return false; - var nadekoId = NadekoBot.Client.CurrentUser().Id; + var nadekoId = NadekoBot.Client.CurrentUser.Id; var normalMention = $"<@{nadekoId}> "; var nickMention = $"<@!{nadekoId}> "; string message; diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 10d652b4..8dfd7d5a 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -80,7 +80,7 @@ namespace NadekoBot.Modules.Games else if ((pick == 0 && nadekoPick == 1) || (pick == 1 && nadekoPick == 2) || (pick == 2 && nadekoPick == 0)) - msg = $"{NadekoBot.Client.CurrentUser().Mention} won! {GetRPSPick(nadekoPick)} beats {GetRPSPick(pick)}"; + msg = $"{NadekoBot.Client.CurrentUser.Mention} won! {GetRPSPick(nadekoPick)} beats {GetRPSPick(pick)}"; else msg = $"{Context.User.Mention} won! {GetRPSPick(pick)} beats {GetRPSPick(nadekoPick)}"; diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 6f062fd5..4a561ca6 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -137,7 +137,7 @@ namespace NadekoBot.Modules.Help } helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} | {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} | {string.Format(com.Remarks, com.Module.GetPrefix())}"); } - helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser().Username , "@BotName"); + helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser.Username , "@BotName"); File.WriteAllText("../../docs/Commands List.md", helpstr.ToString()); await Context.Channel.SendConfirmAsync("Commandlist Regenerated").ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs index b2ae710c..decd705f 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs +++ b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs @@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Utility var channel = imsg.Channel as ITextChannel; if (channel == null) return; - if (msg.Author.Id == NadekoBot.Client.CurrentUser().Id) return; + if (msg.Author.Id == NadekoBot.Client.CurrentUser.Id) return; foreach (var subscriber in Subscribers) { var set = subscriber.Value; diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index bc413919..4730b433 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -277,7 +277,7 @@ namespace NadekoBot.Modules.Utility .WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg")) .AddField(efb => efb.WithName(Format.Bold("Author")).WithValue(stats.Author).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Library")).WithValue(stats.Library).WithIsInline(true)) - .AddField(efb => efb.WithName(Format.Bold("Bot ID")).WithValue(NadekoBot.Client.CurrentUser().Id.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName(Format.Bold("Bot ID")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Commands Ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true)) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index a0bce533..a79597af 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -28,7 +28,7 @@ namespace NadekoBot public static CommandService CommandService { get; private set; } public static CommandHandler CommandHandler { get; private set; } - public static ShardedDiscordClient Client { get; private set; } + public static DiscordShardedClient Client { get; private set; } public static BotCredentials Credentials { get; private set; } public static GoogleApiService Google { get; private set; } @@ -59,7 +59,7 @@ namespace NadekoBot _log.Info("Starting NadekoBot v" + StatsService.BotVersion); //create client - Client = new ShardedDiscordClient(new DiscordSocketConfig + Client = new DiscordShardedClient(new DiscordSocketConfig { AudioMode = Discord.Audio.AudioMode.Outgoing, MessageCacheSize = 10, @@ -68,6 +68,8 @@ namespace NadekoBot ConnectionTimeout = int.MaxValue }); + Client.Log += Client_Log; + //initialize Services CommandService = new CommandService(new CommandServiceConfig() { CaseSensitiveCommands = false @@ -114,6 +116,15 @@ namespace NadekoBot Console.WriteLine(await Stats.Print().ConfigureAwait(false)); } + private Task Client_Log(LogMessage arg) + { + _log.Warn(arg.Message); + if (arg.Exception != null) + _log.Warn(arg.Exception); + + return Task.CompletedTask; + } + public async Task RunAndBlockAsync(params string[] args) { await RunAsync(args).ConfigureAwait(false); diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index d0dc776b..bb1c54f1 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -32,7 +32,7 @@ namespace NadekoBot.Services { public const int GlobalCommandsCooldown = 1500; - private readonly ShardedDiscordClient _client; + private readonly DiscordShardedClient _client; private readonly CommandService _commandService; private readonly Logger _log; @@ -46,7 +46,7 @@ namespace NadekoBot.Services public ConcurrentHashSet UsersOnShortCooldown { get; } = new ConcurrentHashSet(); private Timer clearUsersOnShortCooldown { get; } - public CommandHandler(ShardedDiscordClient client, CommandService commandService) + public CommandHandler(DiscordShardedClient client, CommandService commandService) { _client = client; _commandService = commandService; diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 12e701e9..3424b7ba 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -1,4 +1,5 @@ using Discord; +using Discord.WebSocket; using NadekoBot.Extensions; using System; using System.Collections.Generic; @@ -11,7 +12,7 @@ namespace NadekoBot.Services.Impl { public class StatsService : IStatsService { - private ShardedDiscordClient client; + private DiscordShardedClient client; private DateTime started; public const string BotVersion = "1.1.0"; @@ -30,7 +31,7 @@ namespace NadekoBot.Services.Impl Timer carbonitexTimer { get; } - public StatsService(ShardedDiscordClient client, CommandHandler cmdHandler) + public StatsService(DiscordShardedClient client, CommandHandler cmdHandler) { this.client = client; @@ -39,15 +40,6 @@ namespace NadekoBot.Services.Impl this.client.MessageReceived += _ => Task.FromResult(MessageCounter++); cmdHandler.CommandExecuted += (_, e) => Task.FromResult(CommandsRan++); - this.client.Disconnected += _ => Reset(); - - this.client.Connected += () => - { - var guilds = this.client.GetGuilds(); - _textChannels = guilds.Sum(g => g.Channels.Where(cx => cx is ITextChannel).Count()); - _voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels; - }; - this.client.ChannelCreated += (c) => { if (c is ITextChannel) @@ -90,7 +82,7 @@ namespace NadekoBot.Services.Impl { using (var content = new FormUrlEncodedContent( new Dictionary { - { "servercount", this.client.GetGuildsCount().ToString() }, + { "servercount", this.client.GetGuildCount().ToString() }, { "key", NadekoBot.Credentials.CarbonKey }})) { content.Headers.Clear(); @@ -103,16 +95,24 @@ namespace NadekoBot.Services.Impl catch { } }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); } + + public void Initialize() + { + var guilds = this.client.GetGuilds(); + _textChannels = guilds.Sum(g => g.Channels.Where(cx => cx is ITextChannel).Count()); + _voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels; + } + public Task Print() { - var curUser = client.CurrentUser(); + var curUser = client.CurrentUser; return Task.FromResult($@" Author: [{Author}] | Library: [{Library}] Bot Version: [{BotVersion}] Bot ID: {curUser.Id} Owner ID(s): {string.Join(", ", NadekoBot.Credentials.OwnerIds)} Uptime: {GetUptimeString()} -Servers: {client.GetGuildsCount()} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels} +Servers: {client.GetGuildCount()} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels} Commands Ran this session: {CommandsRan} Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]"); } diff --git a/src/NadekoBot/ShardedDiscordClient.cs b/src/NadekoBot/ShardedDiscordClient.cs index 5d6e9fa3..268a9a54 100644 --- a/src/NadekoBot/ShardedDiscordClient.cs +++ b/src/NadekoBot/ShardedDiscordClient.cs @@ -107,7 +107,7 @@ namespace NadekoBot public DiscordSocketClient MainClient => Clients[0]; - public SocketSelfUser CurrentUser() => + public SocketSelfUser CurrentUser => Clients[0].CurrentUser; public IEnumerable GetGuilds() => @@ -182,22 +182,7 @@ namespace NadekoBot public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status)))); - private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) - { - switch (sus) - { - case SettableUserStatus.Online: - return UserStatus.Online; - case SettableUserStatus.Invisible: - return UserStatus.Invisible; - case SettableUserStatus.Idle: - return UserStatus.AFK; - case SettableUserStatus.Dnd: - return UserStatus.DoNotDisturb; - } - - return UserStatus.Online; - } + } public enum SettableUserStatus diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 634dd34a..d2dd36fc 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -203,7 +203,7 @@ namespace NadekoBot.Extensions await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false); public static bool IsAuthor(this IUserMessage msg) => - NadekoBot.Client.CurrentUser().Id == msg.Author.Id; + NadekoBot.Client.CurrentUser.Id == msg.Author.Id; public static IEnumerable Members(this IRole role) => role.Guild.GetUsersAsync().GetAwaiter().GetResult().Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty(); From c603efa6439103b30039ca9997563a534206f3bf Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 02:28:33 +0100 Subject: [PATCH 17/74] new sharded client --- .../Administration/Commands/LogCommand.cs | 28 +++++++++---------- .../Administration/Commands/MuteCommands.cs | 2 +- .../Commands/PlayingRotateCommands.cs | 2 +- .../Commands/ServerGreetCommands.cs | 4 +-- .../Commands/VoicePlusTextCommands.cs | 2 +- .../Modules/Gambling/Commands/AnimalRacing.cs | 8 +++--- .../Modules/Games/Commands/Acropobia.cs | 2 +- .../Games/Commands/Hangman/HangmanGame.cs | 2 +- .../Games/Commands/PlantAndPickCommands.cs | 2 +- .../Modules/Games/Commands/PollCommands.cs | 2 +- .../Games/Commands/SpeedTypingCommands.cs | 2 +- .../Games/Commands/Trivia/TriviaGame.cs | 2 +- src/NadekoBot/Modules/Music/Music.cs | 2 +- src/NadekoBot/Modules/Utility/Utility.cs | 2 +- src/NadekoBot/Services/CommandHandler.cs | 4 +-- .../Discord/SocketMessageEventWrapper.cs | 12 ++++++-- src/NadekoBot/Services/Impl/StatsService.cs | 8 ++++++ src/NadekoBot/ShardedDiscordClient.cs | 4 +-- 18 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 31dfbdc8..96ecee55 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -94,7 +94,7 @@ namespace NadekoBot.Modules.Administration MuteCommands.UserUnmuted += MuteCommands_UserUnmuted; } - private static async void _client_UserUpdated(SocketUser before, SocketUser uAfter) + private static async Task _client_UserUpdated(SocketUser before, SocketUser uAfter) { try { @@ -162,7 +162,7 @@ namespace NadekoBot.Modules.Administration { } } - private static async void _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) + private static async Task _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) { try { @@ -317,7 +317,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after) + private static async Task _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after) { try { @@ -360,7 +360,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_ChannelUpdated(IChannel cbefore, IChannel cafter) + private static async Task _client_ChannelUpdated(IChannel cbefore, IChannel cafter) { try { @@ -403,7 +403,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_ChannelDestroyed(IChannel ich) + private static async Task _client_ChannelDestroyed(IChannel ich) { try { @@ -430,7 +430,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_ChannelCreated(IChannel ich) + private static async Task _client_ChannelCreated(IChannel ich) { try { @@ -456,7 +456,7 @@ namespace NadekoBot.Modules.Administration catch (Exception ex) { _log.Warn(ex); } } - private static async void _client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) + private static async Task _client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState before, SocketVoiceState after) { try { @@ -498,7 +498,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) + private static async Task _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) { try { @@ -532,7 +532,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_UserLeft(IGuildUser usr) + private static async Task _client_UserLeft(IGuildUser usr) { try { @@ -556,7 +556,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_UserJoined(IGuildUser usr) + private static async Task _client_UserJoined(IGuildUser usr) { try { @@ -580,7 +580,7 @@ namespace NadekoBot.Modules.Administration catch (Exception ex) { _log.Warn(ex); } } - private static async void _client_UserUnbanned(IUser usr, IGuild guild) + private static async Task _client_UserUnbanned(IUser usr, IGuild guild) { try { @@ -604,7 +604,7 @@ namespace NadekoBot.Modules.Administration catch (Exception ex) { _log.Warn(ex); } } - private static async void _client_UserBanned(IUser usr, IGuild guild) + private static async Task _client_UserBanned(IUser usr, IGuild guild) { try { @@ -627,7 +627,7 @@ namespace NadekoBot.Modules.Administration catch (Exception ex) { _log.Warn(ex); } } - private static async void _client_MessageDeleted(ulong arg1, Optional imsg) + private static async Task _client_MessageDeleted(ulong arg1, Optional imsg) { try @@ -664,7 +664,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void _client_MessageUpdated(Optional optmsg, SocketMessage imsg2) + private static async Task _client_MessageUpdated(Optional optmsg, SocketMessage imsg2) { try { diff --git a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs b/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs index b4993bf2..fc69d1cb 100644 --- a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs @@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Administration _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); } - private static async void Client_UserJoined(IGuildUser usr) + private static async Task Client_UserJoined(IGuildUser usr) { try { diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index f8817db9..89eeb2c5 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -67,7 +67,7 @@ namespace NadekoBot.Modules.Administration public static Dictionary> PlayingPlaceholders { get; } = new Dictionary> { - {"%servers%", () => NadekoBot.Client.GetGuildsCount().ToString()}, + {"%servers%", () => NadekoBot.Client.GetGuildCount().ToString()}, {"%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()}, {"%playing%", () => { var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); diff --git a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs index 80b8c394..9fa0971c 100644 --- a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs @@ -26,7 +26,7 @@ namespace NadekoBot.Modules.Administration _log = LogManager.GetCurrentClassLogger(); } //todo optimize ASAP - private static async void UserLeft(IGuildUser user) + private static async Task UserLeft(IGuildUser user) { try { @@ -58,7 +58,7 @@ namespace NadekoBot.Modules.Administration catch { } } - private static async void UserJoined(IGuildUser user) + private static async Task UserJoined(IGuildUser user) { try { diff --git a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs index 1d76b1e7..a52a2c60 100644 --- a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs @@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); } - private static async void UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after) + private static async Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after) { var user = (iuser as SocketGuildUser); var guild = user?.Guild; diff --git a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs index c8e612f6..f2b6404c 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs @@ -207,15 +207,15 @@ namespace NadekoBot.Modules.Gambling } - private void Client_MessageReceived(SocketMessage imsg) + private Task Client_MessageReceived(SocketMessage imsg) { var msg = imsg as SocketUserMessage; if (msg == null) - return; + return Task.CompletedTask; if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel) - return; + return Task.CompletedTask; messagesSinceGameStarted++; - return; + return Task.CompletedTask; } private async Task CheckForFullGameAsync(CancellationToken cancelToken) diff --git a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs index 4160754b..2a7307bb 100644 --- a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs +++ b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs @@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Games await End().ConfigureAwait(false); } - private async void PotentialAcro(SocketMessage arg) + private async Task PotentialAcro(SocketMessage arg) { try { diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs index b209ecdc..4dcb54e9 100644 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs @@ -114,7 +114,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman await GameChannel.EmbedAsync(embed.WithOkColor()).ConfigureAwait(false); } - private async void PotentialGuess(SocketMessage msg) + private async Task PotentialGuess(SocketMessage msg) { try { diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 6bc9eb4e..3782fe78 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Games .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); } - private static async void PotentialFlowerGeneration(SocketMessage imsg) + private static async Task PotentialFlowerGeneration(SocketMessage imsg) { try { diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs index 01d0477c..3088546c 100644 --- a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs @@ -153,7 +153,7 @@ namespace NadekoBot.Modules.Games await originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false); } - private async void Vote(SocketMessage imsg) + private async Task Vote(SocketMessage imsg) { try { diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs index bb69063f..47724820 100644 --- a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs @@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Games NadekoBot.Client.MessageReceived += AnswerReceived; } - private async void AnswerReceived(SocketMessage imsg) + private async Task AnswerReceived(SocketMessage imsg) { try { diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index ce8f0ebc..e9469358 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -143,7 +143,7 @@ namespace NadekoBot.Modules.Games.Trivia try { await channel.SendConfirmAsync("Trivia Game", "Stopping after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } } - private async void PotentialGuess(SocketMessage imsg) + private async Task PotentialGuess(SocketMessage imsg) { try { diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index efdd9e6a..b27afe25 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -37,7 +37,7 @@ namespace NadekoBot.Modules.Music Directory.CreateDirectory(MusicDataPath); } - private static async void Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) + private static async Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) { var usr = iusr as SocketGuildUser; if (usr == null || diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 4730b433..ef075c78 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -283,7 +283,7 @@ namespace NadekoBot.Modules.Utility .AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Owner ID(s)")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true)) .AddField(efb => efb.WithName(Format.Bold("Uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true)) - .AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuildsCount()} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true)) + .AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuildCount()} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true)) #if !GLOBAL_NADEKO .WithFooter(efb => efb.WithText($"Playing {Music.Music.MusicPlayers.Where(mp => mp.Value.CurrentSong != null).Count()} songs, {Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)} queued.")) #endif diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index bb1c54f1..16bf8e89 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -180,7 +180,7 @@ namespace NadekoBot.Services return false; } - private async void MessageReceivedHandler(SocketMessage msg) + private async Task MessageReceivedHandler(SocketMessage msg) { try { @@ -228,7 +228,7 @@ namespace NadekoBot.Services string messageContent = usrMsg.Content; // execute the command and measure the time it took - var exec = await ExecuteCommand(new CommandContext(_client.MainClient, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); + var exec = await ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); execTime = Environment.TickCount - execTime; if (exec.Result.IsSuccess) diff --git a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs index b73961a0..57153ac2 100644 --- a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs +++ b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs @@ -26,7 +26,7 @@ namespace NadekoBot.Services.Discord NadekoBot.Client.ReactionsCleared += Discord_ReactionsCleared; } - private void Discord_ReactionsCleared(ulong messageId, Optional reaction) + private Task Discord_ReactionsCleared(ulong messageId, Optional reaction) { try { @@ -34,9 +34,11 @@ namespace NadekoBot.Services.Discord OnReactionsCleared?.Invoke(); } catch { } + + return Task.CompletedTask; } - private void Discord_ReactionRemoved(ulong messageId, Optional arg2, SocketReaction reaction) + private Task Discord_ReactionRemoved(ulong messageId, Optional arg2, SocketReaction reaction) { try { @@ -44,9 +46,11 @@ namespace NadekoBot.Services.Discord OnReactionRemoved?.Invoke(reaction); } catch { } + + return Task.CompletedTask; } - private void Discord_ReactionAdded(ulong messageId, Optional message, SocketReaction reaction) + private Task Discord_ReactionAdded(ulong messageId, Optional message, SocketReaction reaction) { try { @@ -54,6 +58,8 @@ namespace NadekoBot.Services.Discord OnReactionAdded?.Invoke(reaction); } catch { } + + return Task.CompletedTask; } public void UnsubAll() diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 3424b7ba..b65f3219 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -46,6 +46,8 @@ namespace NadekoBot.Services.Impl ++_textChannels; else if (c is IVoiceChannel) ++_voiceChannels; + + return Task.CompletedTask; }; this.client.ChannelDestroyed += (c) => @@ -54,6 +56,8 @@ namespace NadekoBot.Services.Impl --_textChannels; else if (c is IVoiceChannel) --_voiceChannels; + + return Task.CompletedTask; }; this.client.JoinedGuild += (g) => @@ -62,6 +66,8 @@ namespace NadekoBot.Services.Impl var vc = g.Channels.Count - tc; _textChannels += tc; _voiceChannels += vc; + + return Task.CompletedTask; }; this.client.LeftGuild += (g) => @@ -70,6 +76,8 @@ namespace NadekoBot.Services.Impl var vc = g.Channels.Count - tc; _textChannels -= tc; _voiceChannels -= vc; + + return Task.CompletedTask; }; this.carbonitexTimer = new Timer(async (state) => diff --git a/src/NadekoBot/ShardedDiscordClient.cs b/src/NadekoBot/ShardedDiscordClient.cs index 268a9a54..0a6e747a 100644 --- a/src/NadekoBot/ShardedDiscordClient.cs +++ b/src/NadekoBot/ShardedDiscordClient.cs @@ -180,9 +180,7 @@ namespace NadekoBot public Task SetStream(string name, string url) => Task.WhenAll(Clients.Select(ms => ms.SetGameAsync(name, url, StreamType.Twitch))); - public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status)))); - - + //public Task SetStatus(SettableUserStatus status) => Task.WhenAll(Clients.Select(ms => ms.SetStatusAsync(SettableUserStatusToUserStatus(status)))); } public enum SettableUserStatus From 0c58b14663b527855276c35b0efb8d58afd87047 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 02:29:44 +0100 Subject: [PATCH 18/74] Updated my discord.net fork --- Discord.Net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discord.Net b/Discord.Net index b9f76733..ac1fa80d 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit b9f767337d2b7c07ed76eb83c3bc5030109d5238 +Subproject commit ac1fa80d8ad07f5c7753279974efda29f4aca6f8 From 2715f36aa92c682e9dfca58033822f95b8222c74 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 02:45:20 +0100 Subject: [PATCH 19/74] Trivia error fix --- src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index e9469358..08e9a9c8 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -56,7 +56,9 @@ namespace NadekoBot.Modules.Games.Trivia // load question CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions); - if (CurrentQuestion == null) + if (CurrentQuestion == null || + string.IsNullOrWhiteSpace(CurrentQuestion.Answer) || + string.IsNullOrWhiteSpace(CurrentQuestion.Question)) { await channel.SendErrorAsync("Trivia Game", "Failed loading a question.").ConfigureAwait(false); return; @@ -74,7 +76,9 @@ namespace NadekoBot.Modules.Games.Trivia questionMessage = await channel.EmbedAsync(questionEmbed).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || + ex.StatusCode == System.Net.HttpStatusCode.Forbidden || + ex.StatusCode == System.Net.HttpStatusCode.BadRequest) { return; } From c04ff7ff94ecfd386d042b1d8ee6b7fd3ab43ff7 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 03:18:13 +0100 Subject: [PATCH 20/74] Auto pause/unpause fixes. !!mv disabled as it breaks music. Better logs --- .../Modules/Music/Classes/MusicControls.cs | 14 +++--- src/NadekoBot/Modules/Music/Music.cs | 44 +++++++++++++------ src/NadekoBot/NadekoBot.cs | 2 +- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs index b705a388..701bc937 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -342,13 +342,13 @@ namespace NadekoBot.Modules.Music.Classes }); } - public Task MoveToVoiceChannel(IVoiceChannel voiceChannel) - { - if (audioClient?.ConnectionState != ConnectionState.Connected) - throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); - PlaybackVoiceChannel = voiceChannel; - return PlaybackVoiceChannel.ConnectAsync(); - } + //public async Task MoveToVoiceChannel(IVoiceChannel voiceChannel) + //{ + // if (audioClient?.ConnectionState != ConnectionState.Connected) + // throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); + // PlaybackVoiceChannel = voiceChannel; + // audioClient = await voiceChannel.ConnectAsync().ConfigureAwait(false); + //} public bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index b27afe25..77e1608e 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -47,18 +47,36 @@ namespace NadekoBot.Modules.Music MusicPlayer player; if (!MusicPlayers.TryGetValue(usr.Guild.Id, out player)) return; + try { - var users = await player.PlaybackVoiceChannel.GetUsersAsync().Flatten().ConfigureAwait(false); + + + //if bot moved + if ((player.PlaybackVoiceChannel == oldState.VoiceChannel) && + usr.Id == NadekoBot.Client.CurrentUser.Id) + { + if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel + player.TogglePause(); + else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel + player.TogglePause(); + + return; + } + + + //if some other user moved if ((player.PlaybackVoiceChannel == newState.VoiceChannel && //if joined first, and player paused, unpause player.Paused && - users.Count() == 2) || // keep in mind bot is in the channel (+1) + newState.VoiceChannel.Users.Count == 2) || // keep in mind bot is in the channel (+1) (player.PlaybackVoiceChannel == oldState.VoiceChannel && // if left last, and player unpaused, pause !player.Paused && - users.Count() == 1)) + oldState.VoiceChannel.Users.Count == 1)) { player.TogglePause(); + return; } + } catch { } } @@ -448,17 +466,17 @@ namespace NadekoBot.Modules.Music } - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Move() - { + //[NadekoCommand, Usage, Description, Aliases] + //[RequireContext(ContextType.Guild)] + //public async Task Move() + //{ - MusicPlayer musicPlayer; - var voiceChannel = ((IGuildUser)Context.User).VoiceChannel; - if (voiceChannel == null || voiceChannel.Guild != Context.Guild || !MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) - return; - await musicPlayer.MoveToVoiceChannel(voiceChannel); - } + // MusicPlayer musicPlayer; + // var voiceChannel = ((IGuildUser)Context.User).VoiceChannel; + // if (voiceChannel == null || voiceChannel.Guild != Context.Guild || !MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) + // return; + // await musicPlayer.MoveToVoiceChannel(voiceChannel); + //} [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index a79597af..4b1eb8ec 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -118,7 +118,7 @@ namespace NadekoBot private Task Client_Log(LogMessage arg) { - _log.Warn(arg.Message); + _log.Warn(arg.Source + " | " + arg.Message); if (arg.Exception != null) _log.Warn(arg.Exception); From 9051e44125addbf46ff3499bcb893c2ff434951f Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 03:35:13 +0100 Subject: [PATCH 21/74] Stats initialize --- src/NadekoBot/NadekoBot.cs | 1 + src/NadekoBot/ShardedDiscordClient.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 4b1eb8ec..f6e4c89d 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -95,6 +95,7 @@ namespace NadekoBot //connect await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false); await Client.ConnectAsync().ConfigureAwait(false); + Stats.Initialize(); #if !GLOBAL_NADEKO await Client.DownloadAllUsersAsync().ConfigureAwait(false); #endif diff --git a/src/NadekoBot/ShardedDiscordClient.cs b/src/NadekoBot/ShardedDiscordClient.cs index 0a6e747a..daa5276e 100644 --- a/src/NadekoBot/ShardedDiscordClient.cs +++ b/src/NadekoBot/ShardedDiscordClient.cs @@ -60,7 +60,8 @@ namespace NadekoBot client.MessageReceived += arg1 => { if (arg1.Author == null || arg1.Author.IsBot) - return Task.CompletedTask; MessageReceived(arg1); + return Task.CompletedTask; + MessageReceived(arg1); return Task.CompletedTask; }; client.UserLeft += arg1 => { UserLeft(arg1); return Task.CompletedTask; }; From 33687d8e390e1e42859b57446a225e6c6ccf6b23 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 03:36:28 +0100 Subject: [PATCH 22/74] updated discord.net fork --- Discord.Net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discord.Net b/Discord.Net index ac1fa80d..9155ac02 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit ac1fa80d8ad07f5c7753279974efda29f4aca6f8 +Subproject commit 9155ac02ee245193825aa307fe43ed25eac9a45c From 9052d96f227b366f8be7a690f092539f795476f8 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 13:45:14 +0100 Subject: [PATCH 23/74] nsfw perf improvements. ~hentaibomb has 5sec cooldown. events trigger in parallel --- Discord.Net | 2 +- .../Administration/Commands/MuteCommands.cs | 6 +- src/NadekoBot/Modules/NSFW/NSFW.cs | 66 +++++++++++-------- src/NadekoBot/Modules/Searches/Searches.cs | 37 ++++++----- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Discord.Net b/Discord.Net index 9155ac02..e9dca6c6 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit 9155ac02ee245193825aa307fe43ed25eac9a45c +Subproject commit e9dca6c648b23bd9e957d8f9eee516df6ce11091 diff --git a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs b/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs index fc69d1cb..f37a26ec 100644 --- a/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/MuteCommands.cs @@ -37,8 +37,7 @@ namespace NadekoBot.Modules.Administration static MuteCommands() { var _log = LogManager.GetCurrentClassLogger(); - var sw = Stopwatch.StartNew(); - + var configs = NadekoBot.AllGuildConfigs; GuildMuteRoles = new ConcurrentDictionary(configs .Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName)) @@ -50,9 +49,6 @@ namespace NadekoBot.Modules.Administration )); NadekoBot.Client.UserJoined += Client_UserJoined; - - sw.Stop(); - _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); } private static async Task Client_UserJoined(IGuildUser usr) diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 4db4c1b3..ae9f8568 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -20,6 +20,7 @@ namespace NadekoBot.Modules.NSFW public class NSFW : DiscordModule { private static ConcurrentDictionary AutoHentaiTimers { get; } = new ConcurrentDictionary(); + private static ConcurrentHashSet _hentaiBombBlacklist { get; } = new ConcurrentHashSet(); private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) { @@ -56,7 +57,8 @@ namespace NadekoBot.Modules.NSFW await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithImageUrl(link) - .WithDescription("Tag: " + tag)).ConfigureAwait(false); + .WithDescription("Tag: " + tag)) + .ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -90,7 +92,7 @@ namespace NadekoBot.Modules.NSFW if (tagsArr == null || tagsArr.Length == 0) await InternalHentai(Context.Channel, null, true).ConfigureAwait(false); else - await InternalHentai(Context.Channel, tagsArr[new NadekoRandom().Next(0, tagsArr.Length)], true); + await InternalHentai(Context.Channel, tagsArr[new NadekoRandom().Next(0, tagsArr.Length)], true).ConfigureAwait(false); } catch { } }, null, interval * 1000, interval * 1000); @@ -101,29 +103,39 @@ namespace NadekoBot.Modules.NSFW return t; }); - await Context.Channel.SendConfirmAsync($"Autohentai started. Reposting every {interval}s with one of the following tags:\n{string.Join(", ", tagsArr)}").ConfigureAwait(false); + await Context.Channel.SendConfirmAsync($"Autohentai started. Reposting every {interval}s with one of the following tags:\n{string.Join(", ", tagsArr)}") + .ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] public async Task HentaiBomb([Remainder] string tag = null) { - tag = tag?.Trim() ?? ""; - tag = "rating%3Aexplicit+" + tag; - - var links = await Task.WhenAll(GetGelbooruImageLink(tag), - GetDanbooruImageLink(tag), - GetKonachanImageLink(tag), - GetYandereImageLink(tag)).ConfigureAwait(false); - - var linksEnum = links?.Where(l => l != null); - if (links == null || !linksEnum.Any()) - { - await Context.Channel.SendErrorAsync("No results found.").ConfigureAwait(false); + if (!_hentaiBombBlacklist.Add(Context.User.Id)) return; - } + try + { + tag = tag?.Trim() ?? ""; + tag = "rating%3Aexplicit+" + tag; - await Context.Channel.SendMessageAsync(String.Join("\n\n", linksEnum)).ConfigureAwait(false); + var links = await Task.WhenAll(GetGelbooruImageLink(tag), + GetDanbooruImageLink(tag), + GetKonachanImageLink(tag), + GetYandereImageLink(tag)).ConfigureAwait(false); + + var linksEnum = links?.Where(l => l != null); + if (links == null || !linksEnum.Any()) + { + await Context.Channel.SendErrorAsync("No results found.").ConfigureAwait(false); + return; + } + + await Context.Channel.SendMessageAsync(String.Join("\n\n", linksEnum)).ConfigureAwait(false); + } + finally { + await Task.Delay(5000).ConfigureAwait(false); + _hentaiBombBlacklist.TryRemove(Context.User.Id); + } } @@ -135,12 +147,13 @@ namespace NadekoBot.Modules.NSFW var url = await GetDanbooruImageLink(tag).ConfigureAwait(false); if (url == null) - await Context.Channel.SendErrorAsync(Context.User.Mention + " No results."); + await Context.Channel.SendErrorAsync(Context.User.Mention + " No results.").ConfigureAwait(false); else await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithDescription(Context.User.Mention + " " + tag) .WithImageUrl(url) - .WithFooter(efb => efb.WithText("Danbooru"))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText("Danbooru"))) + .ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -172,7 +185,8 @@ namespace NadekoBot.Modules.NSFW await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithDescription(Context.User.Mention + " " + tag) .WithImageUrl(url) - .WithFooter(efb => efb.WithText("e621"))).ConfigureAwait(false); + .WithFooter(efb => efb.WithText("e621"))) + .ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] @@ -217,14 +231,14 @@ namespace NadekoBot.Modules.NSFW } } - public static async Task GetDanbooruImageLink(string tag) + public static Task GetDanbooruImageLink(string tag) => Task.Run(async () => { try { using (var http = new HttpClient()) { http.AddFakeHeaders(); - var data = await http.GetStreamAsync("https://danbooru.donmai.us/posts.xml?limit=100&tags=" + tag); + var data = await http.GetStreamAsync("https://danbooru.donmai.us/posts.xml?limit=100&tags=" + tag).ConfigureAwait(false); var doc = new XmlDocument(); doc.Load(data); var nodes = doc.GetElementsByTagName("file-url"); @@ -237,17 +251,17 @@ namespace NadekoBot.Modules.NSFW { return null; } - } + }); - public static async Task GetE621ImageLink(string tag) + public static Task GetE621ImageLink(string tag) => Task.Run(async () => { try { using (var http = new HttpClient()) { http.AddFakeHeaders(); - var data = await http.GetStreamAsync("http://e621.net/post/index.xml?tags=" + tag); + var data = await http.GetStreamAsync("http://e621.net/post/index.xml?tags=" + tag).ConfigureAwait(false); var doc = new XmlDocument(); doc.Load(data); var nodes = doc.GetElementsByTagName("file_url"); @@ -260,7 +274,7 @@ namespace NadekoBot.Modules.NSFW { return null; } - } + }); public static Task GetYandereImageLink(string tag) => Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Yandere); diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 16b67e18..60bebedf 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -517,7 +517,8 @@ namespace NadekoBot.Modules.Searches .WithAuthor(eab => eab.WithUrl(link) .WithIconUrl("http://res.cloudinary.com/urbandictionary/image/upload/a_exif,c_fit,h_200,w_200/v1394975045/b8oszuu3tbq7ebyo7vo1.jpg") .WithName(query)) - .WithDescription(desc)); + .WithDescription(desc)) + .ConfigureAwait(false); } catch { @@ -572,9 +573,9 @@ namespace NadekoBot.Modules.Searches var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)); var data = JsonConvert.DeserializeObject(result); if (data.Query.Pages[0].Missing) - await Context.Channel.SendErrorAsync("That page could not be found."); + await Context.Channel.SendErrorAsync("That page could not be found.").ConfigureAwait(false); else - await Context.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl); + await Context.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl).ConfigureAwait(false); } } @@ -588,7 +589,7 @@ namespace NadekoBot.Modules.Searches img.BackgroundColor(new ImageSharp.Color(color)); - await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png"); + await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png").ConfigureAwait(false); ; } [NadekoCommand, Usage, Description, Aliases] @@ -642,7 +643,7 @@ namespace NadekoBot.Modules.Searches var response = $@"`Title:` {found["title"].ToString()} `Quality:` {found["quality"]} `URL:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}"; - await Context.Channel.SendMessageAsync(response); + await Context.Channel.SendMessageAsync(response).ConfigureAwait(false); } catch { @@ -774,20 +775,24 @@ namespace NadekoBot.Modules.Searches } try { - using (var http = new HttpClient()) + var toReturn = await Task.Run(async () => { - http.AddFakeHeaders(); - var data = await http.GetStreamAsync(website); - var doc = new XmlDocument(); - doc.Load(data); + using (var http = new HttpClient()) + { + http.AddFakeHeaders(); + var data = await http.GetStreamAsync(website).ConfigureAwait(false); + var doc = new XmlDocument(); + doc.Load(data); - var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)]; + var node = doc.LastChild.ChildNodes[new NadekoRandom().Next(0, doc.LastChild.ChildNodes.Count)]; - var url = node.Attributes["file_url"].Value; - if (!url.StartsWith("http")) - url = "https:" + url; - return url; - } + var url = node.Attributes["file_url"].Value; + if (!url.StartsWith("http")) + url = "https:" + url; + return url; + } + }).ConfigureAwait(false); + return toReturn; } catch { From 228c6b57cfd7070a915761c023af7d1c061735d1 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 14:28:06 +0100 Subject: [PATCH 24/74] version upped to 1.1.1 --- src/NadekoBot/Services/Impl/StatsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index b65f3219..86955b83 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl private DiscordShardedClient client; private DateTime started; - public const string BotVersion = "1.1.0"; + public const string BotVersion = "1.1.1"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From 33d810f03602927b8efe00cc3cd8aea030ec830f Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 15:05:17 +0100 Subject: [PATCH 25/74] $startevent added --- .../Resources/CommandStrings.Designer.cs | 27 +++++++++++++++++++ src/NadekoBot/Resources/CommandStrings.resx | 9 +++++++ 2 files changed, 36 insertions(+) diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 0d4f9d0a..717d767e 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -7295,6 +7295,33 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to startevent. + /// + public static string startevent_cmd { + get { + return ResourceManager.GetString("startevent_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts one of the events seen on public nadeko.. + /// + public static string startevent_desc { + get { + return ResourceManager.GetString("startevent_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}startevent flowerreaction`. + /// + public static string startevent_usage { + get { + return ResourceManager.GetString("startevent_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to startwar sw. /// diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 5599995f..1e9ba5d4 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -2943,4 +2943,13 @@ `{0}cmdcost 0 !!q` or `{0}cmdcost 1 >8ball` + + startevent + + + Starts one of the events seen on public nadeko. + + + `{0}startevent flowerreaction` + \ No newline at end of file From d8d657e6bd9569891144e529297c2cbb8abb2a20 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 15:09:48 +0100 Subject: [PATCH 26/74] Flower event is prettier --- .../Gambling/Commands/CurrencyEvents.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs new file mode 100644 index 00000000..9b43f842 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -0,0 +1,67 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class CurrencyEvents : ModuleBase + { + public enum CurrencyEvent + { + FlowerReaction + } + //flower reaction event + public static readonly ConcurrentHashSet _flowerReactionAwardedUsers = new ConcurrentHashSet(); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task StartEvent(CurrencyEvent e) + { + var channel = (ITextChannel)Context.Channel; + + switch (e) + { + case CurrencyEvent.FlowerReaction: + await FlowerReactionEvent(Context).ConfigureAwait(false); + break; + default: + break; + } + } + + + public static async Task FlowerReactionEvent(CommandContext Context) + { + var msg = await Context.Channel.SendConfirmAsync("Flower reaction event started!", + "Add 🌸 reaction to this message to get 100" + NadekoBot.BotConfig.CurrencySign, + footer: "This event is active for 24 hours.") + .ConfigureAwait(false); + await msg.AddReactionAsync("🌸").ConfigureAwait(false); + using (msg.OnReaction(async (r) => + { + if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + { + try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, true).ConfigureAwait(false); } catch { } + } + })) + { + await Task.Delay(TimeSpan.FromHours(24)).ConfigureAwait(false); + try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } + _flowerReactionAwardedUsers.Clear(); + } + } + } + } +} From 257dbcb73824c312a5f970ee5ca76b54bc9bdd6b Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 18:04:22 +0100 Subject: [PATCH 27/74] Fixed flower event hopefully. --- .../Gambling/Commands/CurrencyEvents.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs index 9b43f842..d7d2d783 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -30,15 +30,19 @@ namespace NadekoBot.Modules.Gambling public async Task StartEvent(CurrencyEvent e) { var channel = (ITextChannel)Context.Channel; - - switch (e) + try { - case CurrencyEvent.FlowerReaction: - await FlowerReactionEvent(Context).ConfigureAwait(false); - break; - default: - break; + + switch (e) + { + case CurrencyEvent.FlowerReaction: + await FlowerReactionEvent(Context).ConfigureAwait(false); + break; + default: + break; + } } + catch { } } @@ -48,13 +52,26 @@ namespace NadekoBot.Modules.Gambling "Add 🌸 reaction to this message to get 100" + NadekoBot.BotConfig.CurrencySign, footer: "This event is active for 24 hours.") .ConfigureAwait(false); - await msg.AddReactionAsync("🌸").ConfigureAwait(false); + try { await msg.AddReactionAsync("🌸").ConfigureAwait(false); } + catch + { + try { await msg.AddReactionAsync("🌸").ConfigureAwait(false); } + catch + { + try { await msg.DeleteAsync().ConfigureAwait(false); } + catch { } + } + } using (msg.OnReaction(async (r) => { - if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + try { - try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, true).ConfigureAwait(false); } catch { } + if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + { + try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, true).ConfigureAwait(false); } catch { } + } } + catch { } })) { await Task.Delay(TimeSpan.FromHours(24)).ConfigureAwait(false); From d65b415ee8842fbd9994545407f4b3a4eda83058 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 18:25:53 +0100 Subject: [PATCH 28/74] Music warning removed --- src/NadekoBot/Modules/Music/Music.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 77e1608e..0cb28121 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -37,17 +37,17 @@ namespace NadekoBot.Modules.Music Directory.CreateDirectory(MusicDataPath); } - private static async Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) + private static Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState) { var usr = iusr as SocketGuildUser; if (usr == null || oldState.VoiceChannel == newState.VoiceChannel) - return; + return Task.CompletedTask; MusicPlayer player; if (!MusicPlayers.TryGetValue(usr.Guild.Id, out player)) - return; - + return Task.CompletedTask; + try { @@ -61,7 +61,7 @@ namespace NadekoBot.Modules.Music else if (!player.Paused && newState.VoiceChannel.Users.Count <= 1) // pause if there are no users in the new channel player.TogglePause(); - return; + return Task.CompletedTask; } @@ -74,11 +74,12 @@ namespace NadekoBot.Modules.Music oldState.VoiceChannel.Users.Count == 1)) { player.TogglePause(); - return; + return Task.CompletedTask; } } catch { } + return Task.CompletedTask; } [NadekoCommand, Usage, Description, Aliases] From a3fd6442b7e0abcd756d06d0218525e97e1d7a34 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 18:56:18 +0100 Subject: [PATCH 29/74] download users --- src/NadekoBot/NadekoBot.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index f6e4c89d..85246fa3 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -95,10 +95,8 @@ namespace NadekoBot //connect await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false); await Client.ConnectAsync().ConfigureAwait(false); - Stats.Initialize(); -#if !GLOBAL_NADEKO await Client.DownloadAllUsersAsync().ConfigureAwait(false); -#endif + Stats.Initialize(); _log.Info("Connected"); @@ -107,6 +105,7 @@ namespace NadekoBot ModulePrefixes = new ConcurrentDictionary(NadekoBot.BotConfig.ModulePrefixes.OrderByDescending(mp => mp.Prefix.Length).ToDictionary(m => m.ModuleName, m => m.Prefix)); // start handling messages received in commandhandler + await CommandHandler.StartHandling().ConfigureAwait(false); await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly).ConfigureAwait(false); From bbdcfb3a16bfe36892b25c7c724d24b092f1d807 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 18:57:01 +0100 Subject: [PATCH 30/74] Update discord.net --- Discord.Net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discord.Net b/Discord.Net index e9dca6c6..f27cce08 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit e9dca6c648b23bd9e957d8f9eee516df6ce11091 +Subproject commit f27cce0854abc42b92d29f9040625238a8991999 From c80e1e07973a5256ddbfe8c976d03fe7e2a66919 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 19:18:16 +0100 Subject: [PATCH 31/74] Don't send messages on event --- src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs index d7d2d783..4dfa935f 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Gambling { if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) { - try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, true).ConfigureAwait(false); } catch { } + try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false).ConfigureAwait(false); } catch { } } } catch { } From 8593127b1bd2c44aef605b40f0804f8fb677e145 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 15 Jan 2017 21:41:35 +0100 Subject: [PATCH 32/74] No logging on self hosted bots --- src/NadekoBot/NadekoBot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 85246fa3..c8084905 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -67,8 +67,9 @@ namespace NadekoBot TotalShards = Credentials.TotalShards, ConnectionTimeout = int.MaxValue }); - +#if GLOBAL_NADEKO Client.Log += Client_Log; +#endif //initialize Services CommandService = new CommandService(new CommandServiceConfig() { From 42f7cc9a315f72566379d3c74ac63fc185e7e96c Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 14:15:24 +0100 Subject: [PATCH 33/74] Fixed empty log spam --- src/NadekoBot/Modules/Administration/Commands/LogCommand.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs index 96ecee55..db339d4f 100644 --- a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -397,6 +397,8 @@ namespace NadekoBot.Modules.Administration .AddField(efb => efb.WithName("Old Topic").WithValue(beforeTextChannel.Topic)) .AddField(efb => efb.WithName("New Topic").WithValue(afterTextChannel.Topic)); } + else + return; await logChannel.EmbedAsync(embed).ConfigureAwait(false); } From 619e83e0be78b0d638cd5bf5ee35572e1b3c448a Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 14:27:39 +0100 Subject: [PATCH 34/74] Repeater will stop if channel was deleted or bot can't write to that channel --- .../Utility/Commands/MessageRepeater.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs index 71bd6748..619b71fa 100644 --- a/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs +++ b/src/NadekoBot/Modules/Utility/Commands/MessageRepeater.cs @@ -1,5 +1,6 @@ using Discord; using Discord.Commands; +using Discord.Net; using Microsoft.EntityFrameworkCore; using NadekoBot.Attributes; using NadekoBot.Extensions; @@ -64,7 +65,24 @@ namespace NadekoBot.Modules.Utility if (oldMsg != null) try { await oldMsg.DeleteAsync(); } catch { } - try { oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + try + { + oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + _log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id); + return; + } + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + _log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id); + return; + } + catch (Exception ex) + { + _log.Warn(ex); + } } } catch (OperationCanceledException) { } From f49a64d7a427fce52c445326c3f19cb47a18323a Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 14:46:17 +0100 Subject: [PATCH 35/74] fixed role changes --- Discord.Net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Discord.Net b/Discord.Net index f27cce08..58766448 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit f27cce0854abc42b92d29f9040625238a8991999 +Subproject commit 58766448d79ac9adec228f341f258aa262a3f278 From 1358b2b524a8fc69ffeec24a4a740ff316872c7b Mon Sep 17 00:00:00 2001 From: samvaio Date: Mon, 16 Jan 2017 19:31:43 +0530 Subject: [PATCH 36/74] Update Windows Guide.md --- docs/guides/Windows Guide.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/guides/Windows Guide.md b/docs/guides/Windows Guide.md index 1f996b1c..0d569909 100644 --- a/docs/guides/Windows Guide.md +++ b/docs/guides/Windows Guide.md @@ -16,8 +16,8 @@ ________________________________________________________________________________ ####Guide - Make sure you have installed both [Git][Git] and the [.NET Core SDK][.NET Core SDK]. - Create a **new folder** anywhere you like and name it `Nadeko`. -- Next, [Right-Click on this link](https://github.com/Kwoth/NadekoBotInstallerWin/raw/master/NadekoInstaller.bat) and select **Save link as** and save the file `NadekoInstaller.bat` inside the `Nadeko` folder that we created earlier. (**DO NOT** rename the file `NadekoInstaller.bat`) -- Once that's done, double-click on `NadekoInstaller.bat` to run it. +- Next, [Right-Click on this link](https://github.com/Kwoth/NadekoBotInstallerWin/raw/master/NadekoInstaller.bat) and select **Save link as** and save the file `NadekoInstaller.bat` inside the `Nadeko` folder that we created earlier. (Please **DO NOT** rename the file `NadekoInstaller.bat`.) +- Once that's done, right-click on `NadekoInstaller.bat` to run it as Administrator. - From the options, - Choose `1` to get the **most recent build**. - Choose `2` to get the **stable build**. @@ -58,7 +58,7 @@ ________________________________________________________________________________ - The bot should have been added to your server. ####Starting the bot -- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file. +- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file as Administrator. - From the options, - Choose `3` to **run the bot normally**. (with normal-run the bot will shutdown and will stay offline if it disconnects by the use of `.die` command until you manually run it again. Useful if you want to test the bot.) @@ -83,6 +83,16 @@ ________________________________________________________________________________ In order to have a functioning music module, you need to install ffmpeg and setup api keys. +#### Setting up `ffmpeg` using NadekoBot Client! +- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file as Administrator. +- From the options select `6` Install ffmpeg (for music) +- Next, **Press Any Key** if you are running as Administrator or just close and relaunch it as Administrator using mouse right-click. +- Wait for it to finish installing and backing up existing. +- Once done, you should see "ffmpeg Installation complete!". +- Next, **Press Any Key** to go back to NadekoBot Client. +- Press `3` to run the bot normally just to test music. (optional) +- `ffmpeg` installation for Music is now complete. + #### Manual `ffmpeg` setup - Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide) - Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build) From 7188f901ccad1c9da0e478560d2e4ef736e01229 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 15:12:51 +0100 Subject: [PATCH 37/74] Fixed !!lq when there are radio links in the queue --- src/NadekoBot/Modules/Music/Classes/MusicControls.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs index 701bc937..6d101975 100644 --- a/src/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -43,7 +43,12 @@ namespace NadekoBot.Modules.Music.Classes /// public uint MaxPlaytimeSeconds { get; set; } = 0; - public TimeSpan TotalPlaytime => new TimeSpan(playlist.Sum(s => s.TotalTime.Ticks)); + + // this should be written better + public TimeSpan TotalPlaytime => + playlist.Any(s => s.TotalTime == TimeSpan.MaxValue) ? + TimeSpan.MaxValue : + new TimeSpan(playlist.Sum(s => s.TotalTime.Ticks)); /// /// Users who recently got their music wish From 8e7d697bb24b0d37f021e3dcc14be20951646476 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 15:13:07 +0100 Subject: [PATCH 38/74] woops --- src/NadekoBot/Modules/Music/Music.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 0cb28121..352821c5 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -204,6 +204,7 @@ namespace NadekoBot.Modules.Music const int itemsPerPage = 10; var total = musicPlayer.TotalPlaytime; + var totalStr = total == TimeSpan.MaxValue ? "∞" : $"{(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s"; var maxPlaytime = musicPlayer.MaxPlaytimeSeconds; var lastPage = musicPlayer.Playlist.Count / itemsPerPage; Func printAction = (curPage) => @@ -218,7 +219,7 @@ namespace NadekoBot.Modules.Music .Take(itemsPerPage) .Select(v => $"`{++number}.` {v.PrettyFullName}"))) .WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + - $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s | " + + $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " + (musicPlayer.FairPlay ? "✔️fairplay" : "✖️fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit"))) .WithOkColor(); From 9a81de1b457cfe0e82e8e3860dab8a6669de19f0 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 15:21:10 +0100 Subject: [PATCH 39/74] gifs now work in >plant, >gc and $bf --- .../Modules/Gambling/Commands/FlipCoinCommand.cs | 2 +- .../Modules/Games/Commands/PlantAndPickCommands.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs b/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs index 6cb49f71..85092dc7 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs @@ -93,7 +93,7 @@ namespace NadekoBot.Modules.Gambling str = $"{Context.User.Mention}`Better luck next time.`"; } - await Context.Channel.SendFileAsync(File.Open(imgPathToSend, FileMode.OpenOrCreate), "coin.jpg", str).ConfigureAwait(false); + await Context.Channel.SendFileAsync(File.Open(imgPathToSend, FileMode.OpenOrCreate), new FileInfo(imgPathToSend).Name, str).ConfigureAwait(false); } } } diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 3782fe78..272ced2a 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -93,10 +93,10 @@ namespace NadekoBot.Modules.Games { firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!"; } - + var file = GetRandomCurrencyImagePath(); var sent = await channel.SendFileAsync( - File.Open(GetRandomCurrencyImagePath(), FileMode.OpenOrCreate), - "RandomFlower.jpg", + File.Open(file, FileMode.OpenOrCreate), + new FileInfo(file).Name, $"❗ {firstPart} Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`") .ConfigureAwait(false); @@ -167,7 +167,7 @@ namespace NadekoBot.Modules.Games } else { - msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), "plant.jpg", msgToSend).ConfigureAwait(false); + msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), new FileInfo(file).Name, msgToSend).ConfigureAwait(false); } plantedFlowers.AddOrUpdate(Context.Channel.Id, new List() { msg }, (id, old) => { old.Add(msg); return old; }); } From 35d2ada373cfdeee05187841ab28b3d6dc13653c Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 15:25:50 +0100 Subject: [PATCH 40/74] Don't download users. It breaks the bot for some people?!?!??! --- src/NadekoBot/NadekoBot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index c8084905..a019476a 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -96,7 +96,7 @@ namespace NadekoBot //connect await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false); await Client.ConnectAsync().ConfigureAwait(false); - await Client.DownloadAllUsersAsync().ConfigureAwait(false); + //await Client.DownloadAllUsersAsync().ConfigureAwait(false); Stats.Initialize(); _log.Info("Connected"); From df05f7dae945249e2cc87ec20f1215c42e399df2 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 16 Jan 2017 15:28:47 +0100 Subject: [PATCH 41/74] Upped version to 1.1.2 --- src/NadekoBot/Services/Impl/StatsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 86955b83..7e3f4406 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl private DiscordShardedClient client; private DateTime started; - public const string BotVersion = "1.1.1"; + public const string BotVersion = "1.1.2"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From c04e376492c8298bad82e92cc214830455cebc2c Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 17 Jan 2017 03:23:13 +0100 Subject: [PATCH 42/74] autohentai requires manage messages permission --- src/NadekoBot/Modules/NSFW/NSFW.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index ae9f8568..8e520c6c 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -66,6 +66,7 @@ namespace NadekoBot.Modules.NSFW InternalHentai(Context.Channel, tag, false); [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(ChannelPermission.ManageMessages)] public async Task AutoHentai(int interval = 0, string tags = null) { Timer t; From 4f07f9efd28a365a780ae6249c96a04f09373af6 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 17 Jan 2017 03:26:24 +0100 Subject: [PATCH 43/74] Fixed queue error message when user is not in a voice channel --- src/NadekoBot/Modules/Music/Music.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 352821c5..868a0885 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -805,7 +805,7 @@ namespace NadekoBot.Modules.Music if (voiceCh == null || voiceCh.Guild != textCh.Guild) { if (!silent) - await textCh.SendErrorAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice (ITextChannel)Context.Channel, try rejoining.").ConfigureAwait(false); + await textCh.SendErrorAsync($"💢 You need to be in a voice channel on this server.").ConfigureAwait(false); throw new ArgumentNullException(nameof(voiceCh)); } if (string.IsNullOrWhiteSpace(query) || query.Length < 3) From 9cb9529b14e7aa6fec22003e0594d0b476c370f3 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 01:42:28 +0100 Subject: [PATCH 44/74] .uinfo is no longer COUNTING the everyone role --- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 8814c50c..f8b5c7ff 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Utility embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count})** - {string.Join(", ", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join(", ", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) .WithThumbnailUrl(user.AvatarUrl) .WithColor(NadekoBot.OkColor); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); From 8bde87c071065adc7dad9dc1c6b8a05888a0ea80 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 02:44:48 +0100 Subject: [PATCH 45/74] .sinfo shows both names of the emojis and actual emojis --- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index f8b5c7ff..0ab94ada 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -34,6 +34,9 @@ namespace NadekoBot.Modules.Utility var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(guild.Id >> 22); var sb = new StringBuilder(); var users = await guild.GetUsersAsync().ConfigureAwait(false); + var features = string.Join("\n", guild.Features); + if (string.IsNullOrWhiteSpace(features)) + features = "-"; var embed = new EmbedBuilder() .WithAuthor(eab => eab.WithName("Server Info")) .WithTitle(guild.Name) @@ -45,11 +48,12 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true)) + .AddField(fb => fb.WithName("**Features**").WithValue(features).WithIsInline(true)) .WithImageUrl(guild.IconUrl) .WithColor(NadekoBot.OkColor); if (guild.Emojis.Count() > 0) { - embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(Format.Italics(string.Join(", ", guild.Emojis))).WithIsInline(true)); + embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(Format.Italics(string.Join(" | ", guild.Emojis.Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))))); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } @@ -92,7 +96,7 @@ namespace NadekoBot.Modules.Utility embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join(", ", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) .WithThumbnailUrl(user.AvatarUrl) .WithColor(NadekoBot.OkColor); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); From 6861965c5b0c3e41fa79f58e023b66bb5b79b3d3 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 05:48:15 +0100 Subject: [PATCH 46/74] sinfo custom emojis no longer italic --- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 0ab94ada..eaea37f0 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Utility .WithColor(NadekoBot.OkColor); if (guild.Emojis.Count() > 0) { - embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(Format.Italics(string.Join(" | ", guild.Emojis.Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))))); + embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(string.Join(" ", guild.Emojis.Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } From 4141ab4a9e29bd42bf06aacf8f1f92f83fee5cf5 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 05:49:12 +0100 Subject: [PATCH 47/74] $slot, $slottest and $slotstats added --- .../Modules/Gambling/Commands/Slots.cs | 301 ++++++++++++++++++ .../Resources/CommandStrings.Designer.cs | 81 +++++ src/NadekoBot/Resources/CommandStrings.resx | 27 ++ src/NadekoBot/data/slots/0.png | Bin 0 -> 551 bytes src/NadekoBot/data/slots/1.png | Bin 0 -> 392 bytes src/NadekoBot/data/slots/2.png | Bin 0 -> 553 bytes src/NadekoBot/data/slots/3.png | Bin 0 -> 539 bytes src/NadekoBot/data/slots/4.png | Bin 0 -> 471 bytes src/NadekoBot/data/slots/5.png | Bin 0 -> 542 bytes src/NadekoBot/data/slots/6.png | Bin 0 -> 562 bytes src/NadekoBot/data/slots/7.png | Bin 0 -> 417 bytes src/NadekoBot/data/slots/8.png | Bin 0 -> 597 bytes src/NadekoBot/data/slots/9.png | Bin 0 -> 578 bytes src/NadekoBot/data/slots/background.png | Bin 0 -> 114008 bytes src/NadekoBot/data/slots/emojis/0.png | Bin 0 -> 8966 bytes src/NadekoBot/data/slots/emojis/1.png | Bin 0 -> 2183 bytes src/NadekoBot/data/slots/emojis/2.png | Bin 0 -> 3478 bytes src/NadekoBot/data/slots/emojis/3.png | Bin 0 -> 5804 bytes src/NadekoBot/data/slots/emojis/4.png | Bin 0 -> 3461 bytes src/NadekoBot/data/slots/emojis/5.png | Bin 0 -> 6302 bytes 20 files changed, 409 insertions(+) create mode 100644 src/NadekoBot/Modules/Gambling/Commands/Slots.cs create mode 100644 src/NadekoBot/data/slots/0.png create mode 100644 src/NadekoBot/data/slots/1.png create mode 100644 src/NadekoBot/data/slots/2.png create mode 100644 src/NadekoBot/data/slots/3.png create mode 100644 src/NadekoBot/data/slots/4.png create mode 100644 src/NadekoBot/data/slots/5.png create mode 100644 src/NadekoBot/data/slots/6.png create mode 100644 src/NadekoBot/data/slots/7.png create mode 100644 src/NadekoBot/data/slots/8.png create mode 100644 src/NadekoBot/data/slots/9.png create mode 100644 src/NadekoBot/data/slots/background.png create mode 100644 src/NadekoBot/data/slots/emojis/0.png create mode 100644 src/NadekoBot/data/slots/emojis/1.png create mode 100644 src/NadekoBot/data/slots/emojis/2.png create mode 100644 src/NadekoBot/data/slots/emojis/3.png create mode 100644 src/NadekoBot/data/slots/emojis/4.png create mode 100644 src/NadekoBot/data/slots/emojis/5.png diff --git a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs new file mode 100644 index 00000000..05c46af5 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs @@ -0,0 +1,301 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class Slots : ModuleBase + { + private static int totalBet = 0; + private static int totalPaidOut = 0; + + private const string backgroundPath = "data/slots/background.png"; + + private static readonly byte[] backgroundBuffer; + private static readonly byte[][] numbersBuffer = new byte[10][]; + private static readonly byte[][] emojiBuffer; + + const int alphaCutOut = byte.MaxValue / 3; + + static Slots() + { + backgroundBuffer = File.ReadAllBytes(backgroundPath); + + for (int i = 0; i < 10; i++) + { + numbersBuffer[i] = File.ReadAllBytes("data/slots/" + i + ".png"); + } + int throwaway; + var emojiFiles = Directory.GetFiles("data/slots/emojis/", "*.png") + .Where(f => int.TryParse(Path.GetFileNameWithoutExtension(f), out throwaway)) + .OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f))) + .ToArray(); + + emojiBuffer = new byte[emojiFiles.Length][]; + for (int i = 0; i < emojiFiles.Length; i++) + { + emojiBuffer[i] = File.ReadAllBytes(emojiFiles[i]); + } + } + + + private static MemoryStream InternalGetStream(string path) + { + var ms = new MemoryStream(); + using (var fs = File.Open(path, FileMode.Open)) + { + fs.CopyTo(ms); + fs.Flush(); + } + ms.Position = 0; + return ms; + } + + //here is a payout chart + //https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg + //thanks to judge for helping me with this + + public class SlotMachine + { + public const int MaxValue = 5; + + static readonly List> winningCombos = new List>() + { + //three flowers + (arr) => arr.All(a=>a==MaxValue) ? 30 : 0, + //three of the same + (arr) => !arr.Any(a => a != arr[0]) ? 10 : 0, + //two flowers + (arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0, + //one flower + (arr) => arr.Any(a => a == MaxValue) ? 1 : 0, + }; + + public static SlotResult Pull() + { + var numbers = new int[3]; + for (int i = 0; i < numbers.Length; i++) + { + numbers[i] = new NadekoRandom().Next(0, MaxValue + 1); + } + int multi = 0; + for (int i = 0; i < winningCombos.Count; i++) + { + multi = winningCombos[i](numbers); + if (multi != 0) + break; + } + + return new SlotResult(numbers, multi); + } + + public struct SlotResult + { + public int[] Numbers { get; } + public int Multiplier { get; } + public SlotResult(int[] nums, int multi) + { + this.Numbers = nums; + this.Multiplier = multi; + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task SlotStats() + { + //i remembered to not be a moron + var paid = totalPaidOut; + var bet = totalBet; + + if (bet <= 0) + bet = 1; + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle("Slot Stats") + .AddField(efb => efb.WithName("Total Bet").WithValue(bet.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName("Paid Out").WithValue(paid.ToString()).WithIsInline(true)) + .WithFooter(efb => efb.WithText($"Payout Rate: {paid * 1.0 / bet * 100:f4}%")); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task SlotTest(int tests = 1000) + { + if (tests <= 0) + return; + //multi vs how many times it occured + var dict = new Dictionary(); + for (int i = 0; i < tests; i++) + { + var res = SlotMachine.Pull(); + if (dict.ContainsKey(res.Multiplier)) + dict[res.Multiplier] += 1; + else + dict.Add(res.Multiplier, 1); + } + + var sb = new StringBuilder(); + const int bet = 1; + int payout = 0; + foreach (var key in dict.Keys.OrderByDescending(x=>x)) + { + sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); + payout += key * dict[key]; + } + await Context.Channel.SendConfirmAsync("Slot Test Results", sb.ToString(), + footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + } + + static HashSet runningUsers = new HashSet(); + [NadekoCommand, Usage, Description, Aliases] + public async Task Slot(int amount = 0) + { + if (!runningUsers.Add(Context.User.Id)) + return; + try + { + if (amount < 1) + { + await Context.Channel.SendErrorAsync($"You can't bet less than 1{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false); + return; + } + + if (amount > 999) + { + await Context.Channel.SendErrorAsync($"You can't bet more than 999{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false); + return; + } + + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Slot Machine", amount, false)) + return; + Interlocked.Add(ref totalBet, amount); + using (var bgFileStream = new MemoryStream(backgroundBuffer)) + { + var bgImage = new ImageSharp.Image(bgFileStream); + + var result = SlotMachine.Pull(); + int[] numbers = result.Numbers; + using (var bgPixels = bgImage.Lock()) + { + for (int i = 0; i < 3; i++) + { + using (var file = new MemoryStream(emojiBuffer[numbers[i]])) + { + var randomImage = new ImageSharp.Image(file); + using (var toAdd = randomImage.Lock()) + { + for (int j = 0; j < toAdd.Width; j++) + { + for (int k = 0; k < toAdd.Height; k++) + { + var x = 95 + 142 * i + j; + int y = 330 + k; + var toSet = toAdd[j, k]; + if (toSet.A < alphaCutOut) + continue; + bgPixels[x, y] = toAdd[j, k]; + } + } + } + } + } + + var won = amount * result.Multiplier; + var printWon = won; + var n = 0; + do + { + var digit = printWon % 10; + using (var fs = new MemoryStream(numbersBuffer[digit])) + { + var img = new ImageSharp.Image(fs); + using (var pixels = img.Lock()) + { + for (int i = 0; i < pixels.Width; i++) + { + for (int j = 0; j < pixels.Height; j++) + { + if (pixels[i, j].A < alphaCutOut) + continue; + var x = 230 - n * 16 + i; + bgPixels[x, 462 + j] = pixels[i, j]; + } + } + } + } + n++; + } while ((printWon /= 10) != 0); + + var printAmount = amount; + n = 0; + do + { + var digit = printAmount % 10; + using (var fs = new MemoryStream(numbersBuffer[digit])) + { + var img = new ImageSharp.Image(fs); + using (var pixels = img.Lock()) + { + for (int i = 0; i < pixels.Width; i++) + { + for (int j = 0; j < pixels.Height; j++) + { + if (pixels[i, j].A < alphaCutOut) + continue; + var x = 395 - n * 16 + i; + bgPixels[x, 462 + j] = pixels[i, j]; + } + } + } + } + n++; + } while ((printAmount /= 10) != 0); + } + + var msg = "Better luck next time ^_^"; + if (result.Multiplier != 0) + { + await CurrencyHandler.AddCurrencyAsync(Context.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false); + Interlocked.Add(ref totalPaidOut, amount * result.Multiplier); + if (result.Multiplier == 1) + msg = $"A single {NadekoBot.BotConfig.CurrencySign}, x1 - Try again!"; + else if (result.Multiplier == 4) + msg = $"Good job! Two {NadekoBot.BotConfig.CurrencySign} - bet x4"; + else if (result.Multiplier == 10) + msg = "Wow! Lucky! Three of a kind! x10"; + else if (result.Multiplier == 30) + msg = "WOAAHHHHHH!!! Congratulations!!! x30"; + } + + await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`Bet:`{amount} `Won:` {amount * result.Multiplier}{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false); + } + } + finally + { + var t = Task.Run(async () => + { + await Task.Delay(3000); + runningUsers.Remove(Context.User.Id); + }); + } + } + } + } +} diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 717d767e..5fe6ae20 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -7079,6 +7079,87 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to slot. + /// + public static string slot_cmd { + get { + return ResourceManager.GetString("slot_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Play Nadeko slots. Max bet is 999. 3 seconds cooldown per user.. + /// + public static string slot_desc { + get { + return ResourceManager.GetString("slot_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}slot 5`. + /// + public static string slot_usage { + get { + return ResourceManager.GetString("slot_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to slotstats. + /// + public static string slotstats_cmd { + get { + return ResourceManager.GetString("slotstats_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows the total stats of the slot command for this bot's session.. + /// + public static string slotstats_desc { + get { + return ResourceManager.GetString("slotstats_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}slotstats`. + /// + public static string slotstats_usage { + get { + return ResourceManager.GetString("slotstats_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to slottest. + /// + public static string slottest_cmd { + get { + return ResourceManager.GetString("slottest_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tests to see how much slots payout for X number of plays.. + /// + public static string slottest_desc { + get { + return ResourceManager.GetString("slottest_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}slottest 1000`. + /// + public static string slottest_usage { + get { + return ResourceManager.GetString("slottest_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to slowmode. /// diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 1e9ba5d4..460d6b7c 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -2952,4 +2952,31 @@ `{0}startevent flowerreaction` + + slotstats + + + Shows the total stats of the slot command for this bot's session. + + + `{0}slotstats` + + + slottest + + + Tests to see how much slots payout for X number of plays. + + + `{0}slottest 1000` + + + slot + + + Play Nadeko slots. Max bet is 999. 3 seconds cooldown per user. + + + `{0}slot 5` + \ No newline at end of file diff --git a/src/NadekoBot/data/slots/0.png b/src/NadekoBot/data/slots/0.png new file mode 100644 index 0000000000000000000000000000000000000000..e260e4004e0ca9251acfc9ba7e324db4036e7122 GIT binary patch literal 551 zcmV+?0@(eDP)N2bZe?^J zG%heMF)*zP3cvsW0i;PpK~y+TwNg(nLqQN97Y9O>gFm6w-SqR&SbJ*#)vvFWlB#=0xawVG*P$DgM9;`reb_`-bfSYo3h zV*SoUvg5l|tazUiWE~$q!Yh_T=eTM|BSEQeaL2nl6{m+4w z;yn8v^8S%r);)82)QIddn$uE(;$}fIvZa0*(3$AzK>0RZZazZ^7vfp*t}yo_^zRNc z7N82y62=-L;9-@gvY4~*O<|g|P4*QB+{p?T2LNgz0hqubD5Xk#5zp?S-+U7Z5 zrNfUd&YwH=nL}$K^8z-tNhWKO5)$fOb6t6`tmaAY=0iCv4DuK*E@0W>`1FHx_|M3I z#5)XcR3`AQ^K!qxOGDr(`?2kLL178J=UlQXo~mSiIToABYM?i3!J!`q)vM(u^7nD& z>91YFv%o2YEnlZ)NxZe@v6_Nu#f@AK*%a(+3Z|IUy0=U>pXkXf#lOC@lfA-c_eGOs2kQb<^gcG4>?xmS#G>{-HGl3?>4iW`w4*IgZr3}N k`2Ouu*#%A}%se~{dy5JSCsn@l1BM!dr>mdKI;Vst02?`#bpQYW literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/2.png b/src/NadekoBot/data/slots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..57c5525a367bd7995c092fff02151dc99adf2787 GIT binary patch literal 553 zcmV+^0@nSBP)N2bZe?^J zG%heMF)*zP3cvsW0j5brK~y+T#golT8$lSxC)V1FnE0^}MPrhgiI5%?iUDy|Vs<|C z)}ntz51zcJw;pO^kGA(>|DGNzf_SM_L3d_$jSB50@trZDArN~KA9z^y-QT=CJMRp# zGC|%qj2;?B{u+*N4JTlDGWtjJ!l4#;&Vg2SKkaEwb|X%{Ye~Mf90_s&^^cdUUK$p> zc5nlso|+_H7)*?HI<92$ALwnq}rS#}f8aw5%B6rkq`j*yKzk@COI0 z<@{UC>F=7PbuIq++P#x=CFncIkU(L8H^XvvDwc#pr?qx#M%Ie5hIKuBU_TX@bmqsx&mvEOi$Q z;zV)0Vdk=vHJ^AF9kyo!u!MsZr=_cfkx>=U9k?l9LqYaihMV%mK>e4WvN2bZe?^J zG%heMF)*zP3cvsW0hmcdK~y+T)swp~1wjst=hkWo>JN$v9e(Bl_acviuA@ zZ$)mt=M(Tz+=qqp*lg{6_n#CvJQsNU2%lO=t!%}k!U`n6MsX>%vh`6cF3qJ=D_ehE z%&$!Dsg*4~VC8*}9f#5`(1qf4f#OYi%xeD16);)*uC){^u|WuX0y!Syc@yHe3J@1R zEqG!%A*xFHsTQ-rrb5N=g88B1Ph})4fZNF9tH}&i&k$A9tJ~giuXyW&Yqfo)1pjHc zli}1kUy7CV8rcemSSQjrLB87O&VyCWo d>8d9pdIN$55})4PsJQ?D002ovPDHLkV1oCU=fwa3 literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/4.png b/src/NadekoBot/data/slots/4.png new file mode 100644 index 0000000000000000000000000000000000000000..d494165020c36f702bc12ec349508d1f5edd547d GIT binary patch literal 471 zcmV;|0Vw{7P)N2bZe?^J zG%heMF)*zP3cvsW0aQsuK~y+TwUIqe!$1&)Hx1NC5h6&~&hFX;1qDJvNJxR$Ya0*+ z6r2DW&X7CsQ&B+f5Q&Z^zzLwFqu?*`uALOnVP>o~k_#t_1Wy`SGw)mb?U?+Zq|c5b zVYl7{oz6&ZOt?%?ax(zGblYw)P7xo`63kTpw8`f&}sbQm5m4ZJEx12Nzo$tH`&_mwG=XGNy z15qgd}?FRI|R+hi4qHLnZ3F8vj)V59Wmf<<$(xd@Th%&xldNZ2)`+ zi)ianxe9w8r^#W6k^Pi$`t;`)vI~0bOpnTyz>byR$FWEE_2|z<$Pa$Lt-4DAXF~u0 N002ovPDHLkV1l({#=Zam literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/5.png b/src/NadekoBot/data/slots/5.png new file mode 100644 index 0000000000000000000000000000000000000000..bc78e972e3bb28390cd3a4aba4c28691d59b70af GIT binary patch literal 542 zcmV+(0^$9MP)N2bZe?^J zG%heMF)*zP3cvsW0h>ugK~y+TwUfO|12GuJQ$=0WQmsQpkM@!%xHxoBY(~4&vbFsP2M)kE2BpCq=>blDl3j=+gS;y!uf%Hy?QJ$ipvrFPAsSNHM#h8LenW z-ZjTf%?TLZjJ_#8RZ$!-grFv-yr8J3a z&g6Kv14+jA5b6ox*VU;ulssuIj1GmtU-Biu0w4Q)VkC;<^?z`~z#O0Y#bhdq;^hfi z2YB~sDU-g=qrPsp5$cUlI1oYin>;&Pb+Svoz(V~r$MR*_Dws(j3^Nn1L)R6jkAb8u znEd5>I3`anv)4k{=w=4>`+_=(kqwuT^0#wkC9^Qoj}_&9)*j-q(^MJol(bm+k^j-q(|A6$cOR16gjpddlCkG5 zYsa==gPShL_aWSxA0E*T-GQ5OQ4{2>V^5HCSJ0j6Vmjk8Iv2tXI1mUQ%tbI=*l`oa g06ln)y@C)zA9^tAg^I~y82|tP07*qoM6N<$f-C*!djJ3c literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/6.png b/src/NadekoBot/data/slots/6.png new file mode 100644 index 0000000000000000000000000000000000000000..b596051d730aa5e6693765a126fedde721c4a189 GIT binary patch literal 562 zcmV-20?qx2P)N2bZe?^J zG%heMF)*zP3cvsW0k26!K~y+TwUf^;13?tWCq!IOrA<_*wKGHF;?RW9N;P(VI1>Mb zxR8i&;Ns^IcZt8pRV30AX%pR@+3l)?3*M|H+5{)x6LdaW6G z(Hu84Ct&z8`mFfawc>a-0zFA4Y9gCaob3CYTq;Q}zHHVI4|83q4_M$EmyNDIl!meE z3=g(Clq9{2P+J5)EsniG%H!AB{_ZI7n*s^2z()Ze>Wf1^>;K?K19N;56cWie^s{_H z>j3XmikZ|^4t2Ep4WafJg$ogMufeU!nqw6Mfra{Rj^)FAHg68aIP}_~%Zk(cP|{a8 z{N;MICKZp_OA%OOc-<{o$AqY$`1`y3RqNf@RBAGm*n8}Zb6Q++-0ev@37 zM#b=mUKs7M+SzC{oH>NS%G}c0*}aI z1_nI^5N5n8BPR_Mlq_+LC<)F_D=AMbN@XZW%*-p%%S$a$Fwry6Gc;Js#dUyzfl+D&_yu&M*7wCR< zU|#09^#NOaY|7GmJ66XqXw~tF9^$pLSgp0UEO;k>$vO2E0(lHUe~!g|J!j3ol|Ml7 zPQ$gX(0BWa!+&!CRqnfeTARW7(W~h4t>Hkq2a8uuF)(PF6}NlEnRc#+?7t-?BK|Em z+~BnH$H96wwrA77EwTyF%lmNCGf?Km!APJN%pF<_n!j%=>T)=0-Z-y@9cZQp*RF%2 zcfWUdEqdR0@$WI;r;n`puPz6fzI2+#`DpQ_C%(Ts<;qY$W1doq-0ECla58wh`njxg HN@xNAC=sow literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/8.png b/src/NadekoBot/data/slots/8.png new file mode 100644 index 0000000000000000000000000000000000000000..48ceed0291e537e5dfc486bd14faf206f77dbe3f GIT binary patch literal 597 zcmV-b0;>IqP)N2bZe?^J zG%heMF)*zP3cvsW0n$lCK~y+T&66=t8!;5eJy2z#4FMvNh=j}828j*~5maddQIxZN zWkh_5D(aGH3@;=~`bsGLv1RX%Cp{vmv0V2;nCaxyjX$4qY}7Zk(Ip@Fra1AsqvDw*Z)MfBuv zjVoyGdON!W@CU;oDd_QtJ6nCvsYC(`c+W?LjBr(T_7wQFz{fUJ=+Oho>93(r?_u+! z2KJwHLO~BK*k`yARGp2;Cmc>XjH_@cg(C*l>;|0NK-#BLarb?5=;lua&vYfZU_;HADyEY5HU6Utnr<>(7w#-RoA_fEKsTDMGr1wj@m0w2TUBz_ zhGmlO&PaJ~9ulkrbA>`qclx`_87RyVOv<-FkS|lgqf99ci3FKj4irW!1^%^;W@ jrZQIqsI5J>h7dvzg||36;!=3100000NkvXXu0mjfW+?(G literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/9.png b/src/NadekoBot/data/slots/9.png new file mode 100644 index 0000000000000000000000000000000000000000..b71dceec27b3ca167c21a1d17a85533648b367d7 GIT binary patch literal 578 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 Ds+YWF literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/emojis/2.png b/src/NadekoBot/data/slots/emojis/2.png new file mode 100644 index 0000000000000000000000000000000000000000..c567b887a54b0d256a5a3c69b9e0eb62beb609a4 GIT binary patch literal 3478 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∾mYXAcd~HpAg1ULFHsO->KR;PgY;MaX-W2 zNA5BWmInj(yhH2J%EVm7*E+joiyo1kj3Lez>4g_BHF&rfsyNzRgvRsqe~jD-?VNGE zoAC5l6Q-N4RVKa}6n&Rpv`Tj}vM_l#8&5}fku=K$K9sv-|0%8YVMCNRNo>it-vAl6 zUVH?tqFiKS%#^724=*KS%`6Cx>pU5-lHyQ#023HuR&?I=4dW}iNR}Nu0*__VPmReoWjP)YU>V; zwv9U?b(dEUG7+DgBw;>Sy4jm%{1Ns=MLZzl-6q}4J`o0tXi;zpE1wS5QF0%Y?k$r# z1Omkncw%xZ-W@V~^7<&RrPBcl49xtve5DT~bQ;T9oh$VEqlNp*kjXg*m1-|_RI5QL zJtmm7OXi3{@~@H;S^_(RPrvk3opyiL$`$d|M(jr#5&WEFx3=mn6X?BpA9IN2SZNRwx83mlgseUEb1Nph`Htzuhl759t6=ZrNdX4Hqfb)G#58GD%lIs=)>LFacq?&YWjH*lyaydKH-_+x~uWXc-L%yR000)kTJn-3(t z+%|rSnz)+C1;uToalu|_XqGrqsO<bL6d0Cn?wfxWHLM!AV)lHC5SE&CyP zbshtTEp(EW%7)f+Tj=8}1L{IIe1&UiGQlUjDtgMlU?!Rh2W3tIz9V=GmbQ? zpX$%x@PA{mpZKP~uYo5qgo0c^iKI8HR$Ed`=dACFg|6GLN3s5l(jEy!lHN+5j@r5m z(kv|UQWxu9l9s#pNo>|U+PWTO9GJhXwslv+Yf>#&qGZ{km!o_jDj=WZuVsDoXUw14 zI~LtqDEL;`sNn z$G%lEep}$zc4~iHfLDaa=INK8yqWIU_l5|2CbxB(4N0j7-Vd+O3_n*WO3)2ryi13m zz80845{x{m0WE)W%ERjwSZ>+XydQ1ZTGc<;wPB^Rf>Q6;A!A+AUXsbZV}ql*>#%a} zUCF@VG9LC=3?1;&Fx{D|Pa`rWS<1Z8+<6S@nM)*xAx|{t+B+lUCbQ#Y*^Ai*IxP<- zL#&OZt&feHR^`IsU+XToQTLU&JT3${Y}y8F`ex+$?UC?H+k@98QuKomvLdfBNKN#z&cv_$pfRj*nS_{@uN7I2QAj&a_QQu$U)U`dftHV*l4ozw%fv>=F zPqr*CtN7L9!=@wmh)%VqSui)iA`Wm`q-S|8;IixJzC{ZwLqFWA{V$V;K)P1Zexij?X)klGUekd1Z!N$?@< zrAom>%p}}$w_4*`ej}70s8+tbP@TDVjO7yn?c{r!Q0o?69Q1QcY6bQ e3U;-1gy=zR?V%n6kpC!T09Zv^xkkw*{C@yJmJ1jF literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/emojis/4.png b/src/NadekoBot/data/slots/emojis/4.png new file mode 100644 index 0000000000000000000000000000000000000000..36dd054c70e6cf2c4a517bdd48021af2c84f469c GIT binary patch literal 3461 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 literal 0 HcmV?d00001 diff --git a/src/NadekoBot/data/slots/emojis/5.png b/src/NadekoBot/data/slots/emojis/5.png new file mode 100644 index 0000000000000000000000000000000000000000..84b4db13e176f198a90d758ee0d6a0957623d709 GIT binary patch literal 6302 zcmZ`;WmFtIvtD3vXK`t)-!+iOlnvd&pB9sbMKh}ZKTws z0D#}Ixc6r0&t-ZGMJ+V|z=s6@2n+=PZl6Vg&+R;T0e}Nj005E-08qGOG-!%GS74Z{ zD98ey{!2N{#fi@nY!^j+Hvj+^^dF%B(lRKYg_!P2YI2zCD8zV}AdDlO%I6+zm1L!~ zVGGL{4(`SZ9=!qlyPqn`E&CI=1{17$KW%h*(r(RR#{m9r|7lKoB;L)_`4q}1#8sG(!}`vNIZ3ave!rig~fnuldHVDhQK z!&{;!T0N(eW)AcZTrbQQcf=@(rt8fQj8OJco2ceO;}hm@lPTL)tpXgZv$! zlW^*bz@lGp?dr@93POc*k%Oyo7cb>tFR+4Mh|1khFm$L05nVWi+NLp&EX?BhZi#sR z>dux>=*)7God&4BTSc$1qz zWQTH$mSw5@b67J}GDVrx|BT<#Z86~vjdC){(W|B*b!l__>ki0_-& zoCBBIr~!4w-!+j4ZGPll)~Y$!PR&A~#mP4esBF0YvyD88orup($ya9rcbH_2V2v<{ zhhclQ@3Nb9x{W?8TYN50h1lL*bxmn&L$D+I@HE+DL|7+8jYdhGAg{NFSWW+2J#YL* z^n(UP`+`7u`OkZaOJ|h)+9%*O(U{{&FZ;r->r>=M+p)0I> z+#TprpT5KUt4PO75a{ZoFBuYCtqaSNb#Tw17ggFx`-${T*1*(s!14<|_w9SS-u;%f z=*-NY-hOz@=-2(q8izGvvSc&$#|jowe632-8Amww<|LMmr55$+nD^W%WZNPe~ZEzI!BY!%Yc;=vpuvnNd~ z@J9aPp5fd-A&}@}TZ^;zjkx@5VUXcoQM-5l3F@(1st9<-vrEvRU{uyp1<(>F-Tmb* zDaL|d(GFfrdM)nbo?(e3?NHg5VHi^iHfriyg5o&aVfs4@LDa{(89&mq{mj;^_6L?Z z%NzpB1PtCX=-Zxh3es@tQX~BaJm-nz`#)}}e#ZQp+@O~>PWqcis?4ie-n07qT^c)U zFg30GHQ5%`{@5)O>_x{FXzT@6wKDzJ-M!ZG1k;&Nn;p9VWF0>u(``-x&Kv@hE>8ar$+MKo0~8Gn6z(ycP_L^er0c~f5SF4)VU}U)8zEQ_ zfAkOj=P=gY$B8E$sMVL80XCjk%7EaPN}sh|DSiITE`HA@OU#%q7s|j$6MO>ou@*XQ zK}*UJY72X~Q;Ljc>ccFE^#B%(VN`Y6{wly$IizW{!RKxO%r8h+KcQdD3IA!1HeE*F zTJc2tlaEi;+r>jG6&)qLm#Y)(=q$+0UaJTDkvlng@f|6;@qjt58P#V!@Rq{qmcmT& z;vVS-!P@l%SjzwN|E{E8>ZRGhwi*uzZ-L0gpzf5<1dzCmu;7!yEnlEh% z-20M(0y;jLp@D3C`5V5Z*>jPz^_1}rX{gVeEnW8!{a~98hzPk3>s{Z*gCWLhP;~Ud zs8g^^(h#of($_DZjYL^PLWPev9BBi-+NmWe=2(`WZvkG%#P*Lyva1*^j0(MlwS37M zf-c-J_n(6HWEp_rMxgdW8Z2(%R@Z+jDwZvh`}=5?jE^WPge>TKa4>wSjkX1Hex5 zM?o>#K-*pSB#7pu){-WL`t8m3V2xNKie9+qDxR)q(gPoFcB$SDsp!iZF!3>rt0*9K z%i2!*+gnE4`YI#^zjO3Ja%JlYWI~M>Px&z4Eoal$*_L@1y{WHlis_JPmBP=Eu6~nJ z?3$1Rl5^>Y$gYi&E0h!qx4Y_PvVupns8L57(l$bPsE`@>yWAE-B#0FoBdr$M6y-hh ze#I|>A#9@A6O)Z*t0q*)V1hV?9Lc%AYOFib-#^8qpBh|(U?ylfKqo)SXMMZWfa5)N zOh5Y{XLO`51JbtU=ozt)?>V#i3Os1D@sYH;tjx$tnW}j5P*!56vc2kh zzJKo|^WuipV9S|3F)G?3Y~Qev5DJic3&1|%20@7)&e+fYr2H7fs;G`fP4kYr5lW%b zgY!dfwIj0gzzlmytb!pN6pTBfV9944A2&ht8mJgSot?8^Fc*7cjjRnWA>X@R4Bkv((T{P%7@<0y_ z8n^qH%SMVdQ_+2hD7;vbO4tn9^ZRY|UO5ijB}0FI!(a4@FC!F2n;A>5UKB_oWs3K_ zbQfJ)Q!!h3fqH-R;B<5Ai!Wcccj1@kI!X?SR~#L8n2d+`SO7DkeI^%T{CM##X|K6Rnf z$wcZYDqZxBUN1nC2{#gLq*m`9cZYtAd4>t0`%P(WIhA(vp#+EfR@XQFH|}R{@<#Ql1 zg|tK3c)Tv~_FO4_AW!!~1CjG*#+248PJ8i=gb`Yx?v|rq)~MWj&XmdL6k~Z%sMe>+ zvb=e}X6U6WJ6B8Ky{<+?h7cBv%GvkdpZ2UI8B3cew^71Sm!j0|Nz8;Ye2s{uQ-JwH zL=QVsKB}be+$cqMTanXH6wJP(vi*ebL%!Y^#Dg-6Qfnbf?2sxVy!jcbsuX^bkD-x^ z@da)l!$` z*(hRwCicYQSs@RQuOSt3FgcK|*Rf9-yC@R(;u;vrPFO9O$QGo<+WFq(ks#n-X?}wq zEvQ8zN=dDRdw;0PPD#!#&_0>U6*zewK%D~l>SGC=oK4JK7b35DGTQVuPs_U$o9bgj z1i)F>ccQy62{jko@FeD>#!hiVvjE@GiQh5ALxPzJR~Yrq8S*A83a$?o)Eq^&jmUyD zT#tZZbg?;+a-i4f?zF6ovs>PoD#v(tf{*a?lji7tr^U>cti;;yt}hR3K3gj(Uw}_1 z499P281WYKb_suMu?F;pk72)*1lf*Y8P|OP#*7!HT1>?em&XeDv8le1i|&;l%Py1MX{Wm<6eORB z*q6UI%O}dwU{+$p*@@j~uAR?}rpVqAz`7N@rfcqq0lmx3-pXv1DVsr2wxyE_pFOJv%E78&1f$X+io$j58j;l463L7(=Bv6>JYqy z&U`Arv5x!BUSK@SpGj?GV2pY`fvM?28ZF@$o+WW_&-XQ6uB6H}k6lOH#NP+D{K^#WC zhgkvm5ULCAHonv9NuUZhMR>yolXFybg7T`x3~mkpDJeoc832I$nzdy1SWq1s!bGQg{L@zD}M z#o?DPONU&Df`^j-VbJK*DvWf)83#Kv&(VK#)9Gza-KeE1!V%xV(ciVS@-WmB@Ul<~ z;}Mak4UI*T7(`})X{oX`f>eZ~dDb0+f{csg;8SNpZ4z@YkeYMTg0SshL6SqIJU9>3 zXU9AAL}R}ha@nx2-6Vt}p>(R3BA!w8g%tLYV-{UBy8nBY7@4{Q7Ag%7MzwOcY?OIy z?2F>+bL^=;yQ*UOO%Km8_fCPhew`kdVv@nez824_c;Q7)KP1s}SckGI^xLHRlk+fs zo(h7di@D=vamJYtYk2h5P`&&~yQP1R+l)qcFvqc!2%SYQ6U&>i4E`EERIw}@^TXJ_ zmdCUUVhoFzP?Xd7%`TqH&Ft3Ty&DRur(3}d3PPH(f-b|FZ_hezKAz_jyf>1tSKYL% zissPyi)SXa;iQ^w2>%0hr9CSe%O}2t##Pl?dftg|Z2W)GUdHzmJ)b*SHq051(_$2RE&K{j3#x?_YPFWdG<9%+AC}5%R2d zh!t*W{G=6>#rPYlMqW?P->lRrmLw+}3147d-<+7?^JURo)2%z0IqlDnad4j4o#P8L zV*-WZJaHRu52&v{KvyZdry%fRVO)g&%Z;^LMLI(N-zkU6U)~$u&dwf>Y;C8>q~!^G zJi!fN4`}`D*?}muN_pK#vaXX|KT5-KeZr<4Tp!@XLTu-wt$~I$dPq@k)H+|L2cAg& z#SA8HpU3FQI(DRgy<33n{1z)UR3dq3hlC9L-sES58?Xm^IH>JNLrQH(dO@LMD_^$_ z|hjM?DR3`qo51K!$sMm?twrikz~+S6V)sk5E|S@DzB z^0egQgrr%OCu!F%`7>=U)>IT{)O%QejXd~wNuN@kNGoPG0p`m+YiJamU-02vdFoKF zFQ8LBHW%WSUqATZ&S3ADVY}MS7p}}yK)>Y9zA`{|-mmQ9S6%5M;Q|< z%It=>$3oNOQFT*Q%`_&Bj%AxTm-B`LtWcIFQl%>efp01e^@j!LI|#~%_EyBJ)xPibh@Rk$7%4WmZ1Z)~U zOWT3>XjQx&RNIzm`s|C;+qlc+=5MV=^NRh31~Mvl#DjxL$+e%wi$!-yvx0&)Qzs!Lp!Y5Vk0f^=#2WhX z``Cx7ss3IShfz$f=KKy&aLR;E@o>cy;j{ZN4R$IGPFAC2{G9rWr7f^^Z>iqjs6J~N z6VdHaeW<%cJS3!%T^vKkGu>))*fd|GIXZB}89r-hkjblk4i$n`w(0yj@Wnkre-!yQ zN4$5dn2jW+41?3K z4a*wdK1G;pnLpflb{e@Feda;KN zJo#B>Z$C!mjT&0tfj=W@c)!qK+P*WJh4tc31v;JM*TniS+O$u&)E4er4{#u<<~1L9 z_Hz~bBu!7>KR?62a+lL{w={FNf>^j(Ju?6wFIb3+7t93~(gyQEc!eNf5iVW<2rn-g z>tOHy0XR8Z+F5)5{{TxLH{WLf)qf*Eovq!y%v`MiQkKr Date: Wed, 18 Jan 2017 17:02:48 +0100 Subject: [PATCH 48/74] Trailing whitespace remove --- src/NadekoBot/Modules/Gambling/Commands/Slots.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs index 05c46af5..7ed1f9ee 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs @@ -63,7 +63,7 @@ namespace NadekoBot.Modules.Gambling return ms; } - //here is a payout chart + //here is a payout chart //https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg //thanks to judge for helping me with this @@ -153,7 +153,7 @@ namespace NadekoBot.Modules.Gambling var sb = new StringBuilder(); const int bet = 1; - int payout = 0; + int payout = 0; foreach (var key in dict.Keys.OrderByDescending(x=>x)) { sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); @@ -293,7 +293,7 @@ namespace NadekoBot.Modules.Gambling { await Task.Delay(3000); runningUsers.Remove(Context.User.Id); - }); + }); } } } From cf13e959516cba894339d48b4fa12faa153d9c0d Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 17:48:16 +0100 Subject: [PATCH 49/74] public nadeko will only have ~boobs and ~butts out of all nsfw commands on next restart --- src/NadekoBot/Modules/NSFW/NSFW.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 8e520c6c..589d421a 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -19,6 +19,7 @@ namespace NadekoBot.Modules.NSFW [NadekoModule("NSFW", "~")] public class NSFW : DiscordModule { +#if !GLOBAL_NADEKO private static ConcurrentDictionary AutoHentaiTimers { get; } = new ConcurrentDictionary(); private static ConcurrentHashSet _hentaiBombBlacklist { get; } = new ConcurrentHashSet(); @@ -189,7 +190,7 @@ namespace NadekoBot.Modules.NSFW .WithFooter(efb => efb.WithText("e621"))) .ConfigureAwait(false); } - +#endif [NadekoCommand, Usage, Description, Aliases] public async Task Cp() { @@ -204,7 +205,7 @@ namespace NadekoBot.Modules.NSFW JToken obj; using (var http = new HttpClient()) { - obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{ new NadekoRandom().Next(0, 10229) }").ConfigureAwait(false))[0]; + obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{ new NadekoRandom().Next(0, 10330) }").ConfigureAwait(false))[0]; } await Context.Channel.SendMessageAsync($"http://media.oboobs.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); } @@ -222,7 +223,7 @@ namespace NadekoBot.Modules.NSFW JToken obj; using (var http = new HttpClient()) { - obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{ new NadekoRandom().Next(0, 4222) }").ConfigureAwait(false))[0]; + obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{ new NadekoRandom().Next(0, 4335) }").ConfigureAwait(false))[0]; } await Context.Channel.SendMessageAsync($"http://media.obutts.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); } @@ -231,7 +232,7 @@ namespace NadekoBot.Modules.NSFW await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); } } - +#if !GLOBAL_NADEKO public static Task GetDanbooruImageLink(string tag) => Task.Run(async () => { try @@ -289,4 +290,5 @@ namespace NadekoBot.Modules.NSFW public static Task GetRule34ImageLink(string tag) => Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34); } +#endif } \ No newline at end of file From 1c142a0fc587e60b711c9857a2f7f4bf59c6b167 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 18:38:31 +0100 Subject: [PATCH 50/74] ~av and .uinfo now show gif avatars properly --- src/NadekoBot/Modules/Searches/Searches.cs | 8 ++++++-- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 2 +- src/NadekoBot/_Extensions/Extensions.cs | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 60bebedf..34ef1074 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -618,9 +618,13 @@ namespace NadekoBot.Modules.Searches if (usr == null) usr = Context.User; + var avatarUrl = usr.RealAvatarUrl(); + var shortenedAvatarUrl = await NadekoBot.Google.ShortenUrl(avatarUrl).ConfigureAwait(false); await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() - .WithTitle($"{usr}'s Avatar") - .WithImageUrl(usr.AvatarUrl)).ConfigureAwait(false); + .AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).WithIsInline(false)) + .AddField(efb => efb.WithName("Avatar Url").WithValue(shortenedAvatarUrl).WithIsInline(false)) + //.AddField(efb => efb.WithName("Avatar Id").WithValue(usr.AvatarId).WithIsInline(false)) + .WithThumbnailUrl(avatarUrl), Context.User.Mention).ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index eaea37f0..7d57ff8a 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -97,7 +97,7 @@ namespace NadekoBot.Modules.Utility .AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true)) .AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true)) - .WithThumbnailUrl(user.AvatarUrl) + .WithThumbnailUrl(user.RealAvatarUrl()) .WithColor(NadekoBot.OkColor); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index d2dd36fc..46be1172 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -425,5 +425,12 @@ namespace NadekoBot.Extensions public static bool IsDiscordInvite(this string str) => filterRegex.IsMatch(str); + + public static string RealAvatarUrl(this IUser usr) + { + return usr.AvatarId.StartsWith("a_") + ? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif" + : usr.AvatarUrl; + } } } \ No newline at end of file From 54ddab13c172e06ffbb0ced754bf76bd707b4f0d Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 21:52:42 +0100 Subject: [PATCH 51/74] global command cooldown reduced to 750ms on self hosted (still 1500 on public bot). Global command cooldown no longer triggers on any message, only on command. --- src/NadekoBot/Services/CommandHandler.cs | 39 +++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 16bf8e89..13c8bac5 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -30,7 +30,11 @@ namespace NadekoBot.Services } public class CommandHandler { +#if GLOBAL_NADEKO public const int GlobalCommandsCooldown = 1500; +#else + public const int GlobalCommandsCooldown = 750; +#endif private readonly DiscordShardedClient _client; private readonly CommandService _commandService; @@ -119,18 +123,18 @@ namespace NadekoBot.Services private void LogErroredExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int ticks) { _log.Warn("Command Errored after {5}s\n\t" + - "User: {0}\n\t" + - "Server: {1}\n\t" + - "Channel: {2}\n\t" + - "Message: {3}\n\t" + - "Error: {4}", - usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} - (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} - (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} - usrMsg.Content,// {3} - exec.Result.ErrorReason, // {4} - ticks * oneThousandth // {5} - ); + "User: {0}\n\t" + + "Server: {1}\n\t" + + "Channel: {2}\n\t" + + "Message: {3}\n\t" + + "Error: {4}", + usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} + (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} + (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} + usrMsg.Content,// {3} + exec.Result.ErrorReason, // {4} + ticks * oneThousandth // {5} + ); } private async Task InviteFiltered(IGuild guild, SocketUserMessage usrMsg) @@ -196,11 +200,6 @@ namespace NadekoBot.Services // track how many messagges each user is sending UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); - // Bot will ignore commands which are ran more often than what specified by - // GlobalCommandsCooldown constant (miliseconds) - if (!UsersOnShortCooldown.Add(usrMsg.Author.Id)) - return; - var channel = msg.Channel as SocketTextChannel; var guild = channel?.Guild; @@ -367,9 +366,13 @@ namespace NadekoBot.Services } } + // Bot will ignore commands which are ran more often than what specified by + // GlobalCommandsCooldown constant (miliseconds) + if (!UsersOnShortCooldown.Add(context.Message.Author.Id)) + return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"You are on a global cooldown.")); if (CmdCdsCommands.HasCooldown(cmd, context.Guild, context.User)) - return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"That command is on cooldown for you.")); + return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"That command is on a cooldown for you.")); return new ExecuteCommandResult(cmd, null, await commands[i].ExecuteAsync(context, parseResult, dependencyMap)); } From 7bdbf227cda90fc8cf313dc2ecccc8a7f2b226a8 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 21:56:31 +0100 Subject: [PATCH 52/74] Ok and error colors are now in bot config table. Default $bf multiplier increased from 1.8x to 1.95x --- .../20170112185538_currency-modifications.cs | 4 +- ...20170118202307_ok-error-colors.Designer.cs | 988 ++++++++++++++++++ .../20170118202307_ok-error-colors.cs | 35 + .../NadekoSqliteContextModelSnapshot.cs | 6 + src/NadekoBot/NadekoBot.cs | 6 +- .../Services/Database/Models/BotConfig.cs | 7 +- src/NadekoBot/_Extensions/Extensions.cs | 16 +- 7 files changed, 1048 insertions(+), 14 deletions(-) create mode 100644 src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs create mode 100644 src/NadekoBot/Migrations/20170118202307_ok-error-colors.cs diff --git a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs index ad0af9e3..3f2f8c22 100644 --- a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs +++ b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs @@ -12,7 +12,7 @@ namespace NadekoBot.Migrations name: "BetflipMultiplier", table: "BotConfig", nullable: false, - defaultValue: 1.8f); + defaultValue: 1.95f); migrationBuilder.AddColumn( name: "Betroll100Multiplier", @@ -42,7 +42,7 @@ namespace NadekoBot.Migrations name: "MinimumBetAmount", table: "BotConfig", nullable: false, - defaultValue: 3); + defaultValue: 2); migrationBuilder.AddColumn( name: "TriviaCurrencyReward", diff --git a/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs new file mode 100644 index 00000000..b13f8275 --- /dev/null +++ b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.Designer.cs @@ -0,0 +1,988 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170118202307_ok-error-colors")] + partial class okerrorcolors + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170118202307_ok-error-colors.cs b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.cs new file mode 100644 index 00000000..b52bee10 --- /dev/null +++ b/src/NadekoBot/Migrations/20170118202307_ok-error-colors.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class okerrorcolors : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ErrorColor", + table: "BotConfig", + nullable: false, + defaultValue: "ee281f"); + + migrationBuilder.AddColumn( + name: "OkColor", + table: "BotConfig", + nullable: false, + defaultValue: "71cd40"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ErrorColor", + table: "BotConfig"); + + migrationBuilder.DropColumn( + name: "OkColor", + table: "BotConfig"); + } + } +} diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 91f7b6fc..454c641f 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -4,6 +4,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; namespace NadekoBot.Migrations { @@ -118,6 +120,8 @@ namespace NadekoBot.Migrations b.Property("DMHelpString"); + b.Property("ErrorColor"); + b.Property("ForwardMessages"); b.Property("ForwardToAllOwners"); @@ -128,6 +132,8 @@ namespace NadekoBot.Migrations b.Property("MinimumBetAmount"); + b.Property("OkColor"); + b.Property("RemindMessageFormat"); b.Property("RotatingStatuses"); diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index a019476a..2e4d3daf 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -23,8 +23,8 @@ namespace NadekoBot { private Logger _log; - public static Color OkColor { get; } = new Color(0x71cd40); - public static Color ErrorColor { get; } = new Color(0xee281f); + public static Color OkColor { get; } + public static Color ErrorColor { get; } public static CommandService CommandService { get; private set; } public static CommandHandler CommandHandler { get; private set; } @@ -49,6 +49,8 @@ namespace NadekoBot { AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(); BotConfig = uow.BotConfig.GetOrCreate(); + OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16)); + ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16)); } } diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 080770ec..2aa973f6 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -25,8 +25,8 @@ namespace NadekoBot.Services.Database.Models public string CurrencyPluralName { get; set; } = "Nadeko Flowers"; public int TriviaCurrencyReward { get; set; } = 0; - public int MinimumBetAmount { get; set; } = 3; - public float BetflipMultiplier { get; set; } = 1.8f; + public int MinimumBetAmount { get; set; } = 2; + public float BetflipMultiplier { get; set; } = 1.95f; public int CurrencyDropAmount { get; set; } = 1; public float Betroll67Multiplier { get; set; } = 2; public float Betroll91Multiplier { get; set; } = 3; @@ -57,6 +57,9 @@ For a specific command help, use `{1}h CommandName` (for example {1}h !!q) Nadeko Support Server: https://discord.gg/0ehQwTK2RBjAxzEY"; public int MigrationVersion { get; set; } + + public string OkColor { get; set; } = "71cd40"; + public string ErrorColor { get; set; } = "ee281f"; } public class PlayingStatus :DbEntity diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 46be1172..fc50058f 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -183,18 +183,18 @@ namespace NadekoBot.Extensions await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(message, isTTS).ConfigureAwait(false); public static async Task SendConfirmAsync(this IUser user, string text) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text)); + => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); public static async Task SendConfirmAsync(this IUser user, string title, string text, string url = null) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text) + => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text) .WithTitle(title).WithUrl(url)); public static async Task SendErrorAsync(this IUser user, string title, string error, string url = null) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(error) + => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error) .WithTitle(title).WithUrl(url)); public static async Task SendErrorAsync(this IUser user, string error) - => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(error)); + => await (await user.CreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); public static async Task SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false) => await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(File.Open(filePath, FileMode.Open), caption ?? "x", text, isTTS).ConfigureAwait(false); @@ -212,18 +212,18 @@ namespace NadekoBot.Extensions => ch.SendMessageAsync(msg, embed: embed); public static Task SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.ErrorColor).WithDescription(error) + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error) .WithTitle(title).WithUrl(url).WithFooter(efb => efb.WithText(footer))); public static Task SendErrorAsync(this IMessageChannel ch, string error) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(error)); + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); public static Task SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text) + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text) .WithTitle(title).WithUrl(url).WithFooter(efb => efb.WithText(footer))); public static Task SendConfirmAsync(this IMessageChannel ch, string text) - => ch.SendMessageAsync("", embed: new EmbedBuilder().WithColor(NadekoBot.OkColor).WithDescription(text)); + => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); public static Task SendTableAsync(this IMessageChannel ch, string seed, IEnumerable items, Func howToPrint, int columns = 3) { From fd97927fdea2d2833d00d00979cd634b026bebf2 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 18 Jan 2017 21:56:53 +0100 Subject: [PATCH 53/74] Version upped to 1.1.3 --- src/NadekoBot/Services/Impl/StatsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 7e3f4406..0a170399 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl private DiscordShardedClient client; private DateTime started; - public const string BotVersion = "1.1.2"; + public const string BotVersion = "1.1.3"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From bef907f61921d66ecc53fdad29d3a968edee6e2f Mon Sep 17 00:00:00 2001 From: Kwoth Date: Thu, 19 Jan 2017 19:36:57 +0100 Subject: [PATCH 54/74] $bf is at 1.95 by default, $br > 90 is 4x --- .../20170112185538_currency-modifications.cs | 2 +- src/NadekoBot/Modules/Gambling/Gambling.cs | 50 +++++++++++++++++++ .../Services/Database/Models/BotConfig.cs | 2 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs index 3f2f8c22..e29e5c8c 100644 --- a/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs +++ b/src/NadekoBot/Migrations/20170112185538_currency-modifications.cs @@ -30,7 +30,7 @@ namespace NadekoBot.Migrations name: "Betroll91Multiplier", table: "BotConfig", nullable: false, - defaultValue: 3f); + defaultValue: 4f); migrationBuilder.AddColumn( name: "CurrencyDropAmount", diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index a52ea433..f52109ee 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -145,6 +145,56 @@ namespace NadekoBot.Modules.Gambling await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {CurrencyPluralName}!").ConfigureAwait(false); } + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task BrTest(int tests = 1000) + { + var t = Task.Run(async () => + { + if (tests <= 0) + return; + //multi vs how many times it occured + var dict = new Dictionary(); + var generator = new NadekoRandom(); + for (int i = 0; i < tests; i++) + { + var rng = generator.Next(0, 101); + var mult = 0; + if (rng < 67) + { + mult = 0; + } + else if (rng < 91) + { + mult = 2; + } + else if (rng < 100) + { + mult = 4; + } + else + mult = 10; + + if (dict.ContainsKey(mult)) + dict[mult] += 1; + else + dict.Add(mult, 1); + } + + var sb = new StringBuilder(); + const int bet = 1; + int payout = 0; + foreach (var key in dict.Keys.OrderByDescending(x => x)) + { + sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); + payout += key * dict[key]; + } + await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), + footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + + }); + } + [NadekoCommand, Usage, Description, Aliases] public async Task BetRoll(long amount) { diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index 2aa973f6..d96d4cc8 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -29,7 +29,7 @@ namespace NadekoBot.Services.Database.Models public float BetflipMultiplier { get; set; } = 1.95f; public int CurrencyDropAmount { get; set; } = 1; public float Betroll67Multiplier { get; set; } = 2; - public float Betroll91Multiplier { get; set; } = 3; + public float Betroll91Multiplier { get; set; } = 4; public float Betroll100Multiplier { get; set; } = 10; //public HashSet CommandCosts { get; set; } = new HashSet(); From ce6b0f383ebee6a952f7276fb113255f84acf333 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Thu, 19 Jan 2017 21:55:13 +0100 Subject: [PATCH 55/74] $slot now tells you if you don't have enough currency --- src/NadekoBot/Modules/Gambling/Commands/Slots.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs index 7ed1f9ee..e1671a6d 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/Slots.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/Slots.cs @@ -184,7 +184,10 @@ namespace NadekoBot.Modules.Gambling } if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Slot Machine", amount, false)) + { + await Context.Channel.SendErrorAsync($"You don't have enough {NadekoBot.BotConfig.CurrencySign}.").ConfigureAwait(false); return; + } Interlocked.Add(ref totalBet, amount); using (var bgFileStream = new MemoryStream(backgroundBuffer)) { From 14b797cfb694228b1ad2da50f7c2d6c6a87c06d5 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Thu, 19 Jan 2017 22:04:57 +0100 Subject: [PATCH 56/74] fixed >cleverbot on locally hosted bots? --- src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs index 08269207..d5df8dfe 100644 --- a/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs @@ -32,7 +32,7 @@ namespace Services.CleverBotApi #if GLOBAL_NADEKO var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=nadekobot"; #else - var url = "http://www.cleverbot.com/webservicemin?uc=3210"; + var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=chatterbotapi"; #endif switch (type) From 3fa6e6b1628667ccefe4ece0d41a1e9fe6e81d80 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Fri, 20 Jan 2017 08:04:24 +0100 Subject: [PATCH 57/74] Some random changes to commandhandler --- src/NadekoBot/Services/CommandHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 13c8bac5..a9689be1 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -215,19 +215,19 @@ namespace NadekoBot.Services if (IsBlacklisted(guild, usrMsg)) return; - var cleverBotRan = await TryRunCleverbot(usrMsg, guild).ConfigureAwait(false); + var cleverBotRan = await Task.Run(() => TryRunCleverbot(usrMsg, guild)).ConfigureAwait(false); if (cleverBotRan) return; // maybe this message is a custom reaction - var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); + var crExecuted = await Task.Run(() => CustomReactions.TryExecuteCustomReaction(usrMsg)).ConfigureAwait(false); if (crExecuted) //if it was, don't execute the command return; string messageContent = usrMsg.Content; // execute the command and measure the time it took - var exec = await ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best); + var exec = await Task.Run(() => ExecuteCommand(new CommandContext(_client, usrMsg), messageContent, DependencyMap.Empty, MultiMatchHandling.Best)).ConfigureAwait(false); execTime = Environment.TickCount - execTime; if (exec.Result.IsSuccess) From 65be4279b89dce762c1db9740b843ac54f05a1b6 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 22 Jan 2017 21:06:10 +0100 Subject: [PATCH 58/74] $claim, $waifuinfo, $affinity and $divorce commands added for a waifu currency game --- .../20170122044958_waifus.Designer.cs | 1089 +++++++++++++++++ .../Migrations/20170122044958_waifus.cs | 140 +++ .../NadekoSqliteContextModelSnapshot.cs | 101 ++ .../Gambling/Commands/WaifuClaimCommands.cs | 516 ++++++++ src/NadekoBot/Modules/Gambling/Gambling.cs | 11 +- .../Modules/Utility/Commands/QuoteCommands.cs | 2 +- .../Resources/CommandStrings.Designer.cs | 135 ++ src/NadekoBot/Resources/CommandStrings.resx | 45 + src/NadekoBot/Services/CommandHandler.cs | 2 + src/NadekoBot/Services/CurrencyHandler.cs | 58 +- .../Services/Database/IUnitOfWork.cs | 2 + .../Services/Database/Models/DiscordUser.cs | 19 + .../Services/Database/Models/Waifu.cs | 49 + .../Services/Database/Models/WaifuUpdate.cs | 27 + .../Services/Database/NadekoContext.cs | 48 +- .../Repositories/IDiscordUserRepository.cs | 15 + .../Database/Repositories/IWaifuRepository.cs | 13 + .../Impl/DiscordUserRepository.cs | 36 + .../Repositories/Impl/WaifuRepository.cs | 49 + src/NadekoBot/Services/Database/UnitOfWork.cs | 6 + src/NadekoBot/Services/DbHandler.cs | 21 +- 21 files changed, 2344 insertions(+), 40 deletions(-) create mode 100644 src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs create mode 100644 src/NadekoBot/Migrations/20170122044958_waifus.cs create mode 100644 src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs create mode 100644 src/NadekoBot/Services/Database/Models/DiscordUser.cs create mode 100644 src/NadekoBot/Services/Database/Models/Waifu.cs create mode 100644 src/NadekoBot/Services/Database/Models/WaifuUpdate.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs create mode 100644 src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs diff --git a/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs new file mode 100644 index 00000000..46eebfab --- /dev/null +++ b/src/NadekoBot/Migrations/20170122044958_waifus.Designer.cs @@ -0,0 +1,1089 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170122044958_waifus")] + partial class waifus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170122044958_waifus.cs b/src/NadekoBot/Migrations/20170122044958_waifus.cs new file mode 100644 index 00000000..4396ae77 --- /dev/null +++ b/src/NadekoBot/Migrations/20170122044958_waifus.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class waifus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DiscordUser", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AvatarId = table.Column(nullable: true), + Discriminator = table.Column(nullable: true), + UserId = table.Column(nullable: false), + Username = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DiscordUser", x => x.Id); + table.UniqueConstraint("AK_DiscordUser_UserId", x => x.UserId); + }); + + migrationBuilder.CreateTable( + name: "WaifuInfo", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AffinityId = table.Column(nullable: true), + ClaimerId = table.Column(nullable: true), + Price = table.Column(nullable: false), + WaifuId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WaifuInfo", x => x.Id); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_AffinityId", + column: x => x.AffinityId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_ClaimerId", + column: x => x.ClaimerId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuInfo_DiscordUser_WaifuId", + column: x => x.WaifuId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "WaifuUpdates", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + NewId = table.Column(nullable: true), + OldId = table.Column(nullable: true), + UpdateType = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WaifuUpdates", x => x.Id); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_NewId", + column: x => x.NewId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_OldId", + column: x => x.OldId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_WaifuUpdates_DiscordUser_UserId", + column: x => x.UserId, + principalTable: "DiscordUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_AffinityId", + table: "WaifuInfo", + column: "AffinityId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_ClaimerId", + table: "WaifuInfo", + column: "ClaimerId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuInfo_WaifuId", + table: "WaifuInfo", + column: "WaifuId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_NewId", + table: "WaifuUpdates", + column: "NewId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_OldId", + table: "WaifuUpdates", + column: "OldId"); + + migrationBuilder.CreateIndex( + name: "IX_WaifuUpdates_UserId", + table: "WaifuUpdates", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WaifuInfo"); + + migrationBuilder.DropTable( + name: "WaifuUpdates"); + + migrationBuilder.DropTable( + name: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 454c641f..dd9bb84d 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -299,6 +299,26 @@ namespace NadekoBot.Migrations b.ToTable("CustomReactions"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("Discriminator"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.ToTable("DiscordUser"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => { b.Property("Id") @@ -817,6 +837,55 @@ namespace NadekoBot.Migrations b.ToTable("PokeGame"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") @@ -982,6 +1051,38 @@ namespace NadekoBot.Migrations .WithMany("RaceAnimals") .HasForeignKey("BotConfigId"); }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); } } } diff --git a/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs new file mode 100644 index 00000000..3a365bf0 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/WaifuClaimCommands.cs @@ -0,0 +1,516 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + public enum ClaimTitles + { + Lonely, + Devoted, + Rookie, + Schemer, + Dilettante, + Intermediate, + Seducer, + Expert, + Veteran, + Incubis, + Harem_King, + Harem_God, + } + + public enum AffinityTitles + { + Pure, + Faithful, + Defiled, + Cheater, + Tainted, + Corrupted, + Lewd, + Sloot, + Depraved, + Harlot + } + + [Group] + public class WaifuClaimCommands : ModuleBase + { + private static ConcurrentDictionary _divorceCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _affinityCooldowns { get; } = new ConcurrentDictionary(); + + enum WaifuClaimResult + { + Success, + NotEnoughFunds, + InsufficientAmount + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuClaim(int amount, [Remainder]IUser target) + { + if (amount < 50) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} No waifu is that cheap. You must pay at least 50{NadekoBot.BotConfig.CurrencySign} to get a waifu, even if their actual value is lower.").ConfigureAwait(false); + return; + } + + if (target.Id == Context.User.Id) + { + await Context.Channel.SendErrorAsync(Context.User.Mention + " You can't claim yourself.").ConfigureAwait(false); + return; + } + + WaifuClaimResult result = WaifuClaimResult.NotEnoughFunds; + int? oldPrice = null; + WaifuInfo w; + var isAffinity = false; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + isAffinity = (w?.Affinity?.UserId == Context.User.Id); + if (w == null) + { + var claimer = uow.DiscordUsers.GetOrCreate(Context.User); + var waifu = uow.DiscordUsers.GetOrCreate(target); + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + uow.Waifus.Add(w = new WaifuInfo() + { + Waifu = waifu, + Claimer = claimer, + Affinity = null, + Price = amount + }); + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = waifu, + Old = null, + New = claimer, + UpdateType = WaifuUpdateType.Claimed + }); + result = WaifuClaimResult.Success; + } + } + else if (isAffinity && amount >= w.Price * 0.88f) + { + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + var oldClaimer = w.Claimer; + w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User); + oldPrice = w.Price; + w.Price = amount + (amount / 4); + result = WaifuClaimResult.Success; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); + } + } + else if (amount >= w.Price * 1.1f) // if no affinity + { + if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User.Id, "Claimed Waifu", amount, uow).ConfigureAwait(false)) + { + result = WaifuClaimResult.NotEnoughFunds; + } + else + { + var oldClaimer = w.Claimer; + w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User); + oldPrice = w.Price; + w.Price = amount; + result = WaifuClaimResult.Success; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = w.Claimer, + UpdateType = WaifuUpdateType.Claimed + }); + } + } + else + result = WaifuClaimResult.InsufficientAmount; + + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (result == WaifuClaimResult.InsufficientAmount) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must pay {Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))} or more to claim that waifu!").ConfigureAwait(false); + return; + } + else if (result == WaifuClaimResult.NotEnoughFunds) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} you don't have {amount}{NadekoBot.BotConfig.CurrencySign}!") + .ConfigureAwait(false); + } + else + { + var msg = $"{Context.User.Mention} claimed {target.Mention} as their waifu for {amount}{NadekoBot.BotConfig.CurrencySign}!"; + if (w.Affinity?.UserId == Context.User.Id) + msg += $"\n🎉 Their love is fulfilled! 🎉\n**{target}'s** new value is {w.Price}{NadekoBot.BotConfig.CurrencySign}!"; + await Context.Channel.SendConfirmAsync(msg) + .ConfigureAwait(false); + } + } + + public enum DivorceResult + { + Success, + SucessWithPenalty, + NotYourWife, + Cooldown + } + + + private static readonly TimeSpan DivorceLimit = TimeSpan.FromHours(6); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Divorce([Remainder]IUser target) + { + var channel = (ITextChannel)Context.Channel; + + if (target.Id == Context.User.Id) + return; + + var result = DivorceResult.NotYourWife; + TimeSpan difference = TimeSpan.Zero; + var amount = 0; + WaifuInfo w = null; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + var now = DateTime.UtcNow; + if (w == null || w.Claimer == null || w.Claimer.UserId != Context.User.Id) + result = DivorceResult.NotYourWife; + else if (_divorceCooldowns.AddOrUpdate(Context.User.Id, + now, + (key, old) => ((difference = now.Subtract(old)) > DivorceLimit) ? now : old) != now) + { + result = DivorceResult.Cooldown; + } + else + { + amount = w.Price / 2; + + if (w.Affinity?.UserId == Context.User.Id) + { + await CurrencyHandler.AddCurrencyAsync(w.Waifu.UserId, "Waifu Compensation", amount, uow).ConfigureAwait(false); + w.Price = (int)Math.Floor(w.Price * 0.75f); + result = DivorceResult.SucessWithPenalty; + } + else + { + await CurrencyHandler.AddCurrencyAsync(Context.User.Id, "Waifu Refund", amount, uow).ConfigureAwait(false); + + result = DivorceResult.Success; + } + var oldClaimer = w.Claimer; + w.Claimer = null; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldClaimer, + New = null, + UpdateType = WaifuUpdateType.Claimed + }); + } + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (result == DivorceResult.SucessWithPenalty) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who likes you. You heartless monster.\n{w.Waifu} received {amount}{NadekoBot.BotConfig.CurrencySign} as a compensation.").ConfigureAwait(false); + } + else if (result == DivorceResult.Success) + { + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who doesn't like you. You received {amount}{NadekoBot.BotConfig.CurrencySign} back.").ConfigureAwait(false); + } + else if (result == DivorceResult.NotYourWife) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} That waifu is not yours.").ConfigureAwait(false); + } + else + { + var remaining = DivorceLimit.Subtract(difference); + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You divorced recently. You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** to divorce again.").ConfigureAwait(false); + } + } + + private static readonly TimeSpan AffinityLimit = TimeSpan.FromMinutes(30); + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuClaimerAffinity([Remainder]IUser u = null) + { + if (u?.Id == Context.User.Id) + { + await Context.Channel.SendErrorAsync($"{Context.User.Mention} you can't set affinity to yourself, you egomaniac.").ConfigureAwait(false); + return; + } + DiscordUser oldAff = null; + var sucess = false; + var cooldown = false; + TimeSpan difference = TimeSpan.Zero; + using (var uow = DbHandler.UnitOfWork()) + { + var w = uow.Waifus.ByWaifuUserId(Context.User.Id); + var newAff = u == null ? null : uow.DiscordUsers.GetOrCreate(u); + var now = DateTime.UtcNow; + if (w?.Affinity?.UserId == u?.Id) + { + sucess = false; + } + else if (_affinityCooldowns.AddOrUpdate(Context.User.Id, + now, + (key, old) => ((difference = now.Subtract(old)) > AffinityLimit) ? now : old) != now) + { + sucess = false; + cooldown = true; + } + else if (w == null) + { + var thisUser = uow.DiscordUsers.GetOrCreate(Context.User); + uow.Waifus.Add(new WaifuInfo() + { + Affinity = newAff, + Waifu = thisUser, + Price = 1, + Claimer = null + }); + sucess = true; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = thisUser, + Old = null, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); + } + else + { + if (w.Affinity != null) + oldAff = w.Affinity; + w.Affinity = newAff; + sucess = true; + + uow._context.WaifuUpdates.Add(new WaifuUpdate() + { + User = w.Waifu, + Old = oldAff, + New = newAff, + UpdateType = WaifuUpdateType.AffinityChanged + }); + } + + await uow.CompleteAsync().ConfigureAwait(false); + } + if (!sucess) + { + if (cooldown) + { + var remaining = AffinityLimit.Subtract(difference); + await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** in order to change your affinity again.").ConfigureAwait(false); + } + else + await Context.Channel.SendErrorAsync($"{Context.User.Mention} your affinity is already set to that waifu or you're trying to remove your affinity while not having one.").ConfigureAwait(false); + return; + } + if (u == null) + await Context.Channel.SendConfirmAsync("Affinity Reset", $"{Context.User.Mention} Your affinity is reset. You no longer have a person you like.").ConfigureAwait(false); + else if (oldAff == null) + await Context.Channel.SendConfirmAsync("Affinity Set", $"{Context.User.Mention} wants to be {u.Mention}'s waifu. Aww <3").ConfigureAwait(false); + else + await Context.Channel.SendConfirmAsync("Affinity Changed", $"{Context.User.Mention} changed their affinity from {oldAff} to {u.Mention}.\n\n*This is morally questionable.*🤔").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuLeaderboard() + { + IList waifus; + using (var uow = DbHandler.UnitOfWork()) + { + waifus = uow.Waifus.GetTop(9); + } + + if (waifus.Count == 0) + { + await Context.Channel.SendConfirmAsync("No waifus have been claimed yet.").ConfigureAwait(false); + return; + } + + var embed = new EmbedBuilder() + .WithTitle("Top Waifus") + .WithOkColor(); + + for (int i = 0; i < waifus.Count; i++) + { + var w = waifus[i]; + + embed.AddField(efb => efb.WithName("#" + (i + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false)); + } + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WaifuInfo([Remainder]IUser target = null) + { + if (target == null) + target = Context.User; + WaifuInfo w; + IList claims; + int divorces = 0; + using (var uow = DbHandler.UnitOfWork()) + { + w = uow.Waifus.ByWaifuUserId(target.Id); + claims = uow.Waifus.ByClaimerUserId(target.Id); + divorces = uow._context.WaifuUpdates.Count(x => x.Old != null && + x.Old.UserId == target.Id && + x.UpdateType == WaifuUpdateType.Claimed && + x.New == null); + if (w == null) + uow.Waifus.Add(w = new WaifuInfo() + { + Affinity = null, + Claimer = null, + Price = 1, + Waifu = uow.DiscordUsers.GetOrCreate(target), + }); + await uow.CompleteAsync().ConfigureAwait(false); + } + + var claimInfo = GetClaimTitle(target.Id); + var affInfo = GetAffinityTitle(target.Id); + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle("Waifu " + w.Waifu.ToString() + " - \"the " + claimInfo.Title + "\"") + .AddField(efb => efb.WithName("Price").WithValue(w.Price.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName("Claimed by").WithValue(w.Claimer?.ToString() ?? "No one").WithIsInline(true)) + .AddField(efb => efb.WithName("Likes").WithValue(w.Affinity?.ToString() ?? "Nobody").WithIsInline(true)) + .AddField(efb => efb.WithName("Changes Of Heart").WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true)) + .AddField(efb => efb.WithName("Divorces").WithValue(divorces.ToString()).WithIsInline(true)) + .AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? "Nobody" : string.Join("\n", claims.Select(x => x.Waifu))).WithIsInline(true)); + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + + + public struct WaifuProfileTitle + { + public int Count { get; } + public string Title { get; } + + public WaifuProfileTitle(int count, string title) + { + Count = count; + Title = title; + } + } + + private static WaifuProfileTitle GetClaimTitle(ulong userId) + { + int count = 0; + using (var uow = DbHandler.UnitOfWork()) + { + count = uow.Waifus.ByClaimerUserId(userId).Count; + } + + ClaimTitles title = ClaimTitles.Lonely; + if (count == 0) + title = ClaimTitles.Lonely; + else if (count == 1) + title = ClaimTitles.Devoted; + else if (count < 4) + title = ClaimTitles.Rookie; + else if (count < 6) + title = ClaimTitles.Schemer; + else if (count < 8) + title = ClaimTitles.Dilettante; + else if (count < 10) + title = ClaimTitles.Intermediate; + else if (count < 12) + title = ClaimTitles.Seducer; + else if (count < 15) + title = ClaimTitles.Expert; + else if (count < 17) + title = ClaimTitles.Veteran; + else if (count < 25) + title = ClaimTitles.Incubis; + else if (count < 50) + title = ClaimTitles.Harem_King; + else + title = ClaimTitles.Harem_God; + + return new WaifuProfileTitle(count, title.ToString().Replace('_', ' ')); + } + + private static WaifuProfileTitle GetAffinityTitle(ulong userId) + { + int count = 0; + using (var uow = DbHandler.UnitOfWork()) + { + count = uow._context.WaifuUpdates.Count(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged); + } + + AffinityTitles title = AffinityTitles.Pure; + if (count < 1) + title = AffinityTitles.Pure; + else if (count < 2) + title = AffinityTitles.Faithful; + else if (count < 4) + title = AffinityTitles.Defiled; + else if (count < 7) + title = AffinityTitles.Cheater; + else if (count < 9) + title = AffinityTitles.Tainted; + else if (count < 11) + title = AffinityTitles.Corrupted; + else if (count < 13) + title = AffinityTitles.Lewd; + else if (count < 15) + title = AffinityTitles.Sloot; + else if (count < 17) + title = AffinityTitles.Depraved; + else if (count < 20) + title = AffinityTitles.Harlot; + + return new WaifuProfileTitle(count, title.ToString().Replace('_', ' ')); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index f52109ee..ff1360b5 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -147,7 +147,7 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] - public async Task BrTest(int tests = 1000) + public Task BrTest(int tests = 1000) { var t = Task.Run(async () => { @@ -189,10 +189,15 @@ namespace NadekoBot.Modules.Gambling sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); payout += key * dict[key]; } - await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), - footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + try + { + await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), + footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + } + catch { } }); + return Task.CompletedTask; } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs index 7796571d..fd60a591 100644 --- a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs @@ -48,7 +48,7 @@ namespace NadekoBot.Modules.Utility keyword = keyword.ToUpperInvariant(); Quote quote; - using (var uow = DbHandler.Instance.GetUnitOfWork()) + using (var uow = DbHandler.UnitOfWork()) { quote = await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false); } diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 5fe6ae20..7510733e 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -2489,6 +2489,33 @@ namespace NadekoBot.Resources { } } + ///

+ /// Looks up a localized string similar to divorce. + /// + public static string divorce_cmd { + get { + return ResourceManager.GetString("divorce_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Releases your claim on a specific waifu. You will get a part of your money back unless that waifu has an affinity towards you.. + /// + public static string divorce_desc { + get { + return ResourceManager.GetString("divorce_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}divorce @CheatingSloot`. + /// + public static string divorce_usage { + get { + return ResourceManager.GetString("divorce_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to donadd. /// @@ -8375,6 +8402,114 @@ namespace NadekoBot.Resources { } } + /// + /// Looks up a localized string similar to claimwaifu claim. + /// + public static string waifuclaim_cmd { + get { + return ResourceManager.GetString("waifuclaim_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `{0}affinity` towards you.. + /// + public static string waifuclaim_desc { + get { + return ResourceManager.GetString("waifuclaim_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}claim 50 @Himesama`. + /// + public static string waifuclaim_usage { + get { + return ResourceManager.GetString("waifuclaim_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to affinity. + /// + public static string waifuclaimeraffinity_cmd { + get { + return ResourceManager.GetString("waifuclaimeraffinity_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20%. + /// + public static string waifuclaimeraffinity_desc { + get { + return ResourceManager.GetString("waifuclaimeraffinity_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}affinity`. + /// + public static string waifuclaimeraffinity_usage { + get { + return ResourceManager.GetString("waifuclaimeraffinity_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to waifuinfo waifustats. + /// + public static string waifuinfo_cmd { + get { + return ResourceManager.GetString("waifuinfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows waifu stats for a target person.. + /// + public static string waifuinfo_desc { + get { + return ResourceManager.GetString("waifuinfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}waifuinfo @MyCrush`. + /// + public static string waifuinfo_usage { + get { + return ResourceManager.GetString("waifuinfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to waifus waifulb. + /// + public static string waifuleaderboard_cmd { + get { + return ResourceManager.GetString("waifuleaderboard_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows top 10 waifus.. + /// + public static string waifuleaderboard_desc { + get { + return ResourceManager.GetString("waifuleaderboard_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}waifus`. + /// + public static string waifuleaderboard_usage { + get { + return ResourceManager.GetString("waifuleaderboard_usage", resourceCulture); + } + } + /// /// Looks up a localized string similar to weather we. /// diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 460d6b7c..2a0be4b8 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -2979,4 +2979,49 @@ `{0}slot 5` + + affinity + + + Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20% + + + `{0}affinity` + + + claimwaifu claim + + + Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `{0}affinity` towards you. + + + `{0}claim 50 @Himesama` + + + waifus waifulb + + + Shows top 10 waifus. + + + `{0}waifus` + + + divorce + + + Releases your claim on a specific waifu. You will get a part of your money back unless that waifu has an affinity towards you. + + + `{0}divorce @CheatingSloot` + + + waifuinfo waifustats + + + Shows waifu stats for a target person. + + + `{0}waifuinfo @MyCrush` + \ No newline at end of file diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index a9689be1..f2aab51c 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -197,8 +197,10 @@ namespace NadekoBot.Services if (usrMsg == null) //has to be an user message, not system/other messages. return; +#if !GLOBAL_NADEKO // track how many messagges each user is sending UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old); +#endif var channel = msg.Channel as SocketTextChannel; var guild = channel?.Guild; diff --git a/src/NadekoBot/Services/CurrencyHandler.cs b/src/NadekoBot/Services/CurrencyHandler.cs index f2d699a0..7e2939e4 100644 --- a/src/NadekoBot/Services/CurrencyHandler.cs +++ b/src/NadekoBot/Services/CurrencyHandler.cs @@ -4,6 +4,7 @@ using Discord; using NadekoBot.Extensions; using NadekoBot.Modules.Gambling; using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Database; namespace NadekoBot.Services { @@ -19,26 +20,36 @@ namespace NadekoBot.Services return success; } - public static async Task RemoveCurrencyAsync(ulong authorId, string reason, long amount) + public static async Task RemoveCurrencyAsync(ulong authorId, string reason, long amount, IUnitOfWork uow = null) { if (amount < 0) throw new ArgumentNullException(nameof(amount)); - using (var uow = DbHandler.UnitOfWork()) + if (uow == null) { - var success = uow.Currency.TryUpdateState(authorId, -amount); - if (!success) - return false; - uow.CurrencyTransactions.Add(new CurrencyTransaction() + using (uow = DbHandler.UnitOfWork()) { - UserId = authorId, - Reason = reason, - Amount = -amount, - }); - await uow.CompleteAsync().ConfigureAwait(false); + var toReturn = InternalRemoveCurrency(authorId, reason, amount, uow); + await uow.CompleteAsync().ConfigureAwait(false); + return toReturn; + } } + return InternalRemoveCurrency(authorId, reason, amount, uow); + } + + private static bool InternalRemoveCurrency(ulong authorId, string reason, long amount, IUnitOfWork uow) + { + var success = uow.Currency.TryUpdateState(authorId, -amount); + if (!success) + return false; + uow.CurrencyTransactions.Add(new CurrencyTransaction() + { + UserId = authorId, + Reason = reason, + Amount = -amount, + }); return true; } @@ -50,22 +61,29 @@ namespace NadekoBot.Services try { await author.SendConfirmAsync($"`You received:` {amount} {NadekoBot.BotConfig.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } } - public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount) + public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount, IUnitOfWork uow = null) { if (amount < 0) throw new ArgumentNullException(nameof(amount)); + var transaction = new CurrencyTransaction() + { + UserId = receiverId, + Reason = reason, + Amount = amount, + }; - using (var uow = DbHandler.UnitOfWork()) + if (uow == null) + using (uow = DbHandler.UnitOfWork()) + { + uow.Currency.TryUpdateState(receiverId, amount); + uow.CurrencyTransactions.Add(transaction); + await uow.CompleteAsync(); + } + else { uow.Currency.TryUpdateState(receiverId, amount); - uow.CurrencyTransactions.Add(new CurrencyTransaction() - { - UserId = receiverId, - Reason = reason, - Amount = amount, - }); - await uow.CompleteAsync(); + uow.CurrencyTransactions.Add(transaction); } } } diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index f17e1f6a..c1c9479e 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -21,6 +21,8 @@ namespace NadekoBot.Services.Database ICurrencyTransactionsRepository CurrencyTransactions { get; } IMusicPlaylistRepository MusicPlaylists { get; } IPokeGameRepository PokeGame { get; } + IWaifuRepository Waifus { get; } + IDiscordUserRepository DiscordUsers { get; } int Complete(); Task CompleteAsync(); diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs new file mode 100644 index 00000000..608daf81 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class DiscordUser : DbEntity + { + public ulong UserId { get; set; } + public string Username { get; set; } + public string Discriminator { get; set; } + public string AvatarId { get; set; } + + public override string ToString() => + Username + "#" + Discriminator; + } +} diff --git a/src/NadekoBot/Services/Database/Models/Waifu.cs b/src/NadekoBot/Services/Database/Models/Waifu.cs new file mode 100644 index 00000000..947bf516 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Waifu.cs @@ -0,0 +1,49 @@ +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class WaifuInfo : DbEntity + { + public int WaifuId { get; set; } + public DiscordUser Waifu { get; set; } + + public int? ClaimerId { get; set; } + public DiscordUser Claimer { get; set; } + + public int? AffinityId { get; set; } + public DiscordUser Affinity { get; set; } + + public int Price { get; set; } + + public override string ToString() + { + var claimer = "no one"; + var status = ""; + + var waifuUsername = Waifu.Username.TrimTo(20); + var claimerUsername = Claimer?.Username.TrimTo(20); + + if (Claimer != null) + { + claimer = $"{ claimerUsername }#{Claimer.Discriminator}"; + } + if (AffinityId == null) + { + status = $"... but {waifuUsername}'s heart is empty"; + } + else if (AffinityId == ClaimerId) + { + status = $"... and {waifuUsername} likes {claimerUsername} too <3"; + } + else { + status = $"... but {waifuUsername}'s heart belongs to {Affinity.Username.TrimTo(20)}#{Affinity.Discriminator}"; + } + return $"**{waifuUsername}#{Waifu.Discriminator}** - claimed by **{claimer}**\n\t{status}"; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs b/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs new file mode 100644 index 00000000..1c48826e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/WaifuUpdate.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Models +{ + public class WaifuUpdate : DbEntity + { + public int UserId { get; set; } + public DiscordUser User { get; set; } + public WaifuUpdateType UpdateType { get; set; } + + public int? OldId { get; set; } + public DiscordUser Old { get; set; } + + public int? NewId { get; set; } + public DiscordUser New { get; set; } + } + + public enum WaifuUpdateType + { + AffinityChanged, + Claimed + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index b49439f6..102c6703 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -3,9 +3,26 @@ using System.Collections.Generic; using System.Linq; using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace NadekoBot.Services.Database { + + public class NadekoContextFactory : IDbContextFactory + { + /// + /// :\ Used for migrations + /// + /// + /// + public NadekoContext Create(DbContextFactoryOptions options) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); + return new NadekoContext(optionsBuilder.Options); + } + } + public class NadekoContext : DbContext { public DbSet Quotes { get; set; } @@ -22,6 +39,7 @@ namespace NadekoBot.Services.Database public DbSet CustomReactions { get; set; } public DbSet CurrencyTransactions { get; set; } public DbSet PokeGame { get; set; } + public DbSet WaifuUpdates { get; set; } //logging public DbSet LogSettings { get; set; } @@ -33,23 +51,15 @@ namespace NadekoBot.Services.Database public DbSet RaceAnimals { get; set; } public DbSet ModulePrefixes { get; set; } - public NadekoContext() + public NadekoContext() : base() { - this.Database.Migrate(); + } public NadekoContext(DbContextOptions options) : base(options) { - this.Database.Migrate(); - EnsureSeedData(); } - ////Uncomment this to db initialisation with dotnet ef migration add [module] - //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - //{ - // optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); - //} - public void EnsureSeedData() { if (!BotConfig.Any()) @@ -244,6 +254,24 @@ namespace NadekoBot.Services.Database // .HasIndex(cp => cp.CommandName) // .IsUnique(); #endregion + + #region Waifus + + var wi = modelBuilder.Entity(); + wi.HasOne(x => x.Waifu) + .WithOne(); + // //.HasForeignKey(w => w.WaifuId) + // //.IsRequired(true); + + //wi.HasOne(x => x.Claimer) + // .WithOne(); + // //.HasForeignKey(w => w.ClaimerId) + // //.IsRequired(false); + + var du = modelBuilder.Entity(); + du.HasAlternateKey(w => w.UserId); + + #endregion } } } diff --git a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs new file mode 100644 index 00000000..c80608dd --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs @@ -0,0 +1,15 @@ +using Discord; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IDiscordUserRepository : IRepository + { + DiscordUser GetOrCreate(IUser original); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs b/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs new file mode 100644 index 00000000..2f2dc0f6 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IWaifuRepository.cs @@ -0,0 +1,13 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IWaifuRepository : IRepository + { + IList GetTop(int count); + WaifuInfo ByWaifuUserId(ulong userId); + IList ByClaimerUserId(ulong userId); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs new file mode 100644 index 00000000..9425f049 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -0,0 +1,36 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Discord; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class DiscordUserRepository : Repository, IDiscordUserRepository + { + public DiscordUserRepository(DbContext context) : base(context) + { + } + + public DiscordUser GetOrCreate(IUser original) + { + DiscordUser toReturn; + + toReturn = _set.FirstOrDefault(u => u.UserId == original.Id); + + if (toReturn == null) + _set.Add(toReturn = new DiscordUser() + { + AvatarId = original.AvatarId, + Discriminator = original.Discriminator, + UserId = original.Id, + Username = original.Username, + }); + + return toReturn; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs new file mode 100644 index 00000000..8a973202 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/WaifuRepository.cs @@ -0,0 +1,49 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class WaifuRepository : Repository, IWaifuRepository + { + public WaifuRepository(DbContext context) : base(context) + { + } + + public WaifuInfo ByWaifuUserId(ulong userId) + { + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .FirstOrDefault(wi => wi.Waifu.UserId == userId); + } + + public IList ByClaimerUserId(ulong userId) + { + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .Where(wi => wi.Claimer != null && wi.Claimer.UserId == userId) + .ToList(); + } + + public IList GetTop(int count) + { + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) + return new List(); + + return _set.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .OrderByDescending(wi => wi.Price) + .Take(count) + .ToList(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index 88231d6b..032c0bb7 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -48,6 +48,12 @@ namespace NadekoBot.Services.Database private IPokeGameRepository _pokegame; public IPokeGameRepository PokeGame => _pokegame ?? (_pokegame = new PokeGameRepository(_context)); + private IWaifuRepository _waifus; + public IWaifuRepository Waifus => _waifus ?? (_waifus = new WaifuRepository(_context)); + + private IDiscordUserRepository _discordUsers; + public IDiscordUserRepository DiscordUsers => _discordUsers ?? (_discordUsers = new DiscordUserRepository(_context)); + public UnitOfWork(NadekoContext context) { _context = context; diff --git a/src/NadekoBot/Services/DbHandler.cs b/src/NadekoBot/Services/DbHandler.cs index 03d12a94..9a4ab212 100644 --- a/src/NadekoBot/Services/DbHandler.cs +++ b/src/NadekoBot/Services/DbHandler.cs @@ -1,4 +1,6 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using NadekoBot.Services.Database; namespace NadekoBot.Services @@ -13,7 +15,8 @@ namespace NadekoBot.Services static DbHandler() { } - private DbHandler() { + private DbHandler() + { connectionString = NadekoBot.Credentials.Db.ConnectionString; var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(NadekoBot.Credentials.Db.ConnectionString); @@ -32,13 +35,19 @@ namespace NadekoBot.Services //} } - public NadekoContext GetDbContext() => - new NadekoContext(options); + public NadekoContext GetDbContext() + { + var context = new NadekoContext(options); + context.Database.Migrate(); + context.EnsureSeedData(); - public IUnitOfWork GetUnitOfWork() => + return context; + } + + private IUnitOfWork GetUnitOfWork() => new UnitOfWork(GetDbContext()); public static IUnitOfWork UnitOfWork() => DbHandler.Instance.GetUnitOfWork(); } -} +} \ No newline at end of file From 1e7a3bf3bc82c6f5ccd12956348ba1c036c6f32a Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 22 Jan 2017 21:20:23 +0100 Subject: [PATCH 59/74] Fixeed compile error on public bot --- src/NadekoBot/Modules/NSFW/NSFW.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 589d421a..266ffa68 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -289,6 +289,6 @@ namespace NadekoBot.Modules.NSFW public static Task GetRule34ImageLink(string tag) => Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Rule34); - } #endif + } } \ No newline at end of file From 53b0b7d3a538ec6c4bf495ae401fb52695c06060 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sun, 22 Jan 2017 21:29:41 +0100 Subject: [PATCH 60/74] Updated commandlist --- docs/Commands List.md | 13 ++- src/NadekoBot/Modules/Gambling/Gambling.cs | 102 +++++++++--------- .../Resources/CommandStrings.Designer.cs | 12 +-- src/NadekoBot/Resources/CommandStrings.resx | 12 +-- src/NadekoBot/Services/Impl/StatsService.cs | 2 +- 5 files changed, 75 insertions(+), 66 deletions(-) diff --git a/docs/Commands List.md b/docs/Commands List.md index 1d354c4a..ed71757c 100644 --- a/docs/Commands List.md +++ b/docs/Commands List.md @@ -125,6 +125,14 @@ Command and aliases | Description | Usage ### Gambling Command and aliases | Description | Usage ----------------|--------------|------- +`$claimwaifu` `$claim` | Claim a waifu for yourself by spending currency. You must spend atleast 10% more than her current value unless she set `$affinity` towards you. | `$claim 50 @Himesama` +`$divorce` | Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. | `$divorce @CheatingSloot` +`$affinity` | Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `$claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. | `$affinity @MyHusband` or `$affinity` +`$waifus` `$waifulb` | Shows top 9 waifus. | `$waifus` +`$waifuinfo` `$waifustats` | Shows waifu stats for a target person. Defaults to you if no user is provided. | `$waifuinfo @MyCrush` or `$waifuinfo` +`$slotstats` | Shows the total stats of the slot command for this bot's session. **Bot Owner only.** | `$slotstats` +`$slottest` | Tests to see how much slots payout for X number of plays. **Bot Owner only.** | `$slottest 1000` +`$slot` | Play Nadeko slots. Max bet is 999. 3 seconds cooldown per user. | `$slot 5` `$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` `$betflip` `$bf` | Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t` `$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5` @@ -132,6 +140,7 @@ Command and aliases | Description | Usage `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. Y can be a letter 'F' if you want to roll fate dice instead of dnd. | `$roll` or `$roll 7` or `$roll 3d5` or `$roll 5dF` `$rolluo` | Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5` `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` +`$startevent` | Starts one of the events seen on public nadeko. **Bot Owner only.** | `$startevent flowerreaction` `$race` | Starts a new animal race. | `$race` `$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5` `$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName` @@ -206,7 +215,6 @@ Command and aliases | Description | Usage `!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot Owner only.** | `!!lopl C:/music/classical` `!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` `!!local` `!!lo` | Queues a local file by specifying a full path. **Bot Owner only.** | `!!lo C:/music/mysong.mp3` -`!!move` `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` `!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` `!!movesong` `!!ms` | Moves a song from one position to another. | `!!ms 5>3` `!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq` @@ -226,7 +234,7 @@ Command and aliases | Description | Usage Command and aliases | Description | Usage ----------------|--------------|------- `~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri` -`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` +`~autohentai` | Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tags. 20 seconds minimum. Provide no arguments to disable. **Requires ManageMessages channel permission.** | `~autohentai 30 yuri|tail|long_hair` or `~autohentai` `~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri` `~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing` `~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2` @@ -249,6 +257,7 @@ Command and aliases | Description | Usage `;chnlfilterwords` `;cfw` | Toggles automatic deleting of messages containing banned words on the channel. Does not negate the ;srvrfilterwords enabled setting. Does not affect bot owner. | `;cfw` `;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`;sfw` or `;cfw` to toggle filtering. | `;fw poop` `;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw` +`;cmdcosts` | Shows a list of command costs. Paginated with 9 command per page. | `;cmdcosts` or `;cmdcosts 2` `;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5` `;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds` `;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313` diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index ff1360b5..12071254 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -145,60 +145,60 @@ namespace NadekoBot.Modules.Gambling await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {CurrencyPluralName}!").ConfigureAwait(false); } - [NadekoCommand, Usage, Description, Aliases] - [OwnerOnly] - public Task BrTest(int tests = 1000) - { - var t = Task.Run(async () => - { - if (tests <= 0) - return; - //multi vs how many times it occured - var dict = new Dictionary(); - var generator = new NadekoRandom(); - for (int i = 0; i < tests; i++) - { - var rng = generator.Next(0, 101); - var mult = 0; - if (rng < 67) - { - mult = 0; - } - else if (rng < 91) - { - mult = 2; - } - else if (rng < 100) - { - mult = 4; - } - else - mult = 10; + //[NadekoCommand, Usage, Description, Aliases] + //[OwnerOnly] + //public Task BrTest(int tests = 1000) + //{ + // var t = Task.Run(async () => + // { + // if (tests <= 0) + // return; + // //multi vs how many times it occured + // var dict = new Dictionary(); + // var generator = new NadekoRandom(); + // for (int i = 0; i < tests; i++) + // { + // var rng = generator.Next(0, 101); + // var mult = 0; + // if (rng < 67) + // { + // mult = 0; + // } + // else if (rng < 91) + // { + // mult = 2; + // } + // else if (rng < 100) + // { + // mult = 4; + // } + // else + // mult = 10; - if (dict.ContainsKey(mult)) - dict[mult] += 1; - else - dict.Add(mult, 1); - } + // if (dict.ContainsKey(mult)) + // dict[mult] += 1; + // else + // dict.Add(mult, 1); + // } - var sb = new StringBuilder(); - const int bet = 1; - int payout = 0; - foreach (var key in dict.Keys.OrderByDescending(x => x)) - { - sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); - payout += key * dict[key]; - } - try - { - await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), - footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); - } - catch { } + // var sb = new StringBuilder(); + // const int bet = 1; + // int payout = 0; + // foreach (var key in dict.Keys.OrderByDescending(x => x)) + // { + // sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%"); + // payout += key * dict[key]; + // } + // try + // { + // await Context.Channel.SendConfirmAsync("BetRoll Test Results", sb.ToString(), + // footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%"); + // } + // catch { } - }); - return Task.CompletedTask; - } + // }); + // return Task.CompletedTask; + //} [NadekoCommand, Usage, Description, Aliases] public async Task BetRoll(long amount) diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 7510733e..0f25ceb5 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -2499,7 +2499,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Releases your claim on a specific waifu. You will get a part of your money back unless that waifu has an affinity towards you.. + /// Looks up a localized string similar to Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown.. /// public static string divorce_desc { get { @@ -8439,7 +8439,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20%. + /// Looks up a localized string similar to Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown.. /// public static string waifuclaimeraffinity_desc { get { @@ -8448,7 +8448,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}affinity`. + /// Looks up a localized string similar to `{0}affinity @MyHusband` or `{0}affinity`. /// public static string waifuclaimeraffinity_usage { get { @@ -8466,7 +8466,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Shows waifu stats for a target person.. + /// Looks up a localized string similar to Shows waifu stats for a target person. Defaults to you if no user is provided.. /// public static string waifuinfo_desc { get { @@ -8475,7 +8475,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}waifuinfo @MyCrush`. + /// Looks up a localized string similar to `{0}waifuinfo @MyCrush` or `{0}waifuinfo`. /// public static string waifuinfo_usage { get { @@ -8493,7 +8493,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Shows top 10 waifus.. + /// Looks up a localized string similar to Shows top 9 waifus.. /// public static string waifuleaderboard_desc { get { diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 2a0be4b8..2b742f4a 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -2983,10 +2983,10 @@ affinity - Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20% + Sets your affinity towards someone you want to be claimed by. Setting affinity will reduce their `{0}claim` on you by 20%. You can leave second argument empty to clear your affinity. 30 minutes cooldown. - `{0}affinity` + `{0}affinity @MyHusband` or `{0}affinity` claimwaifu claim @@ -3001,7 +3001,7 @@ waifus waifulb - Shows top 10 waifus. + Shows top 9 waifus. `{0}waifus` @@ -3010,7 +3010,7 @@ divorce - Releases your claim on a specific waifu. You will get a part of your money back unless that waifu has an affinity towards you. + Releases your claim on a specific waifu. You will get some of the money you've spent back unless that waifu has an affinity towards you. 6 hours cooldown. `{0}divorce @CheatingSloot` @@ -3019,9 +3019,9 @@ waifuinfo waifustats - Shows waifu stats for a target person. + Shows waifu stats for a target person. Defaults to you if no user is provided. - `{0}waifuinfo @MyCrush` + `{0}waifuinfo @MyCrush` or `{0}waifuinfo` \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 0a170399..cdd6fd8f 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl private DiscordShardedClient client; private DateTime started; - public const string BotVersion = "1.1.3"; + public const string BotVersion = "1.1.4"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From b6f44de8b5433b6d6d77991e0b6491da02fbd3e0 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 01:33:07 +0100 Subject: [PATCH 61/74] $lb redesign --- src/NadekoBot/Modules/Gambling/Gambling.cs | 33 ++++++++++++------- .../Resources/CommandStrings.Designer.cs | 2 +- src/NadekoBot/Resources/CommandStrings.resx | 2 +- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 12071254..1edf0f9e 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -248,22 +248,33 @@ namespace NadekoBot.Modules.Gambling [NadekoCommand, Usage, Description, Aliases] public async Task Leaderboard() { - IEnumerable richest = new List(); + var richest = new List(); using (var uow = DbHandler.UnitOfWork()) { - richest = uow.Currency.GetTopRichest(10); + richest = uow.Currency.GetTopRichest(9).ToList(); } if (!richest.Any()) return; - await Context.Channel.SendMessageAsync( - richest.Aggregate(new StringBuilder( -$@"```xl -┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓ -┃ Id ┃ $$$ ┃ -"), - (cur, cs) => cur.AppendLine($@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫ -┃{(Context.Guild.GetUserAsync(cs.UserId).GetAwaiter().GetResult()?.Username?.TrimTo(18, true) ?? cs.UserId.ToString()),-20} ┃ {cs.Amount,6} ┃") - ).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛```").ConfigureAwait(false); + + + var embed = new EmbedBuilder() + .WithOkColor() + .WithTitle(NadekoBot.BotConfig.CurrencySign + " Leaderboard"); + + for (var i = 0; i < richest.Count; i++) + { + var x = richest[i]; + var usr = await Context.Guild.GetUserAsync(x.UserId).ConfigureAwait(false); + var usrStr = ""; + if (usr == null) + usrStr = x.UserId.ToString(); + else + usrStr = usr.Username?.TrimTo(20, true); + + embed.AddField(efb => efb.WithName("#" + (i + 1) + " " + usrStr).WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign).WithIsInline(true)); + } + + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } } } diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 0f25ceb5..f8097764 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -879,7 +879,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet.. + /// Looks up a localized string similar to Bet to guess will the result be heads or tails. Guessing awards you 1.95x the currency you've bet (rounded up). Multiplier can be changed by the bot owner.. /// public static string betflip_desc { get { diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 2b742f4a..c9fcc6a8 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -1183,7 +1183,7 @@ betflip bf - Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. + Bet to guess will the result be heads or tails. Guessing awards you 1.95x the currency you've bet (rounded up). Multiplier can be changed by the bot owner. `{0}bf 5 heads` or `{0}bf 3 t` From e0aa06c93db3f8d03fa1e51a982cdebd36801235 Mon Sep 17 00:00:00 2001 From: samvaio Date: Mon, 23 Jan 2017 06:10:53 +0530 Subject: [PATCH 62/74] ~ow fix for no rank players --- .../Searches/Commands/OverwatchCommands.cs | 204 +++++++++--------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs index 019f7f20..b29f70d9 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs @@ -1,102 +1,102 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Searches.Models; -using Newtonsoft.Json; -using NLog; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches -{ - public partial class Searches - { - [Group] - public class OverwatchCommands : ModuleBase - { - private readonly Logger _log; - public OverwatchCommands() - { - _log = LogManager.GetCurrentClassLogger(); - } - [NadekoCommand, Usage, Description, Aliases] - public async Task Overwatch(string region, [Remainder] string query = null) - { - if (string.IsNullOrWhiteSpace(query)) - return; - var battletag = Regex.Replace(query, "#", "-", RegexOptions.IgnoreCase); - - await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); - try - { - await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); - var model = await GetProfile(region, battletag); - - var rankimg = $"{model.Competitive.rank_img}"; - var rank = $"{model.Competitive.rank}"; - var competitiveplay = $"{model.Games.Competitive.played}"; - if (string.IsNullOrWhiteSpace(rank)) - { - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName($"{model.username}") - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl($"{model.avatar}")) - .WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png") - .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithOkColor(); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - } - else - { - var embed = new EmbedBuilder() - .WithAuthor(eau => eau.WithName($"{model.username}") - .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") - .WithIconUrl($"{model.avatar}")) - .WithThumbnailUrl(rankimg) - .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true)) - .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) - .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) - .WithColor(NadekoBot.OkColor); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); - return; - } - } - catch - { - await Context.Channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again."); - } - } - public async Task GetProfile(string region, string battletag) - { - try - { - using (var http = new HttpClient()) - { - var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile"); - var model = JsonConvert.DeserializeObject(Url); - return model.data; - } - } - catch - { - return null; - } - } - - } - } -} +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Searches.Models; +using Newtonsoft.Json; +using NLog; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class OverwatchCommands : ModuleBase + { + private readonly Logger _log; + public OverwatchCommands() + { + _log = LogManager.GetCurrentClassLogger(); + } + [NadekoCommand, Usage, Description, Aliases] + public async Task Overwatch(string region, [Remainder] string query = null) + { + if (string.IsNullOrWhiteSpace(query)) + return; + var battletag = Regex.Replace(query, "#", "-", RegexOptions.IgnoreCase); + + await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); + try + { + await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); + var model = await GetProfile(region, battletag); + + var rankimg = $"{model.Competitive.rank_img}"; + var rank = $"{model.Competitive.rank}"; + var competitiveplay = $"{model.Games.Competitive.played}"; + if (string.IsNullOrWhiteSpace(rank)) + { + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName($"{model.username}") + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl($"{model.avatar}")) + .WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png") + .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) + //.AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) + //.AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) + //.AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) + //.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) + .WithOkColor(); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + else + { + var embed = new EmbedBuilder() + .WithAuthor(eau => eau.WithName($"{model.username}") + .WithUrl($"https://www.overbuff.com/players/pc/{battletag}") + .WithIconUrl($"{model.avatar}")) + .WithThumbnailUrl(rankimg) + .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true)) + .AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) + .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) + .WithColor(NadekoBot.OkColor); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + return; + } + } + catch + { + await Context.Channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again."); + } + } + public async Task GetProfile(string region, string battletag) + { + try + { + using (var http = new HttpClient()) + { + var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile"); + var model = JsonConvert.DeserializeObject(Url); + return model.data; + } + } + catch + { + return null; + } + } + + } + } +} From 6e7165cc5a6713939983ee4d7dbc940877b30533 Mon Sep 17 00:00:00 2001 From: samvaio Date: Mon, 23 Jan 2017 06:14:13 +0530 Subject: [PATCH 63/74] Cleanups --- src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs index b29f70d9..bcb5e082 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs @@ -46,11 +46,7 @@ namespace NadekoBot.Modules.Searches .WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png") .AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true)) .AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true)) - //.AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true)) - //.AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true)) - //.AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true)) .AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true)) - //.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true)) .AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true)) .WithOkColor(); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); From 1fde33676f200fe489ebe672cc4763c0f39fea87 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 01:44:25 +0100 Subject: [PATCH 64/74] 1:44:25.47 can now be set in .adpl to show current server time --- .../Modules/Administration/Commands/PlayingRotateCommands.cs | 3 ++- src/NadekoBot/_Extensions/Extensions.cs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index 89eeb2c5..af1cc15d 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -81,7 +81,8 @@ namespace NadekoBot.Modules.Administration } } }, - {"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()} + {"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, + { "%time%", () => DateTime.Now.ToString("hh:mm "+TimeZoneInfo.Local.StandardName.GetInitials()) } }; [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index fc50058f..56479a6f 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -103,6 +103,9 @@ namespace NadekoBot.Extensions http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); } + public static string GetInitials(this string txt, string glue = "") => + string.Join(glue, txt.Split(' ').Select(x => x.FirstOrDefault())); + public static DateTime ToUnixTimestamp(this double number) => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(number); public static EmbedBuilder WithOkColor(this EmbedBuilder eb) => From 7da31331179404d21e69c31b030ac758f62ecf00 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 02:04:15 +0100 Subject: [PATCH 65/74] !!lq now shows 'now playing' song --- .../Commands/PlayingRotateCommands.cs | 12 +++---- src/NadekoBot/Modules/Music/Music.cs | 32 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index af1cc15d..b70c033b 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -67,9 +67,9 @@ namespace NadekoBot.Modules.Administration public static Dictionary> PlayingPlaceholders { get; } = new Dictionary> { - {"%servers%", () => NadekoBot.Client.GetGuildCount().ToString()}, - {"%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()}, - {"%playing%", () => { + { "%servers%", () => NadekoBot.Client.GetGuildCount().ToString()}, + { "%users%", () => NadekoBot.Client.GetGuilds().Sum(s => s.Users.Count).ToString()}, + { "%playing%", () => { var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); if (cnt != 1) return cnt.ToString(); try { @@ -81,8 +81,8 @@ namespace NadekoBot.Modules.Administration } } }, - {"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, - { "%time%", () => DateTime.Now.ToString("hh:mm "+TimeZoneInfo.Local.StandardName.GetInitials()) } + { "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, + { "%time%", () => DateTime.Now.ToString("hh:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) } }; [NadekoCommand, Usage, Description, Aliases] @@ -154,4 +154,4 @@ namespace NadekoBot.Modules.Administration } } } -} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index 868a0885..e90c910c 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -211,30 +211,30 @@ namespace NadekoBot.Modules.Music { int startAt = itemsPerPage * (curPage - 1); var number = 0 + startAt; + var desc = string.Join("\n", musicPlayer.Playlist + .Skip(startAt) + .Take(itemsPerPage) + .Select(v => $"`{++number}.` {v.PrettyFullName}")); + + if (currentSong != null) + desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc; + + if (musicPlayer.RepeatSong) + desc = "🔂 Repeating Current Song\n\n" + desc; + else if (musicPlayer.RepeatPlaylist) + desc = "🔁 Repeating Playlist\n\n" + desc; + + + var embed = new EmbedBuilder() .WithAuthor(eab => eab.WithName($"Player Queue - Page {curPage}/{lastPage + 1}") .WithMusicIcon()) - .WithDescription(string.Join("\n", musicPlayer.Playlist - .Skip(startAt) - .Take(itemsPerPage) - .Select(v => $"`{++number}.` {v.PrettyFullName}"))) + .WithDescription(desc) .WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " + $"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " + (musicPlayer.FairPlay ? "✔️fairplay" : "✖️fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit"))) .WithOkColor(); - if (musicPlayer.RepeatSong) - { - embed.WithTitle($"🔂 Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}"); - } - else if (musicPlayer.RepeatPlaylist) - { - embed.WithTitle("🔁 Repeating Playlist"); - } - if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) - { - embed.WithTitle("🎵 Song queue is full!"); - } return embed; }; await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage, false).ConfigureAwait(false); From 93d8b24b597cc19508006be05177aa6284e26f9b Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 02:06:09 +0100 Subject: [PATCH 66/74] :eyes: --- src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs index bcb5e082..9a063715 100644 --- a/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Commands/OverwatchCommands.cs @@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Searches { await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); var model = await GetProfile(region, battletag); - + var rankimg = $"{model.Competitive.rank_img}"; var rank = $"{model.Competitive.rank}"; var competitiveplay = $"{model.Games.Competitive.played}"; @@ -92,7 +92,6 @@ namespace NadekoBot.Modules.Searches return null; } } - } } -} +} \ No newline at end of file From 1769a2a553dc97e8a75ffe1c209d051351e0b039 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 02:35:53 +0100 Subject: [PATCH 67/74] You can now plant multiple flowers (default is still one) >plant 5 --- .../Games/Commands/PlantAndPickCommands.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 272ced2a..6b5da1b1 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -147,9 +147,12 @@ namespace NadekoBot.Modules.Games [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - public async Task Plant() + public async Task Plant(int amount = 1) { - var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", 1, false).ConfigureAwait(false); + if (amount < 1) + return; + + var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false); if (!removed) { await Context.Channel.SendErrorAsync($"You don't have any {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false); @@ -160,7 +163,7 @@ namespace NadekoBot.Modules.Games IUserMessage msg; var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]); - var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.BotConfig.CurrencyName}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; + var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; if (file == null) { msg = await Context.Channel.SendConfirmAsync(NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); @@ -169,7 +172,15 @@ namespace NadekoBot.Modules.Games { msg = await Context.Channel.SendFileAsync(File.Open(file, FileMode.OpenOrCreate), new FileInfo(file).Name, msgToSend).ConfigureAwait(false); } - plantedFlowers.AddOrUpdate(Context.Channel.Id, new List() { msg }, (id, old) => { old.Add(msg); return old; }); + + var msgs = new IUserMessage[amount]; + msgs[0] = msg; + + plantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) => + { + old.AddRange(msgs); + return old; + }); } [NadekoCommand, Usage, Description, Aliases] From 8b766655ac5bcf37621988541360dfb7dc42c4a2 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 03:17:50 +0100 Subject: [PATCH 68/74] %shardid% %shardcount% and %shardguilds% added to .ropl --- Discord.Net | 2 +- .../Commands/PlayingRotateCommands.cs | 21 +++++++++++++++++-- .../Resources/CommandStrings.Designer.cs | 4 ++-- src/NadekoBot/Resources/CommandStrings.resx | 5 +++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Discord.Net b/Discord.Net index 58766448..7c0cce6d 160000 --- a/Discord.Net +++ b/Discord.Net @@ -1 +1 @@ -Subproject commit 58766448d79ac9adec228f341f258aa262a3f278 +Subproject commit 7c0cce6d35b04d883cf5ec2d775b051e4bc8739f diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs index b70c033b..3f70f908 100644 --- a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -1,5 +1,6 @@ using Discord; using Discord.Commands; +using Discord.WebSocket; using NadekoBot.Attributes; using NadekoBot.Extensions; using NadekoBot.Services; @@ -50,7 +51,16 @@ namespace NadekoBot.Modules.Administration if (string.IsNullOrWhiteSpace(status)) continue; PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); - await NadekoBot.Client.SetGameAsync(status).ConfigureAwait(false); + var shards = NadekoBot.Client.Shards; + for (int i = 0; i < shards.Count; i++) + { + ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(shards.ElementAt(i)))); + try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); } + catch (Exception ex) + { + _log.Warn(ex); + } + } } } catch (Exception ex) @@ -82,7 +92,14 @@ namespace NadekoBot.Modules.Administration } }, { "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, - { "%time%", () => DateTime.Now.ToString("hh:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) } + { "%time%", () => DateTime.Now.ToString("hh:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) }, + { "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() }, + }; + + public static Dictionary> ShardSpecificPlaceholders { get; } = + new Dictionary> { + { "%shardid%", (client) => client.ShardId.ToString()}, + { "%shardguilds%", (client) => client.Guilds.Count.ToString()}, }; [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index f8097764..8a56ca9e 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -5118,7 +5118,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost). + /// Looks up a localized string similar to Spend an amount of currency to plant it in this channel. Default is 1. (If bot is restarted or crashes, the currency will be lost). /// public static string plant_desc { get { @@ -5127,7 +5127,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to `{0}plant`. + /// Looks up a localized string similar to `{0}plant` or `{0}plant 5`. /// public static string plant_usage { get { diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index c9fcc6a8..20d3b78e 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -1372,10 +1372,11 @@ plant - Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) + Spend an amount of currency to plant it in this channel. Default is 1. (If bot is restarted or crashes, the currency will be lost) - `{0}plant` + `{0}plant` or `{0}plant 5` + gencurrency gc From 14a026555c78eb47ad9c3354143bc1caddf9ff67 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 20:41:48 +0100 Subject: [PATCH 69/74] .sinfo fix? --- src/NadekoBot/Modules/Gambling/Gambling.cs | 4 ++-- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 1edf0f9e..53ad4389 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -67,14 +67,14 @@ namespace NadekoBot.Modules.Gambling { if (amount <= 0 || Context.User.Id == receiver.Id) return; - var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, true).ConfigureAwait(false); + var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false).ConfigureAwait(false); if (!success) { await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {CurrencyPluralName}.").ConfigureAwait(false); return; } await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false); - await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully sent {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} to {receiver.Mention}!").ConfigureAwait(false); + await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully sent {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} to {receiver}!").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 7d57ff8a..d1a3e76a 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Utility .WithColor(NadekoBot.OkColor); if (guild.Emojis.Count() > 0) { - embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(string.Join(" ", guild.Emojis.Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); + embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Take(30).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } From f0415bb36907e7e6e570356267c848b2cb5bc5a9 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 22:02:35 +0100 Subject: [PATCH 70/74] ;cmdcd confirmation response fix --- src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs index d00972eb..fbb41acd 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs @@ -65,12 +65,12 @@ namespace NadekoBot.Modules.Permissions { var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); activeCds.RemoveWhere(ac => ac.Command == command.Aliases.First().ToLowerInvariant()); - await channel.SendConfirmAsync($"🚮 Command **{command}** has no coooldown now and all existing cooldowns have been cleared.") + await channel.SendConfirmAsync($"🚮 Command **{command.Aliases.First()}** has no coooldown now and all existing cooldowns have been cleared.") .ConfigureAwait(false); } else { - await channel.SendConfirmAsync($"✅ Command **{command}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.") + await channel.SendConfirmAsync($"✅ Command **{command.Aliases.First()}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.") .ConfigureAwait(false); } } From 8cd12bbec4c39273451b83dec866bddf0f8fdd41 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 23 Jan 2017 22:21:16 +0100 Subject: [PATCH 71/74] .sinfo fixed. Shows 25 random emojis from the server. closes #991 --- src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index d1a3e76a..66ffa3c2 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Utility .WithColor(NadekoBot.OkColor); if (guild.Emojis.Count() > 0) { - embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Take(30).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); + embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(25).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>")))); } await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); } From c34338458cbaff4c1b34cd610d425d02a5a189f8 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 24 Jan 2017 15:45:00 +0100 Subject: [PATCH 72/74] .adpl help updated. closes #999 --- src/NadekoBot/Resources/CommandStrings.Designer.cs | 2 +- src/NadekoBot/Resources/CommandStrings.resx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs index 8a56ca9e..f37738f5 100644 --- a/src/NadekoBot/Resources/CommandStrings.Designer.cs +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -177,7 +177,7 @@ namespace NadekoBot.Resources { } /// - /// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%. + /// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds%. /// public static string addplaying_desc { get { diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 20d3b78e..95b16f7d 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -292,7 +292,7 @@ addplaying adpl - Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% + Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds% `{0}adpl` @@ -1376,7 +1376,6 @@ `{0}plant` or `{0}plant 5` - gencurrency gc From c898027952dd5e5f384addceab930265d03b77d3 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 24 Jan 2017 18:30:21 +0100 Subject: [PATCH 73/74] img and rimg will use google images first. if it fails, then fallback to imgur closes #970 --- src/NadekoBot/Modules/Searches/Searches.cs | 117 +++++++++++------- .../Services/Impl/GoogleApiService.cs | 40 +++++- 2 files changed, 113 insertions(+), 44 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 34ef1074..ad160599 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -115,33 +115,50 @@ namespace NadekoBot.Modules.Searches if (string.IsNullOrWhiteSpace(terms)) return; - terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); + try + { + var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch") + .WithIconUrl("http://i.imgur.com/G46fm8J.png")) + .WithDescription(res.Link) + .WithImageUrl(res.Link) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + catch + { + _log.Warn("Falling back to Imgur search."); + terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); - var fullQueryLink = $"http://imgur.com/search?q={ terms }"; - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + var fullQueryLink = $"http://imgur.com/search?q={ terms }"; + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); - var elems = document.QuerySelectorAll("a.image-list-link"); + var elems = document.QuerySelectorAll("a.image-list-link"); - if (!elems.Any()) - return; + if (!elems.Any()) + return; - var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement); + var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement); - if (img?.Source == null) - return; + if (img?.Source == null) + return; - var source = img.Source.Replace("b.", "."); + var source = img.Source.Replace("b.", "."); - var embed = new EmbedBuilder() - .WithOkColor() - .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) - .WithUrl(fullQueryLink) - .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) - .WithDescription(source) - .WithImageUrl(source) - .WithTitle(Context.User.Mention); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl(fullQueryLink) + .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) + .WithDescription(source) + .WithImageUrl(source) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } } [NadekoCommand, Usage, Description, Aliases] @@ -150,34 +167,50 @@ namespace NadekoBot.Modules.Searches terms = terms?.Trim(); if (string.IsNullOrWhiteSpace(terms)) return; + try + { + var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch") + .WithIconUrl("http://i.imgur.com/G46fm8J.png")) + .WithDescription(res.Link) + .WithImageUrl(res.Link) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } + catch + { + _log.Warn("Falling back to Imgur"); + terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); - terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); + var fullQueryLink = $"http://imgur.com/search?q={ terms }"; + var config = Configuration.Default.WithDefaultLoader(); + var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); - var fullQueryLink = $"http://imgur.com/search?q={ terms }"; - var config = Configuration.Default.WithDefaultLoader(); - var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink); + var elems = document.QuerySelectorAll("a.image-list-link").ToList(); - var elems = document.QuerySelectorAll("a.image-list-link").ToList(); + if (!elems.Any()) + return; - if (!elems.Any()) - return; + var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement); - var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement); + if (img?.Source == null) + return; - if (img?.Source == null) - return; + var source = img.Source.Replace("b.", "."); - var source = img.Source.Replace("b.", "."); - - var embed = new EmbedBuilder() - .WithOkColor() - .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) - .WithUrl(fullQueryLink) - .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) - .WithDescription(source) - .WithImageUrl(source) - .WithTitle(Context.User.Mention); - await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + var embed = new EmbedBuilder() + .WithOkColor() + .WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50)) + .WithUrl(fullQueryLink) + .WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?")) + .WithDescription(source) + .WithImageUrl(source) + .WithTitle(Context.User.Mention); + await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); + } } [NadekoCommand, Usage, Description, Aliases] @@ -260,7 +293,7 @@ namespace NadekoBot.Modules.Searches .WithTitle(Context.User.Mention) .WithFooter(efb => efb.WithText(totalResults)); - var desc = await Task.WhenAll(results.Select(async res => + var desc = await Task.WhenAll(results.Select(async res => $"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n")) .ConfigureAwait(false); await Context.Channel.EmbedAsync(embed.WithDescription(String.Concat(desc))).ConfigureAwait(false); diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index 7da7ad7b..f2934ba6 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -8,13 +8,19 @@ using System.Text.RegularExpressions; using Google.Apis.Urlshortener.v1; using Google.Apis.Urlshortener.v1.Data; using NLog; +using Google.Apis.Customsearch.v1; +using Google.Apis.Customsearch.v1.Data; namespace NadekoBot.Services.Impl { public class GoogleApiService : IGoogleApiService { + const string search_engine_id = "018084019232060951019:hs5piey28-e"; + private YouTubeService yt; private UrlshortenerService sh; + private CustomsearchService cs; + private Logger _log { get; } public GoogleApiService() @@ -22,13 +28,14 @@ namespace NadekoBot.Services.Impl var bcs = new BaseClientService.Initializer { ApplicationName = "Nadeko Bot", - ApiKey = NadekoBot.Credentials.GoogleApiKey + ApiKey = NadekoBot.Credentials.GoogleApiKey, }; _log = LogManager.GetCurrentClassLogger(); yt = new YouTubeService(bcs); sh = new UrlshortenerService(bcs); + cs = new CustomsearchService(bcs); } public async Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) { @@ -150,7 +157,7 @@ namespace NadekoBot.Services.Impl return toReturn; } //todo AsyncEnumerable - public async Task> GetVideoDurationsAsync(IEnumerable videoIds) + public async Task> GetVideoDurationsAsync(IEnumerable videoIds) { var videoIdsList = videoIds as List ?? videoIds.ToList(); @@ -179,5 +186,34 @@ namespace NadekoBot.Services.Impl return toReturn; } + + public struct ImageResult + { + public Result.ImageData Image { get; } + public string Link { get; } + + public ImageResult(Result.ImageData image, string link) + { + this.Image = image; + this.Link = link; + } + } + + public async Task GetImageAsync(string query, int start = 0) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + + var req = cs.Cse.List(query); + req.Cx = search_engine_id; + req.Num = 1; + req.Fields = "items(image(contextLink,thumbnailLink),link)"; + req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image; + req.Start = start; + + var search = await req.ExecuteAsync().ConfigureAwait(false); + + return new ImageResult(search.Items[0].Image, search.Items[0].Link); + } } } From 809192c732058b282f5f33ced0dbc21934e79bc1 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 24 Jan 2017 22:27:29 +0100 Subject: [PATCH 74/74] flower event fix --- src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs index 4dfa935f..5df56e36 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -66,7 +66,7 @@ namespace NadekoBot.Modules.Gambling { try { - if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) + if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id)) { try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false).ConfigureAwait(false); } catch { } }