From 46f9de01d667416b057affdc4bef34cea4f5f6e1 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 12 Sep 2017 22:27:51 +0200 Subject: [PATCH 01/16] Using redis to cache avatar images, reduced xp image size. --- NadekoBot.sln | 5 +++- .../Modules/Xp/Services/XpService.cs | 29 +++++++++---------- src/NadekoBot/Modules/Xp/Xp.cs | 8 +++-- src/NadekoBot/NadekoBot.cs | 1 + src/NadekoBot/NadekoBot.csproj | 6 ++++ src/NadekoBot/Services/IDataCache.cs | 14 +++++++++ src/NadekoBot/Services/Impl/RedisCache.cs | 28 ++++++++++++++++++ 7 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 src/NadekoBot/Services/IDataCache.cs create mode 100644 src/NadekoBot/Services/Impl/RedisCache.cs diff --git a/NadekoBot.sln b/NadekoBot.sln index 8c235940..42b343e5 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.12 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" EndProject @@ -33,4 +33,7 @@ Global GlobalSection(NestedProjects) = preSolution {45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} + EndGlobalSection EndGlobal diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index 78dcc5f9..f82741bf 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -36,6 +36,7 @@ namespace NadekoBot.Modules.Xp.Services private readonly IImagesService _images; private readonly Logger _log; private readonly NadekoStrings _strings; + private readonly IDataCache _cache; private readonly FontCollection _fonts = new FontCollection(); public const int XP_REQUIRED_LVL_1 = 36; @@ -54,9 +55,6 @@ namespace NadekoBot.Modules.Xp.Services private readonly ConcurrentQueue _addMessageXp = new ConcurrentQueue(); - private readonly ConcurrentDictionary _imageStreams - = new ConcurrentDictionary(); - private readonly Timer updateXpTimer; private readonly HttpClient http = new HttpClient(); private FontFamily _usernameFontFamily; @@ -69,7 +67,7 @@ namespace NadekoBot.Modules.Xp.Services public XpService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable allGuildConfigs, IImagesService images, - DbService db, NadekoStrings strings) + DbService db, NadekoStrings strings, IDataCache cache) { _db = db; _cmd = cmd; @@ -77,6 +75,7 @@ namespace NadekoBot.Modules.Xp.Services _images = images; _log = LogManager.GetCurrentClassLogger(); _strings = strings; + _cache = cache; //load settings allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null); @@ -665,20 +664,20 @@ namespace NadekoBot.Modules.Xp.Services { var avatarUrl = stats.User.RealAvatarUrl(); - byte[] s; - if (!_imageStreams.TryGetValue(avatarUrl, out s)) + var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); + if (!succ) { using (var temp = await http.GetStreamAsync(avatarUrl)) { var tempDraw = Image.Load(temp); tempDraw = tempDraw.Resize(69, 70); ApplyRoundedCorners(tempDraw, 35); - s = tempDraw.ToStream().ToArray(); + data = tempDraw.ToStream().ToArray(); } - _imageStreams.AddOrUpdate(avatarUrl, s, (k, v) => s); + await _cache.SetImageDataAsync(avatarUrl, data); } - var toDraw = Image.Load(s); + var toDraw = Image.Load(data); img.DrawImage(toDraw, @@ -699,20 +698,20 @@ namespace NadekoBot.Modules.Xp.Services var imgUrl = stats.User.Club.ImageUrl; try { - byte[] s; - if (!_imageStreams.TryGetValue(imgUrl, out s)) + var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); + if (!succ) { using (var temp = await http.GetStreamAsync(imgUrl)) { var tempDraw = Image.Load(temp); tempDraw = tempDraw.Resize(45, 45); ApplyRoundedCorners(tempDraw, 22.5f); - s = tempDraw.ToStream().ToArray(); + data = tempDraw.ToStream().ToArray(); } - _imageStreams.AddOrUpdate(imgUrl, s, (k, v) => s); + await _cache.SetImageDataAsync(imgUrl, data); } - var toDraw = Image.Load(s); + var toDraw = Image.Load(data); img.DrawImage(toDraw, 1, @@ -724,7 +723,7 @@ namespace NadekoBot.Modules.Xp.Services _log.Warn(ex); } } - + img.Resize(432, 211); var arr = img.ToStream().ToArray(); //_log.Info("{0:F2} KB", arr.Length * 1.0f / 1.KB()); diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs index ad687792..ee88aa00 100644 --- a/src/NadekoBot/Modules/Xp/Xp.cs +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -26,12 +26,16 @@ namespace NadekoBot.Modules.Xp public async Task Experience([Remainder]IUser user = null) { user = user ?? Context.User; - + var sw = Stopwatch.StartNew(); await Context.Channel.TriggerTypingAsync(); var img = await _service.GenerateImageAsync((IGuildUser)user); - + sw.Stop(); + _log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds); + sw.Restart(); await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png") .ConfigureAwait(false); + sw.Stop(); + _log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index a92380ca..a85da294 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -139,6 +139,7 @@ namespace NadekoBot .AddManual>(AllGuildConfigs) //todo wrap this .AddManual(this) .AddManual(uow) + .AddManual(new RedisCache()) .LoadFrom(Assembly.GetEntryAssembly()) .Build(); diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index ff8ed03e..1a5bf786 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -21,6 +21,7 @@ 1.0.0.0 nadeko_icon.ico win7-x64 + Debug;Release;global_nadeko @@ -78,6 +79,7 @@ + @@ -95,6 +97,10 @@ latest + + latest + + diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs new file mode 100644 index 00000000..67223dfe --- /dev/null +++ b/src/NadekoBot/Services/IDataCache.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services +{ + public interface IDataCache + { + Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key); + Task SetImageDataAsync(string key, byte[] data); + } +} diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs new file mode 100644 index 00000000..b4d9a89d --- /dev/null +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -0,0 +1,28 @@ +using StackExchange.Redis; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Impl +{ + public class RedisCache : IDataCache + { + private readonly ConnectionMultiplexer _redis; + private readonly IDatabase _db; + + public RedisCache() + { + _redis = ConnectionMultiplexer.Connect("localhost"); + _db = _redis.GetDatabase(); + } + + public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key) + { + byte[] x = await _db.StringGetAsync("image_" + key); + return (x != null, x); + } + + public Task SetImageDataAsync(string key, byte[] data) + { + return _db.StringSetAsync("image_" + key, data); + } + } +} From 438f68cde7e1b14ce3c6951e04d5d995878d17ae Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Tue, 12 Sep 2017 23:49:37 +0200 Subject: [PATCH 02/16] global nadeko won't cache nsfw images --- .../Searches/Common/SearchImageCacher.cs | 2 + .../Modules/Xp/Services/XpService.cs | 278 +++++++++--------- src/NadekoBot/Modules/Xp/Xp.cs | 2 +- 3 files changed, 140 insertions(+), 142 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs b/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs index 323a5eae..8bd5d919 100644 --- a/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs +++ b/src/NadekoBot/Modules/Searches/Common/SearchImageCacher.cs @@ -76,11 +76,13 @@ namespace NadekoBot.Modules.Searches.Common if (images.Length == 0) return null; var toReturn = images[_rng.Next(images.Length)]; +#if !GLOBAL_NADEKO foreach (var dledImg in images) { if(dledImg != toReturn) _cache.Add(dledImg); } +#endif return toReturn; } } diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index f82741bf..9bc1969b 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -549,7 +549,7 @@ namespace NadekoBot.Modules.Xp.Services } } - public Task> GenerateImageAsync(IGuildUser user) + public Task GenerateImageAsync(IGuildUser user) { return GenerateImageAsync(GetUserStats(user)); } @@ -565,170 +565,166 @@ namespace NadekoBot.Modules.Xp.Services _timeFont = _fonts.Find("Whitney-Bold").CreateFont(20); } - public Task> GenerateImageAsync(FullUserStats stats) => Task.Run(async () => + public Task GenerateImageAsync(FullUserStats stats) => Task.Run(async () => { - var img = Image.Load(_images.XpCard.ToArray()); - - var username = stats.User.ToString(); - var usernameFont = _usernameFontFamily - .CreateFont(username.Length <= 6 - ? 50 - : 50 - username.Length); - - img.DrawText("@" + username, usernameFont, Rgba32.White, - new PointF(130, 5)); - - // level - - img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, - new PointF(47, 137)); - - img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, - new PointF(47, 285)); - - //club name - - var clubName = stats.User.Club?.ToString() ?? "-"; - - var clubFont = _clubFontFamily - .CreateFont(clubName.Length <= 8 - ? 35 - : 35 - (clubName.Length / 2)); - - img.DrawText(clubName, clubFont, Rgba32.White, - new PointF(650 - clubName.Length * 10, 40)); - - var pen = new Pen(Rgba32.Black, 1); - var brush = Brushes.Solid(Rgba32.White); - var xpBgBrush = Brushes.Solid(new Rgba32(0, 0, 0, 0.4f)); - - var global = stats.Global; - var guild = stats.Guild; - - //xp bar - - img.FillPolygon(xpBgBrush, new[] { - new PointF(321, 104), - new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104), - new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235), - new PointF(286, 235), - }); - img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen, - new PointF(430, 130)); - - img.FillPolygon(xpBgBrush, new[] { - new PointF(282, 248), - new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248), - new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379), - new PointF(247, 379), - }); - img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen, - new PointF(400, 270)); - - if (stats.FullGuildStats.AwardedXp != 0) + using (var img = Image.Load(_images.XpCard.ToArray())) { - var sign = stats.FullGuildStats.AwardedXp > 0 - ? "+ " - : ""; - img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen, - new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335)); - } - //ranking + var username = stats.User.ToString(); + var usernameFont = _usernameFontFamily + .CreateFont(username.Length <= 6 + ? 50 + : 50 - username.Length); - img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White, - new PointF(148, 170)); + img.DrawText("@" + username, usernameFont, Rgba32.White, + new PointF(130, 5)); - img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, - new PointF(148, 317)); + // level - //time on this level + img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, + new PointF(47, 137)); - string GetTimeSpent(DateTime time) - { - var offset = DateTime.UtcNow - time; - return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; - } + img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, + new PointF(47, 285)); - img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, - new PointF(50, 197)); + //club name - img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, - new PointF(50, 344)); + var clubName = stats.User.Club?.ToString() ?? "-"; - //avatar + var clubFont = _clubFontFamily + .CreateFont(clubName.Length <= 8 + ? 35 + : 35 - (clubName.Length / 2)); - if (stats.User.AvatarId != null) - { - try + img.DrawText(clubName, clubFont, Rgba32.White, + new PointF(650 - clubName.Length * 10, 40)); + + var pen = new Pen(Rgba32.Black, 1); + var brush = Brushes.Solid(Rgba32.White); + var xpBgBrush = Brushes.Solid(new Rgba32(0, 0, 0, 0.4f)); + + var global = stats.Global; + var guild = stats.Guild; + + //xp bar + + img.FillPolygon(xpBgBrush, new[] { + new PointF(321, 104), + new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104), + new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235), + new PointF(286, 235), + }); + img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen, + new PointF(430, 130)); + + img.FillPolygon(xpBgBrush, new[] { + new PointF(282, 248), + new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248), + new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379), + new PointF(247, 379), + }); + img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen, + new PointF(400, 270)); + + if (stats.FullGuildStats.AwardedXp != 0) { - var avatarUrl = stats.User.RealAvatarUrl(); + var sign = stats.FullGuildStats.AwardedXp > 0 + ? "+ " + : ""; + img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen, + new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335)); + } - var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); - if (!succ) + //ranking + + img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White, + new PointF(148, 170)); + + img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, + new PointF(148, 317)); + + //time on this level + + string GetTimeSpent(DateTime time) + { + var offset = DateTime.UtcNow - time; + return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; + } + + img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, + new PointF(50, 197)); + + img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, + new PointF(50, 344)); + + //avatar + + if (stats.User.AvatarId != null) + { + try { - using (var temp = await http.GetStreamAsync(avatarUrl)) + var avatarUrl = stats.User.RealAvatarUrl(); + + var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); + if (!succ) { - var tempDraw = Image.Load(temp); - tempDraw = tempDraw.Resize(69, 70); - ApplyRoundedCorners(tempDraw, 35); - data = tempDraw.ToStream().ToArray(); + using (var temp = await http.GetStreamAsync(avatarUrl)) + using (var tempDraw = Image.Load(temp).Resize(69, 70)) + { + ApplyRoundedCorners(tempDraw, 35); + data = tempDraw.ToStream().ToArray(); + } + + await _cache.SetImageDataAsync(avatarUrl, data); } + var toDraw = Image.Load(data); - await _cache.SetImageDataAsync(avatarUrl, data); + + img.DrawImage(toDraw, + 1, + new Size(69, 70), + new Point(32, 10)); } - var toDraw = Image.Load(data); - - - img.DrawImage(toDraw, - 1, - new Size(69, 70), - new Point(32, 10)); - } - catch (Exception ex) - { - _log.Warn(ex); - } - } - - //club image - - if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl)) - { - var imgUrl = stats.User.Club.ImageUrl; - try - { - var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); - if (!succ) + catch (Exception ex) { - using (var temp = await http.GetStreamAsync(imgUrl)) - { - var tempDraw = Image.Load(temp); - tempDraw = tempDraw.Resize(45, 45); - ApplyRoundedCorners(tempDraw, 22.5f); - data = tempDraw.ToStream().ToArray(); - } - - await _cache.SetImageDataAsync(imgUrl, data); + _log.Warn(ex); } - var toDraw = Image.Load(data); - - img.DrawImage(toDraw, - 1, - new Size(45, 45), - new Point(722, 25)); } - catch (Exception ex) + + //club image + + if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl)) { - _log.Warn(ex); + var imgUrl = stats.User.Club.ImageUrl; + try + { + var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); + if (!succ) + { + using (var temp = await http.GetStreamAsync(imgUrl)) + using (var tempDraw = Image.Load(temp).Resize(45, 45)) + { + ApplyRoundedCorners(tempDraw, 22.5f); + data = tempDraw.ToStream().ToArray(); + } + + await _cache.SetImageDataAsync(imgUrl, data); + } + var toDraw = Image.Load(data); + + img.DrawImage(toDraw, + 1, + new Size(45, 45), + new Point(722, 25)); + } + catch (Exception ex) + { + _log.Warn(ex); + } } + + return img.Resize(432, 211).ToStream(); } - img.Resize(432, 211); - var arr = img.ToStream().ToArray(); - - //_log.Info("{0:F2} KB", arr.Length * 1.0f / 1.KB()); - - return img; }); diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs index ee88aa00..31a02b35 100644 --- a/src/NadekoBot/Modules/Xp/Xp.cs +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -32,7 +32,7 @@ namespace NadekoBot.Modules.Xp sw.Stop(); _log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds); sw.Restart(); - await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png") + await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png") .ConfigureAwait(false); sw.Stop(); _log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds); From a2c4695557313663e4675b8f6e14ff780bb049a7 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 13 Sep 2017 03:12:40 +0200 Subject: [PATCH 03/16] Global custom reactions now use redis pub/sub --- .../CustomReactions/CustomReactions.cs | 6 +-- .../Services/CustomReactionsService.cs | 39 ++++++++++++++++++- .../Database/Models/CustomReaction.cs | 6 ++- src/NadekoBot/Services/IDataCache.cs | 4 +- src/NadekoBot/Services/Impl/RedisCache.cs | 7 ++-- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index 9867dbcb..33af0699 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -58,8 +58,7 @@ namespace NadekoBot.Modules.CustomReactions if (channel == null) { - Array.Resize(ref _service.GlobalReactions, _service.GlobalReactions.Length + 1); - _service.GlobalReactions[_service.GlobalReactions.Length - 1] = cr; + await _service.AddGcr(cr).ConfigureAwait(false); } else { @@ -237,8 +236,7 @@ namespace NadekoBot.Modules.CustomReactions if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) { uow.CustomReactions.Remove(toDelete); - //todo 91 i can dramatically improve performance of this, if Ids are ordered. - _service.GlobalReactions = _service.GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray(); + await _service.DelGcr(toDelete.Id); success = true; } else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) diff --git a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs index 2944ab6c..bcb36784 100644 --- a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs +++ b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs @@ -15,6 +15,7 @@ using NadekoBot.Modules.CustomReactions.Extensions; using NadekoBot.Modules.Permissions.Common; using NadekoBot.Modules.Permissions.Services; using NadekoBot.Services.Impl; +using Newtonsoft.Json; namespace NadekoBot.Modules.CustomReactions.Services { @@ -32,9 +33,11 @@ namespace NadekoBot.Modules.CustomReactions.Services private readonly CommandHandler _cmd; private readonly IBotConfigProvider _bc; private readonly NadekoStrings _strings; + private readonly IDataCache _cache; public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings, - DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow) + DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow, + IDataCache cache) { _log = LogManager.GetCurrentClassLogger(); _db = db; @@ -43,10 +46,42 @@ namespace NadekoBot.Modules.CustomReactions.Services _cmd = cmd; _bc = bc; _strings = strings; - + _cache = cache; + + var sub = _cache.Redis.GetSubscriber(); + sub.Subscribe("gcr.added", (ch, msg) => + { + Array.Resize(ref GlobalReactions, GlobalReactions.Length + 1); + GlobalReactions[GlobalReactions.Length - 1] = JsonConvert.DeserializeObject(msg); + }, StackExchange.Redis.CommandFlags.FireAndForget); + sub.Subscribe("gcr.deleted", (ch, msg) => + { + var id = int.Parse(msg); + GlobalReactions = GlobalReactions.Where(cr => cr?.Id != id).ToArray(); + }, StackExchange.Redis.CommandFlags.FireAndForget); + var items = uow.CustomReactions.GetAll(); + GuildReactions = new ConcurrentDictionary(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); + foreach (var item in items) + { + _log.Info(item.Id); + _log.Info(item.Trigger); + _log.Info(item.GuildId); + } + } + + public Task AddGcr(CustomReaction cr) + { + var sub = _cache.Redis.GetSubscriber(); + return sub.PublishAsync("gcr.added", JsonConvert.SerializeObject(cr)); + } + + public Task DelGcr(int id) + { + var sub = _cache.Redis.GetSubscriber(); + return sub.PublishAsync("gcr.deleted", id); } public void ClearStats() => ReactionStats.Clear(); diff --git a/src/NadekoBot/Services/Database/Models/CustomReaction.cs b/src/NadekoBot/Services/Database/Models/CustomReaction.cs index 2f268849..cd57bc35 100644 --- a/src/NadekoBot/Services/Database/Models/CustomReaction.cs +++ b/src/NadekoBot/Services/Database/Models/CustomReaction.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations.Schema; using System.Text.RegularExpressions; namespace NadekoBot.Services.Database.Models @@ -6,7 +7,9 @@ namespace NadekoBot.Services.Database.Models public class CustomReaction : DbEntity { public ulong? GuildId { get; set; } + [NotMapped] + [JsonIgnore] public Regex Regex { get; set; } public string Response { get; set; } public string Trigger { get; set; } @@ -16,6 +19,7 @@ namespace NadekoBot.Services.Database.Models public bool AutoDeleteTrigger { get; set; } public bool DmResponse { get; set; } + [JsonIgnore] public bool IsGlobal => !GuildId.HasValue; public bool ContainsAnywhere { get; set; } diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs index 67223dfe..b708a66b 100644 --- a/src/NadekoBot/Services/IDataCache.cs +++ b/src/NadekoBot/Services/IDataCache.cs @@ -1,4 +1,5 @@ -using System; +using StackExchange.Redis; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,6 +9,7 @@ namespace NadekoBot.Services { public interface IDataCache { + ConnectionMultiplexer Redis { get; } Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key); Task SetImageDataAsync(string key, byte[] data); } diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs index b4d9a89d..5e54979d 100644 --- a/src/NadekoBot/Services/Impl/RedisCache.cs +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -5,13 +5,14 @@ namespace NadekoBot.Services.Impl { public class RedisCache : IDataCache { - private readonly ConnectionMultiplexer _redis; + public ConnectionMultiplexer Redis { get; } private readonly IDatabase _db; public RedisCache() { - _redis = ConnectionMultiplexer.Connect("localhost"); - _db = _redis.GetDatabase(); + Redis = ConnectionMultiplexer.Connect("localhost"); + Redis.PreserveAsyncOrder = false; + _db = Redis.GetDatabase(); } public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key) From 067297478edb7464df57369cbf8d41d6528b4281 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 13 Sep 2017 03:18:33 +0200 Subject: [PATCH 04/16] Removed leftover logs --- .../CustomReactions/Services/CustomReactionsService.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs index bcb36784..4cada529 100644 --- a/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs +++ b/src/NadekoBot/Modules/CustomReactions/Services/CustomReactionsService.cs @@ -64,12 +64,6 @@ namespace NadekoBot.Modules.CustomReactions.Services GuildReactions = new ConcurrentDictionary(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); - foreach (var item in items) - { - _log.Info(item.Id); - _log.Info(item.Trigger); - _log.Info(item.GuildId); - } } public Task AddGcr(CustomReaction cr) From 48adfc19af30042524bdb9d803888267497a279b Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 13 Sep 2017 05:10:26 +0200 Subject: [PATCH 05/16] Fixed slow `.xp` and `.xpglb` --- .../20170913022654_total-xp.Designer.cs | 1949 +++++++++++++++++ .../Migrations/20170913022654_total-xp.cs | 27 + src/NadekoBot/Migrations/MigrationQueries.cs | 4 + .../NadekoSqliteContextModelSnapshot.cs | 6 +- .../Modules/Xp/Services/XpService.cs | 7 +- src/NadekoBot/Modules/Xp/Xp.cs | 74 +- src/NadekoBot/Resources/CommandStrings.resx | 2 +- .../Services/Database/Models/DiscordUser.cs | 2 + .../Repositories/IDiscordUserRepository.cs | 2 + .../Database/Repositories/IXpRepository.cs | 4 +- .../Impl/DiscordUserRepository.cs | 19 + .../Repositories/Impl/XpRepository.cs | 20 - 12 files changed, 2063 insertions(+), 53 deletions(-) create mode 100644 src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs create mode 100644 src/NadekoBot/Migrations/20170913022654_total-xp.cs diff --git a/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs b/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs new file mode 100644 index 00000000..6764dd56 --- /dev/null +++ b/src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs @@ -0,0 +1,1949 @@ +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; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170913022654_total-xp")] + partial class totalxp + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + 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.Property("DateAdded"); + + 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("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + 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("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + 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("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + 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("DateAdded"); + + 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("DateAdded"); + + 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.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + 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("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 906, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + 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("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + 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.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 910, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + 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.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + 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.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + 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.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + 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.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + 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.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + 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.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + 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.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .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.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + 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.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + 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.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + 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); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170913022654_total-xp.cs b/src/NadekoBot/Migrations/20170913022654_total-xp.cs new file mode 100644 index 00000000..f608e696 --- /dev/null +++ b/src/NadekoBot/Migrations/20170913022654_total-xp.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class totalxp : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TotalXp", + table: "DiscordUser", + nullable: false, + defaultValue: 0); + + migrationBuilder.Sql(MigrationQueries.TotalXp); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TotalXp", + table: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/MigrationQueries.cs b/src/NadekoBot/Migrations/MigrationQueries.cs index 201f0c34..d89a1685 100644 --- a/src/NadekoBot/Migrations/MigrationQueries.cs +++ b/src/NadekoBot/Migrations/MigrationQueries.cs @@ -34,5 +34,9 @@ INSERT INTO DiscordUser FROM DiscordUser_tmp; DROP TABLE DiscordUser_tmp;"; + public static string TotalXp { get; } = +@"UPDATE DiscordUser +SET TotalXp = (SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId)"; + } } \ No newline at end of file diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 25117604..5f522e7b 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -464,12 +464,14 @@ namespace NadekoBot.Migrations b.Property("LastLevelUp") .ValueGeneratedOnAdd() - .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 236, DateTimeKind.Local)); + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 906, DateTimeKind.Local)); b.Property("LastXpGain"); b.Property("NotifyOnLevelUp"); + b.Property("TotalXp"); + b.Property("UserId"); b.Property("Username"); @@ -1362,7 +1364,7 @@ namespace NadekoBot.Migrations b.Property("LastLevelUp") .ValueGeneratedOnAdd() - .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 238, DateTimeKind.Local)); + .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 910, DateTimeKind.Local)); b.Property("NotifyOnLevelUp"); diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index 9bc1969b..ad701ed9 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -150,6 +150,7 @@ namespace NadekoBot.Modules.Xp.Services var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); usr.Xp += xp; + du.TotalXp += xp; if (du.Club != null) du.Club.Xp += xp; var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); @@ -311,7 +312,7 @@ namespace NadekoBot.Modules.Xp.Services { using (var uow = _db.UnitOfWork) { - return uow.Xp.GetUsersFor(page); + return uow.DiscordUsers.GetUsersXpLeaderboardFor(page); } } @@ -424,8 +425,8 @@ namespace NadekoBot.Modules.Xp.Services { du = uow.DiscordUsers.GetOrCreate(user); stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id); - totalXp = uow.Xp.GetTotalUserXp(user.Id); - globalRank = uow.Xp.GetUserGlobalRanking(user.Id); + totalXp = du.TotalXp; + globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id); guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId); } diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs index 31a02b35..f44bc483 100644 --- a/src/NadekoBot/Modules/Xp/Xp.cs +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -1,10 +1,12 @@ using Discord; using Discord.Commands; using Discord.WebSocket; +using NadekoBot.Common; using NadekoBot.Common.Attributes; using NadekoBot.Extensions; using NadekoBot.Modules.Xp.Common; using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services; using NadekoBot.Services.Database.Models; using System.Diagnostics; using System.Linq; @@ -15,14 +17,42 @@ namespace NadekoBot.Modules.Xp public partial class Xp : NadekoTopLevelModule { private readonly DiscordSocketClient _client; + private readonly DbService _db; - public Xp(DiscordSocketClient client) + public Xp(DiscordSocketClient client,DbService db) { _client = client; + _db = db; } + //[NadekoCommand, Usage, Description, Aliases] + //[RequireContext(ContextType.Guild)] + //[OwnerOnly] + //public async Task Populate() + //{ + // var rng = new NadekoRandom(); + // using (var uow = _db.UnitOfWork) + // { + // for (var i = 0ul; i < 1000000; i++) + // { + // uow.DiscordUsers.Add(new DiscordUser() + // { + // AvatarId = i.ToString(), + // Discriminator = "1234", + // UserId = i, + // Username = i.ToString(), + // Club = null, + // }); + // var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i); + // xp.Xp = rng.Next(100, 100000); + // } + // uow.Complete(); + // } + //} + [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] + //[Ratelimit(30)] public async Task Experience([Remainder]IUser user = null) { user = user ?? Context.User; @@ -44,7 +74,7 @@ namespace NadekoBot.Modules.Xp { page--; - if (page < 0) + if (page < 0 || page > 100) return Task.CompletedTask; var roles = _service.GetRoleRewards(Context.Guild.Id) @@ -173,7 +203,7 @@ namespace NadekoBot.Modules.Xp [RequireContext(ContextType.Guild)] public Task XpLeaderboard(int page = 1) { - if (--page < 0) + if (--page < 0 || page > 100) return Task.CompletedTask; return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => @@ -214,32 +244,28 @@ namespace NadekoBot.Modules.Xp [RequireContext(ContextType.Guild)] public async Task XpGlobalLeaderboard(int page = 1) { - if (--page < 0) + if (--page < 0 || page > 100) return; + var users = _service.GetUserXps(page); - await Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => + var embed = new EmbedBuilder() + .WithTitle(GetText("global_leaderboard")) + .WithOkColor(); + + if (!users.Any()) + embed.WithDescription("-"); + else { - var users = _service.GetUserXps(curPage); - - var embed = new EmbedBuilder() - .WithTitle(GetText("global_leaderboard")) - .WithOkColor(); - - if (!users.Any()) - return embed.WithDescription("-"); - else + for (int i = 0; i < users.Length; i++) { - for (int i = 0; i < users.Length; i++) - { - var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); - embed.AddField( - $"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}", - $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); - } - - return embed; + var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); + embed.AddField( + $"#{(i + 1 + page * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}", + $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); } - }, addPaginatedFooter: false); + } + + await Context.Channel.EmbedAsync(embed); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 079f5fc4..587d643e 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -3589,7 +3589,7 @@ `{0}xpex Role Excluded-Role` `{0}xpex Server` - Exclude a user or a role from the xp system, or whole current server. + Exclude a channel, role or current server from the xp system. xpnotify xpn diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs index 6c02388d..77a573b6 100644 --- a/src/NadekoBot/Services/Database/Models/DiscordUser.cs +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -10,6 +10,8 @@ namespace NadekoBot.Services.Database.Models public string AvatarId { get; set; } public ClubInfo Club { get; set; } + + public int TotalXp { get; set; } public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; public DateTime LastXpGain { get; set; } = DateTime.MinValue; public XpNotificationType NotifyOnLevelUp { get; set; } diff --git a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs index 480d0723..2d149d85 100644 --- a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs @@ -6,5 +6,7 @@ namespace NadekoBot.Services.Database.Repositories public interface IDiscordUserRepository : IRepository { DiscordUser GetOrCreate(IUser original); + int GetUserGlobalRanking(ulong id); + (ulong UserId, int TotalXp)[] GetUsersXpLeaderboardFor(int page); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs index 667dd315..10345d5d 100644 --- a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs @@ -6,9 +6,7 @@ namespace NadekoBot.Services.Database.Repositories { UserXpStats GetOrCreateUser(ulong guildId, ulong userId); int GetTotalUserXp(ulong userId); - UserXpStats[] GetUsersFor(ulong guildId, int page); - (ulong UserId, int TotalXp)[] GetUsersFor(int page); - int GetUserGlobalRanking(ulong userId); int GetUserGuildRanking(ulong userId, ulong guildId); + UserXpStats[] GetUsersFor(ulong guildId, int page); } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs index 6fa22ebc..034fbe7f 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -37,5 +37,24 @@ namespace NadekoBot.Services.Database.Repositories.Impl return toReturn; } + + public int GetUserGlobalRanking(ulong id) + { + return _set.Count(x => x.TotalXp > + _set.Where(y => y.UserId == id) + .DefaultIfEmpty() + .Sum(y => y.TotalXp)); + } + + public (ulong UserId, int TotalXp)[] GetUsersXpLeaderboardFor(int page) + { + return _set + .OrderByDescending(x => x.TotalXp) + .Skip(page * 9) + .Take(9) + .AsEnumerable() + .Select(y => (y.UserId, y.TotalXp)) + .ToArray(); + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs index 43d35202..cc026b6a 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs @@ -43,15 +43,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl .ToArray(); } - public int GetUserGlobalRanking(ulong userId) - { - return _set - .GroupBy(x => x.UserId) - .Count(x => x.Sum(y => y.Xp) > _set - .Where(y => y.UserId == userId) - .Sum(y => y.Xp)) + 1; - } - public int GetUserGuildRanking(ulong userId, ulong guildId) { return _set @@ -62,16 +53,5 @@ namespace NadekoBot.Services.Database.Repositories.Impl .DefaultIfEmpty() .Sum())) + 1; } - - public (ulong UserId, int TotalXp)[] GetUsersFor(int page) - { - return _set.GroupBy(x => x.UserId) - .OrderByDescending(x => x.Sum(y => y.Xp)) - .Skip(page * 9) - .Take(9) - .AsEnumerable() - .Select(x => (x.Key, x.Sum(y => y.Xp))) - .ToArray(); - } } } \ No newline at end of file From 0a5267604200f764b1cbdfbe6613231c9bfb566d Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 13 Sep 2017 08:12:07 +0200 Subject: [PATCH 06/16] possible fix for null migration error --- src/NadekoBot/Migrations/MigrationQueries.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NadekoBot/Migrations/MigrationQueries.cs b/src/NadekoBot/Migrations/MigrationQueries.cs index d89a1685..b0d8413a 100644 --- a/src/NadekoBot/Migrations/MigrationQueries.cs +++ b/src/NadekoBot/Migrations/MigrationQueries.cs @@ -36,7 +36,7 @@ INSERT INTO DiscordUser DROP TABLE DiscordUser_tmp;"; public static string TotalXp { get; } = @"UPDATE DiscordUser -SET TotalXp = (SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId)"; +SET TotalXp = ifnull((SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId), 0)"; } } \ No newline at end of file From 37412e4e730e3be1be1902a3d6fb335c416deeac Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Wed, 13 Sep 2017 21:15:49 +0200 Subject: [PATCH 07/16] Fixed shop role name. Fixed .xpglb (it will now show usernames and discriminators) --- src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs | 4 ++-- src/NadekoBot/Modules/Xp/Services/XpService.cs | 2 +- src/NadekoBot/Modules/Xp/Xp.cs | 4 ++-- .../Services/Database/Repositories/IDiscordUserRepository.cs | 2 +- .../Database/Repositories/Impl/DiscordUserRepository.cs | 3 +-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs b/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs index 660b0d33..6f50b7c3 100644 --- a/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs +++ b/src/NadekoBot/Modules/Gambling/FlowerShopCommands.cs @@ -331,7 +331,7 @@ namespace NadekoBot.Modules.Gambling var embed = new EmbedBuilder().WithOkColor(); if (entry.Type == ShopEntryType.Role) - return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true)) + return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true)); else if (entry.Type == ShopEntryType.List) @@ -349,7 +349,7 @@ namespace NadekoBot.Modules.Gambling { if (entry.Type == ShopEntryType.Role) { - return GetText("shop_role", Format.Bold(entry.RoleName)); + return GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")); } else if (entry.Type == ShopEntryType.List) { diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index ad701ed9..537953e2 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -308,7 +308,7 @@ namespace NadekoBot.Modules.Xp.Services } } - public (ulong UserId, int TotalXp)[] GetUserXps(int page) + public DiscordUser[] GetUserXps(int page) { using (var uow = _db.UnitOfWork) { diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs index f44bc483..a3356372 100644 --- a/src/NadekoBot/Modules/Xp/Xp.cs +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -258,9 +258,9 @@ namespace NadekoBot.Modules.Xp { for (int i = 0; i < users.Length; i++) { - var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); + var user = users[i]; embed.AddField( - $"#{(i + 1 + page * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}", + $"#{(i + 1 + page * 9)} {(user.ToString())}", $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs index 2d149d85..fb9360b7 100644 --- a/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IDiscordUserRepository.cs @@ -7,6 +7,6 @@ namespace NadekoBot.Services.Database.Repositories { DiscordUser GetOrCreate(IUser original); int GetUserGlobalRanking(ulong id); - (ulong UserId, int TotalXp)[] GetUsersXpLeaderboardFor(int page); + DiscordUser[] GetUsersXpLeaderboardFor(int page); } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs index 034fbe7f..16938a0d 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -46,14 +46,13 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Sum(y => y.TotalXp)); } - public (ulong UserId, int TotalXp)[] GetUsersXpLeaderboardFor(int page) + public DiscordUser[] GetUsersXpLeaderboardFor(int page) { return _set .OrderByDescending(x => x.TotalXp) .Skip(page * 9) .Take(9) .AsEnumerable() - .Select(y => (y.UserId, y.TotalXp)) .ToArray(); } } From 25258a0c6174f16eea3c60203468b493bd1b93dc Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Thu, 14 Sep 2017 19:37:41 +0200 Subject: [PATCH 08/16] Possible fix for redis on linux. Setgame/SetStream and rotating statuses will now properly work across shards. --- .../Modules/Administration/SelfCommands.cs | 6 +- .../Services/PlayingRotateService.cs | 68 +++++++++++-------- src/NadekoBot/NadekoBot.cs | 49 +++++++++++++ src/NadekoBot/Services/Impl/RedisCache.cs | 2 +- 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/NadekoBot/Modules/Administration/SelfCommands.cs b/src/NadekoBot/Modules/Administration/SelfCommands.cs index 622e1b6b..32d902df 100644 --- a/src/NadekoBot/Modules/Administration/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/SelfCommands.cs @@ -30,8 +30,9 @@ namespace NadekoBot.Modules.Administration private readonly IImagesService _images; private readonly MusicService _music; private readonly IBotConfigProvider _bc; + private readonly NadekoBot _bot; - public SelfCommands(DbService db, DiscordSocketClient client, + public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client, MusicService music, IImagesService images, IBotConfigProvider bc) { _db = db; @@ -39,6 +40,7 @@ namespace NadekoBot.Modules.Administration _images = images; _music = music; _bc = bc; + _bot = bot; } [NadekoCommand, Usage, Description, Aliases] @@ -349,7 +351,7 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task SetGame([Remainder] string game = null) { - await _client.SetGameAsync(game).ConfigureAwait(false); + await _bot.SetGameAsync(game).ConfigureAwait(false); await ReplyConfirmLocalized("set_game").ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs index 9786d085..d0cd5eef 100644 --- a/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs +++ b/src/NadekoBot/Modules/Administration/Services/PlayingRotateService.cs @@ -7,6 +7,7 @@ using NadekoBot.Modules.Music.Services; using NadekoBot.Services; using NadekoBot.Services.Database.Models; using NLog; +using System.Threading.Tasks; namespace NadekoBot.Modules.Administration.Services { @@ -16,6 +17,7 @@ namespace NadekoBot.Modules.Administration.Services private readonly DiscordSocketClient _client; private readonly MusicService _music; private readonly Logger _log; + private readonly IDataCache _cache; private readonly Replacer _rep; private readonly DbService _db; private readonly IBotConfigProvider _bcp; @@ -27,50 +29,60 @@ namespace NadekoBot.Modules.Administration.Services public int Index { get; set; } } - public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp, MusicService music, DbService db) + public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp, + MusicService music, DbService db, IDataCache cache, NadekoBot bot) { _client = client; _bcp = bcp; _music = music; _db = db; _log = LogManager.GetCurrentClassLogger(); - _rep = new ReplacementBuilder() - .WithClient(client) - .WithStats(client) - .WithMusic(music) - .Build(); + _cache = cache; - _t = new Timer(async (objState) => + if (client.ShardId == 0) { - try + + _rep = new ReplacementBuilder() + .WithClient(client) + .WithStats(client) + .WithMusic(music) + .Build(); + + _t = new Timer(async (objState) => { - bcp.Reload(); + try + { + bcp.Reload(); - var state = (TimerState)objState; - if (!BotConfig.RotatingStatuses) - return; - if (state.Index >= BotConfig.RotatingStatusMessages.Count) - state.Index = 0; + var state = (TimerState)objState; + if (!BotConfig.RotatingStatuses) + return; + if (state.Index >= BotConfig.RotatingStatusMessages.Count) + state.Index = 0; - if (!BotConfig.RotatingStatusMessages.Any()) - return; - var status = BotConfig.RotatingStatusMessages[state.Index++].Status; - if (string.IsNullOrWhiteSpace(status)) - return; + if (!BotConfig.RotatingStatusMessages.Any()) + return; + var status = BotConfig.RotatingStatusMessages[state.Index++].Status; + if (string.IsNullOrWhiteSpace(status)) + return; - status = _rep.Replace(status); + status = _rep.Replace(status); - try { await client.SetGameAsync(status).ConfigureAwait(false); } + try + { + await bot.SetGameAsync(status).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + } catch (Exception ex) { - _log.Warn(ex); + _log.Warn("Rotating playing status errored.\n" + ex); } - } - catch (Exception ex) - { - _log.Warn("Rotating playing status errored.\n" + ex); - } - }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + } } } } diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index a85da294..5fd01eba 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -20,6 +20,8 @@ using NadekoBot.Common.ShardCom; using NadekoBot.Common.TypeReaders; using NadekoBot.Common.TypeReaders.Models; using NadekoBot.Services.Database; +using StackExchange.Redis; +using Newtonsoft.Json; namespace NadekoBot { @@ -257,6 +259,7 @@ namespace NadekoBot .ForEach(x => CommandService.RemoveModuleAsync(x)); Ready.TrySetResult(true); + HandleStatusChanges(); _log.Info($"Shard {Client.ShardId} ready."); //_log.Info(await stats.Print().ConfigureAwait(false)); } @@ -319,5 +322,51 @@ namespace NadekoBot } })).Start(); } + + private void HandleStatusChanges() + { + var sub = Services.GetService().Redis.GetSubscriber(); + sub.Subscribe("status.game_set", async (ch, game) => + { + try + { + var obj = new { Name = default(string) }; + obj = JsonConvert.DeserializeAnonymousType(game, obj); + await Client.SetGameAsync(obj.Name).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, CommandFlags.FireAndForget); + + sub.Subscribe("status.stream_set", async (ch, streamData) => + { + try + { + var obj = new { Name = "", Url = "" }; + obj = JsonConvert.DeserializeAnonymousType(streamData, obj); + await Client.SetGameAsync(obj.Name, obj.Url, StreamType.Twitch).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, CommandFlags.FireAndForget); + } + + public Task SetGameAsync(string game) + { + var obj = new { Name = game }; + var sub = Services.GetService().Redis.GetSubscriber(); + return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj)); + } + + public Task SetStreamAsync(string name, string url) + { + var obj = new { Name = name, Url = url }; + var sub = Services.GetService().Redis.GetSubscriber(); + return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj)); + } } } diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs index 5e54979d..716b46ea 100644 --- a/src/NadekoBot/Services/Impl/RedisCache.cs +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -10,7 +10,7 @@ namespace NadekoBot.Services.Impl public RedisCache() { - Redis = ConnectionMultiplexer.Connect("localhost"); + Redis = ConnectionMultiplexer.Connect("127.0.0.1"); Redis.PreserveAsyncOrder = false; _db = Redis.GetDatabase(); } From 16fd835d4b8a743d15ad7080ad7b7df9322f484e Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 15 Sep 2017 02:42:51 +0200 Subject: [PATCH 09/16] Caching anime and manga seraches. Club disband error message fixed. --- .../Searches/Services/AnimeSearchService.cs | 30 ++++++++++++++----- src/NadekoBot/Modules/Xp/Club.cs | 2 +- src/NadekoBot/NadekoBot.cs | 7 ----- src/NadekoBot/Services/IDataCache.cs | 2 ++ src/NadekoBot/Services/Impl/RedisCache.cs | 11 +++++++ 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs index 638db338..1a372d8c 100644 --- a/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Services/AnimeSearchService.cs @@ -11,10 +11,14 @@ namespace NadekoBot.Modules.Searches.Services public class AnimeSearchService : INService { private readonly Logger _log; + private readonly IDataCache _cache; + private readonly HttpClient _http; - public AnimeSearchService() + public AnimeSearchService(IDataCache cache) { _log = LogManager.GetCurrentClassLogger(); + _cache = cache; + _http = new HttpClient(); } public async Task GetAnimeData(string query) @@ -25,11 +29,16 @@ namespace NadekoBot.Modules.Searches.Services { var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " ")); - using (var http = new HttpClient()) + link = link.ToLowerInvariant(); + var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); + if (!ok) { - var res = await http.GetStringAsync(link).ConfigureAwait(false); - return JsonConvert.DeserializeObject(res); + data = await _http.GetStringAsync(link).ConfigureAwait(false); + await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); } + + + return JsonConvert.DeserializeObject(data); } catch { @@ -44,12 +53,17 @@ namespace NadekoBot.Modules.Searches.Services try { - var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); - using (var http = new HttpClient()) + var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); + link = link.ToLowerInvariant(); + var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); + if (!ok) { - var res = await http.GetStringAsync(link).ConfigureAwait(false); - return JsonConvert.DeserializeObject(res); + data = await _http.GetStringAsync(link).ConfigureAwait(false); + await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); } + + + return JsonConvert.DeserializeObject(data); } catch { diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs index a911ab4f..8c4ce4c5 100644 --- a/src/NadekoBot/Modules/Xp/Club.cs +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -282,7 +282,7 @@ namespace NadekoBot.Modules.Xp } else { - await ReplyErrorLocalized("club_disaband_error").ConfigureAwait(false); + await ReplyErrorLocalized("club_disband_error").ConfigureAwait(false); } } diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 5fd01eba..95fd9574 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -242,13 +242,6 @@ namespace NadekoBot #if GLOBAL_NADEKO isPublicNadeko = true; #endif - //_log.Info(string.Join(", ", CommandService.Commands - // .Distinct(x => x.Name + x.Module.Name) - // .SelectMany(x => x.Aliases) - // .GroupBy(x => x) - // .Where(x => x.Count() > 1) - // .Select(x => x.Key + $"({x.Count()})"))); - //unload modules which are not available on the public bot if(isPublicNadeko) diff --git a/src/NadekoBot/Services/IDataCache.cs b/src/NadekoBot/Services/IDataCache.cs index b708a66b..3be7ad35 100644 --- a/src/NadekoBot/Services/IDataCache.cs +++ b/src/NadekoBot/Services/IDataCache.cs @@ -11,6 +11,8 @@ namespace NadekoBot.Services { ConnectionMultiplexer Redis { get; } Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key); + Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key); Task SetImageDataAsync(string key, byte[] data); + Task SetAnimeDataAsync(string link, string data); } } diff --git a/src/NadekoBot/Services/Impl/RedisCache.cs b/src/NadekoBot/Services/Impl/RedisCache.cs index 716b46ea..84083ef8 100644 --- a/src/NadekoBot/Services/Impl/RedisCache.cs +++ b/src/NadekoBot/Services/Impl/RedisCache.cs @@ -25,5 +25,16 @@ namespace NadekoBot.Services.Impl { return _db.StringSetAsync("image_" + key, data); } + + public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key) + { + string x = await _db.StringGetAsync("anime_" + key); + return (x != null, x); + } + + public Task SetAnimeDataAsync(string key, string data) + { + return _db.StringSetAsync("anime_" + key, data); + } } } From 4841418cff47c7e898f44bb8d57160338125230c Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 15 Sep 2017 22:17:31 +0200 Subject: [PATCH 10/16] Added `.clubadmin`, `.autoboobs` and `.autobutts`, cleaned up SongBuffer. --- .../20170915034808_club-admins.Designer.cs | 1951 +++++++++++++++++ .../Migrations/20170915034808_club-admins.cs | 25 + .../NadekoSqliteContextModelSnapshot.cs | 6 +- .../Modules/Music/Common/SongBuffer.cs | 326 +-- src/NadekoBot/Modules/NSFW/NSFW.cs | 120 +- src/NadekoBot/Modules/Xp/Club.cs | 40 +- .../Modules/Xp/Services/ClubService.cs | 52 +- .../Modules/Xp/Services/XpService.cs | 13 +- src/NadekoBot/Resources/CommandStrings.resx | 27 + .../Services/Database/Models/DiscordUser.cs | 1 + .../Services/Database/NadekoContext.cs | 4 +- .../Database/Repositories/IClubRepository.cs | 1 + .../Database/Repositories/IXpRepository.cs | 1 - .../Repositories/Impl/ClubRepository.cs | 24 + .../Repositories/Impl/XpRepository.cs | 5 - .../_strings/ResponseStrings.en-US.json | 8 +- 16 files changed, 2225 insertions(+), 379 deletions(-) create mode 100644 src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs create mode 100644 src/NadekoBot/Migrations/20170915034808_club-admins.cs diff --git a/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs b/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs new file mode 100644 index 00000000..d7b4c4d9 --- /dev/null +++ b/src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs @@ -0,0 +1,1951 @@ +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; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170915034808_club-admins")] + partial class clubadmins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + 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.Property("DateAdded"); + + 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("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + 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("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + 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("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + 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("DateAdded"); + + 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("DateAdded"); + + 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.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + 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("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("IsClubAdmin"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local)); + + b.Property("LastXpGain"); + + b.Property("NotifyOnLevelUp"); + + b.Property("TotalXp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + 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("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + 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.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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("DateAdded"); + + 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.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + 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.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + 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.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + 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.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + 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.ClubApplicants", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Applicants") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Bans") + .HasForeignKey("ClubId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + 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.DiscordUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club") + .WithMany("Users") + .HasForeignKey("ClubId"); + }); + + 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.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + 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.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .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.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + 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.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + 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.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + 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); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170915034808_club-admins.cs b/src/NadekoBot/Migrations/20170915034808_club-admins.cs new file mode 100644 index 00000000..b1c0d3e6 --- /dev/null +++ b/src/NadekoBot/Migrations/20170915034808_club-admins.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class clubadmins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsClubAdmin", + table: "DiscordUser", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsClubAdmin", + table: "DiscordUser"); + } + } +} diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 5f522e7b..cb6b041e 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -462,9 +462,11 @@ namespace NadekoBot.Migrations b.Property("Discriminator"); + b.Property("IsClubAdmin"); + b.Property("LastLevelUp") .ValueGeneratedOnAdd() - .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 906, DateTimeKind.Local)); + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local)); b.Property("LastXpGain"); @@ -1364,7 +1366,7 @@ namespace NadekoBot.Migrations b.Property("LastLevelUp") .ValueGeneratedOnAdd() - .HasDefaultValue(new DateTime(2017, 9, 13, 4, 26, 53, 910, DateTimeKind.Local)); + .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local)); b.Property("NotifyOnLevelUp"); diff --git a/src/NadekoBot/Modules/Music/Common/SongBuffer.cs b/src/NadekoBot/Modules/Music/Common/SongBuffer.cs index 8f76be79..2193877f 100644 --- a/src/NadekoBot/Modules/Music/Common/SongBuffer.cs +++ b/src/NadekoBot/Modules/Music/Common/SongBuffer.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace NadekoBot.Modules.Music.Common { @@ -18,27 +17,16 @@ namespace NadekoBot.Modules.Music.Common public string SongUri { get; private set; } - //private volatile bool restart = false; - public SongBuffer(string songUri, string skipTo, bool isLocal) { _log = LogManager.GetCurrentClassLogger(); - //_log.Warn(songUri); this.SongUri = songUri; this._isLocal = isLocal; try { this.p = StartFFmpegProcess(SongUri, 0); - var t = Task.Run(() => - { - this.p.BeginErrorReadLine(); - this.p.ErrorDataReceived += P_ErrorDataReceived; - this.p.WaitForExit(); - }); - this._outStream = this.p.StandardOutput.BaseStream; - } catch (System.ComponentModel.Win32Exception) { @@ -68,113 +56,14 @@ Check the guides for your platform on how to setup ffmpeg correctly: Arguments = args, UseShellExecute = false, RedirectStandardOutput = true, - RedirectStandardError = true, + RedirectStandardError = false, CreateNoWindow = true, }); } - private void P_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (string.IsNullOrWhiteSpace(e.Data)) - return; - _log.Error(">>> " + e.Data); - if (e.Data?.Contains("Error in the pull function") == true) - { - _log.Error("Ignore this."); - //restart = true; - } - } - private readonly object locker = new object(); private readonly bool _isLocal; - public Task StartBuffering(CancellationToken cancelToken) - { - var toReturn = new TaskCompletionSource(); - var _ = Task.Run(() => - { - try - { - - ////int maxLoopsPerSec = 25; - //var sw = Stopwatch.StartNew(); - ////var delay = 1000 / maxLoopsPerSec; - //int currentLoops = 0; - //int _bytesSent = 0; - //try - //{ - // //do - // //{ - // // if (restart) - // // { - // // var cur = _bytesSent / 3840 / (1000 / 20.0f); - // // _log.Info("Restarting"); - // // try { this.p.StandardOutput.Dispose(); } catch { } - // // try { this.p.Dispose(); } catch { } - // // this.p = StartFFmpegProcess(SongUri, cur); - // // } - // // restart = false; - // ++currentLoops; - // byte[] buffer = new byte[readSize]; - // int bytesRead = 1; - // while (!cancelToken.IsCancellationRequested && !this.p.HasExited) - // { - // bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, readSize, cancelToken).ConfigureAwait(false); - // _bytesSent += bytesRead; - // if (bytesRead == 0) - // break; - // bool written; - // do - // { - // lock (locker) - // written = _outStream.Write(buffer, 0, bytesRead); - // if (!written) - // await Task.Delay(2000, cancelToken); - // } - // while (!written && !cancelToken.IsCancellationRequested); - // lock (locker) - // if (_outStream.Length > 200_000 || bytesRead == 0) - // if (toReturn.TrySetResult(true)) - // _log.Info("Prebuffering finished in {0}", sw.Elapsed.TotalSeconds.ToString("F2")); - - // //_log.Info(_outStream.Length); - // await Task.Delay(10); - // } - // //if (cancelToken.IsCancellationRequested) - // // _log.Info("Song canceled"); - // //else if (p.HasExited) - // // _log.Info("Song buffered completely (FFmpeg exited)"); - // //else if (bytesRead == 0) - // // _log.Info("Nothing read"); - // //} - // //while (restart && !cancelToken.IsCancellationRequested); - //return Task.CompletedTask; - toReturn.TrySetResult(true); - } - catch (System.ComponentModel.Win32Exception) - { - _log.Error(@"You have not properly installed or configured FFMPEG. -Please install and configure FFMPEG to play music. -Check the guides for your platform on how to setup ffmpeg correctly: - Windows Guide: https://goo.gl/OjKk8F - Linux Guide: https://goo.gl/ShjCUo"); - } - catch (OperationCanceledException) { } - catch (InvalidOperationException) { } // when ffmpeg is disposed - catch (Exception ex) - { - _log.Info(ex); - } - finally - { - if (toReturn.TrySetResult(false)) - _log.Info("Prebuffering failed"); - } - }, cancelToken); - - return toReturn.Task; - } - public int Read(byte[] b, int offset, int toRead) { lock (locker) @@ -203,215 +92,4 @@ Check the guides for your platform on how to setup ffmpeg correctly: this.p.Dispose(); } } -} - -//namespace NadekoBot.Services.Music -//{ -// /// -// /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. -// /// It also help for large music by deleting files that are already seen. -// /// -// class SongBuffer : Stream -// { -// public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize) -// { -// MusicPlayer = musicPlayer; -// Basename = basename; -// SongInfo = songInfo; -// SkipTo = skipTo; -// MaxFileSize = maxFileSize; -// CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); -// _log = LogManager.GetCurrentClassLogger(); -// } - -// MusicPlayer MusicPlayer { get; } - -// private string Basename { get; } - -// private SongInfo SongInfo { get; } - -// private int SkipTo { get; } - -// private int MaxFileSize { get; } = 2.MiB(); - -// private long FileNumber = -1; - -// private long NextFileToRead = 0; - -// public bool BufferingCompleted { get; private set; } = false; - -// private ulong CurrentBufferSize = 0; - -// private FileStream CurrentFileStream; -// private Logger _log; - -// public Task BufferSong(CancellationToken cancelToken) => -// Task.Run(async () => -// { -// Process p = null; -// FileStream outStream = null; -// try -// { -// p = Process.Start(new ProcessStartInfo -// { -// FileName = "ffmpeg", -// Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet", -// UseShellExecute = false, -// RedirectStandardOutput = true, -// RedirectStandardError = false, -// CreateNoWindow = true, -// }); - -// byte[] buffer = new byte[81920]; -// int currentFileSize = 0; -// ulong prebufferSize = 100ul.MiB(); - -// outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); -// while (!p.HasExited) //Also fix low bandwidth -// { -// int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); -// if (currentFileSize >= MaxFileSize) -// { -// try -// { -// outStream.Dispose(); -// } -// catch { } -// outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); -// currentFileSize = bytesRead; -// } -// else -// { -// currentFileSize += bytesRead; -// } -// CurrentBufferSize += Convert.ToUInt64(bytesRead); -// await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); -// while (CurrentBufferSize > prebufferSize) -// await Task.Delay(100, cancelToken); -// } -// BufferingCompleted = true; -// } -// catch (System.ComponentModel.Win32Exception) -// { -// var oldclr = Console.ForegroundColor; -// Console.ForegroundColor = ConsoleColor.Red; -// Console.WriteLine(@"You have not properly installed or configured FFMPEG. -//Please install and configure FFMPEG to play music. -//Check the guides for your platform on how to setup ffmpeg correctly: -// Windows Guide: https://goo.gl/OjKk8F -// Linux Guide: https://goo.gl/ShjCUo"); -// Console.ForegroundColor = oldclr; -// } -// catch (Exception ex) -// { -// Console.WriteLine($"Buffering stopped: {ex.Message}"); -// } -// finally -// { -// if (outStream != null) -// outStream.Dispose(); -// if (p != null) -// { -// try -// { -// p.Kill(); -// } -// catch { } -// p.Dispose(); -// } -// } -// }); - -// /// -// /// Return the next file to read, and delete the old one -// /// -// /// Name of the file to read -// private string GetNextFile() -// { -// string filename = Basename + "-" + NextFileToRead; - -// if (NextFileToRead != 0) -// { -// try -// { -// CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); -// File.Delete(Basename + "-" + (NextFileToRead - 1)); -// } -// catch { } -// } -// NextFileToRead++; -// return filename; -// } - -// private bool IsNextFileReady() -// { -// return NextFileToRead <= FileNumber; -// } - -// private void CleanFiles() -// { -// for (long i = NextFileToRead - 1; i <= FileNumber; i++) -// { -// try -// { -// File.Delete(Basename + "-" + i); -// } -// catch { } -// } -// } - -// //Stream part - -// public override bool CanRead => true; - -// public override bool CanSeek => false; - -// public override bool CanWrite => false; - -// public override long Length => (long)CurrentBufferSize; - -// public override long Position { get; set; } = 0; - -// public override void Flush() { } - -// public override int Read(byte[] buffer, int offset, int count) -// { -// int read = CurrentFileStream.Read(buffer, offset, count); -// if (read < count) -// { -// if (!BufferingCompleted || IsNextFileReady()) -// { -// CurrentFileStream.Dispose(); -// CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); -// read += CurrentFileStream.Read(buffer, read + offset, count - read); -// } -// if (read < count) -// Array.Clear(buffer, read, count - read); -// } -// return read; -// } - -// public override long Seek(long offset, SeekOrigin origin) -// { -// throw new NotImplementedException(); -// } - -// public override void SetLength(long value) -// { -// throw new NotImplementedException(); -// } - -// public override void Write(byte[] buffer, int offset, int count) -// { -// throw new NotImplementedException(); -// } - -// public new void Dispose() -// { -// CurrentFileStream.Dispose(); -// MusicPlayer.SongCancelSource.Cancel(); -// CleanFiles(); -// base.Dispose(); -// } -// } -//} \ No newline at end of file +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 5e3591db..1ca27691 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -16,9 +16,13 @@ using NadekoBot.Modules.NSFW.Exceptions; namespace NadekoBot.Modules.NSFW { + // thanks to halitalf for adding autoboob and autobutt features :D public class NSFW : NadekoTopLevelModule { private static readonly ConcurrentDictionary _autoHentaiTimers = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _autoBoobTimers = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _autoButtTimers = new ConcurrentDictionary(); + private static readonly ConcurrentHashSet _hentaiBombBlacklist = new ConcurrentHashSet(); private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) @@ -49,10 +53,34 @@ namespace NadekoBot.Modules.NSFW .WithDescription($"[{GetText("tag")}: {tag}]({img})")) .ConfigureAwait(false); } + private async Task InternalBoobs(IMessageChannel Channel) + { + try + { + JToken obj; + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0]; + await Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false); + } + catch (Exception ex) + { + await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); + } + } + + private async Task InternalButts(IMessageChannel Channel) + { + try + { + JToken obj; + obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0]; + await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false); + } + catch (Exception ex) + { + await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); + } + } - [NadekoCommand, Usage, Description, Aliases] - public Task Hentai([Remainder] string tag = null) => - InternalHentai(Context.Channel, tag, false); #if !GLOBAL_NADEKO [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(ChannelPermission.ManageMessages)] @@ -65,7 +93,7 @@ namespace NadekoBot.Modules.NSFW if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer - await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false); + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); return; } @@ -99,8 +127,90 @@ namespace NadekoBot.Modules.NSFW interval, string.Join(", ", tagsArr)).ConfigureAwait(false); } + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(ChannelPermission.ManageMessages)] + public async Task AutoBoobs(int interval = 0) + { + Timer t; + + if (interval == 0) + { + if (!_autoBoobTimers.TryRemove(Context.Channel.Id, out t)) return; + + t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); + return; + } + + if (interval < 20) + return; + + t = new Timer(async (state) => + { + try + { + await InternalBoobs(Context.Channel).ConfigureAwait(false); + } + catch + { + // ignored + } + }, null, interval * 1000, interval * 1000); + + _autoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); + + await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(ChannelPermission.ManageMessages)] + public async Task AutoButts(int interval = 0) + { + Timer t; + + if (interval == 0) + { + if (!_autoButtTimers.TryRemove(Context.Channel.Id, out t)) return; + + t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer + await ReplyConfirmLocalized("stopped").ConfigureAwait(false); + return; + } + + if (interval < 20) + return; + + t = new Timer(async (state) => + { + try + { + await InternalButts(Context.Channel).ConfigureAwait(false); + } + catch + { + // ignored + } + }, null, interval * 1000, interval * 1000); + + _autoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => + { + old.Change(Timeout.Infinite, Timeout.Infinite); + return t; + }); + + await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); + } #endif + [NadekoCommand, Usage, Description, Aliases] + public Task Hentai([Remainder] string tag = null) => + InternalHentai(Context.Channel, tag, false); + [NadekoCommand, Usage, Description, Aliases] public async Task HentaiBomb([Remainder] string tag = null) { @@ -199,7 +309,7 @@ namespace NadekoBot.Modules.NSFW tag = tag.Trim().ToLowerInvariant(); var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag); - if(added) + if (added) await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false); else await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs index 8c4ce4c5..06fd5336 100644 --- a/src/NadekoBot/Modules/Xp/Club.cs +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -26,6 +26,27 @@ namespace NadekoBot.Modules.Xp _client = client; } + [NadekoCommand, Usage, Description, Aliases] + public async Task ClubAdmin([Remainder]IUser toAdmin) + { + bool admin; + try + { + admin = _service.ToggleAdmin(Context.User, toAdmin); + } + catch (InvalidOperationException) + { + await ReplyErrorLocalized("club_admin_error").ConfigureAwait(false); + return; + } + + if(admin) + await ReplyConfirmLocalized("club_admin_add", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); + else + await ReplyConfirmLocalized("club_admin_remove", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); + + } + [NadekoCommand, Usage, Description, Aliases] public async Task ClubCreate([Remainder]string clubName) { @@ -97,7 +118,14 @@ namespace NadekoBot.Modules.Xp .AddField("Level Req.", club.MinimumLevelReq.ToString(), true) .AddField("Members", string.Join("\n", club.Users .Skip(page * 10) - .Take(10)), false); + .Take(10) + .OrderByDescending(x => x.IsClubAdmin) + .Select(x => + { + if (x.IsClubAdmin) + return x.ToString() + "⭐"; + return x.ToString(); + })), false); if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) return embed.WithThumbnailUrl(club.ImageUrl); @@ -112,7 +140,7 @@ namespace NadekoBot.Modules.Xp if (--page < 0) return Task.CompletedTask; - var club = _service.GetBansAndApplications(Context.User.Id); + var club = _service.GetClubWithBansAndApplications(Context.User.Id); if (club == null) return ReplyErrorLocalized("club_not_exists"); @@ -143,11 +171,11 @@ namespace NadekoBot.Modules.Xp if (--page < 0) return Task.CompletedTask; - var club = _service.GetBansAndApplications(Context.User.Id); + var club = _service.GetClubWithBansAndApplications(Context.User.Id); if (club == null) return ReplyErrorLocalized("club_not_exists"); - var bans = club + var apps = club .Applicants .Select(x => x.User) .ToArray(); @@ -155,7 +183,7 @@ namespace NadekoBot.Modules.Xp return Context.Channel.SendPaginatedConfirmAsync(_client, page, curPage => { - var toShow = string.Join("\n", bans + var toShow = string.Join("\n", apps .Skip(page * 10) .Take(10) .Select(x => x.ToString())); @@ -164,7 +192,7 @@ namespace NadekoBot.Modules.Xp .WithTitle(GetText("club_apps_for", club.ToString())) .WithDescription(toShow); - }, bans.Length / 10); + }, apps.Length / 10); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Xp/Services/ClubService.cs b/src/NadekoBot/Modules/Xp/Services/ClubService.cs index f8d08f66..362067eb 100644 --- a/src/NadekoBot/Modules/Xp/Services/ClubService.cs +++ b/src/NadekoBot/Modules/Xp/Services/ClubService.cs @@ -27,10 +27,11 @@ namespace NadekoBot.Modules.Xp.Services { var du = uow.DiscordUsers.GetOrCreate(user); uow._context.SaveChanges(); - var xp = new LevelStats(uow.Xp.GetTotalUserXp(user.Id)); + var xp = new LevelStats(du.TotalXp); if (xp.Level >= 5 && du.Club == null) { + du.IsClubAdmin = true; du.Club = new ClubInfo() { Name = clubName, @@ -52,6 +53,27 @@ namespace NadekoBot.Modules.Xp.Services return true; } + public bool ToggleAdmin(IUser owner, IUser toAdmin) + { + bool newState; + using (var uow = _db.UnitOfWork) + { + var club = uow.Clubs.GetByOwner(owner.Id); + var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin); + + if (club.OwnerId == adminUser.Id) + return true; + + if (club == null || club.Owner.UserId != owner.Id || + !club.Users.Contains(adminUser)) + throw new InvalidOperationException(); + + newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin; + uow.Complete(); + } + return newState; + } + public ClubInfo GetClubByMember(IUser user) { using (var uow = _db.UnitOfWork) @@ -107,7 +129,7 @@ namespace NadekoBot.Modules.Xp.Services uow._context.SaveChanges(); if (du.Club != null - || new LevelStats(uow.Xp.GetTotalUserXp(user.Id)).Level < club.MinimumLevelReq + || new LevelStats(du.TotalXp).Level < club.MinimumLevelReq || club.Bans.Any(x => x.UserId == du.Id) || club.Applicants.Any(x => x.UserId == du.Id)) { @@ -134,11 +156,7 @@ namespace NadekoBot.Modules.Xp.Services discordUser = null; using (var uow = _db.UnitOfWork) { - var club = uow.Clubs.GetByOwner(clubOwnerUserId, - set => set.Include(x => x.Applicants) - .ThenInclude(x => x.Club) - .Include(x => x.Applicants) - .ThenInclude(x => x.User)); + var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId); if (club == null) return false; @@ -147,6 +165,7 @@ namespace NadekoBot.Modules.Xp.Services return false; applicant.User.Club = club; + applicant.User.IsClubAdmin = false; club.Applicants.Remove(applicant); //remove that user's all other applications @@ -159,15 +178,11 @@ namespace NadekoBot.Modules.Xp.Services return true; } - public ClubInfo GetBansAndApplications(ulong ownerUserId) + public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId) { using (var uow = _db.UnitOfWork) { - return uow.Clubs.GetByOwner(ownerUserId, - x => x.Include(y => y.Bans) - .ThenInclude(y => y.User) - .Include(y => y.Applicants) - .ThenInclude(y => y.User)); + return uow.Clubs.GetByOwnerOrAdmin(ownerUserId); } } @@ -180,6 +195,7 @@ namespace NadekoBot.Modules.Xp.Services return false; du.Club = null; + du.IsClubAdmin = false; uow.Complete(); } return true; @@ -221,9 +237,7 @@ namespace NadekoBot.Modules.Xp.Services { using (var uow = _db.UnitOfWork) { - club = uow.Clubs.GetByOwner(ownerUserId, - set => set.Include(x => x.Applicants) - .ThenInclude(x => x.User)); + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); if (club == null) return false; @@ -256,9 +270,7 @@ namespace NadekoBot.Modules.Xp.Services { using (var uow = _db.UnitOfWork) { - club = uow.Clubs.GetByOwner(ownerUserId, - set => set.Include(x => x.Bans) - .ThenInclude(x => x.User)); + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); if (club == null) return false; @@ -277,7 +289,7 @@ namespace NadekoBot.Modules.Xp.Services { using (var uow = _db.UnitOfWork) { - club = uow.Clubs.GetByOwner(ownerUserId); + club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); if (club == null) return false; diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs index 537953e2..09fa7ec9 100644 --- a/src/NadekoBot/Modules/Xp/Services/XpService.cs +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -144,7 +144,7 @@ namespace NadekoBot.Modules.Xp.Services du.LastXpGain = DateTime.UtcNow; - var globalXp = uow.Xp.GetTotalUserXp(item.Key.User.Id); + var globalXp = du.TotalXp; var oldGlobalLevelData = new LevelStats(globalXp); var newGlobalLevelData = new LevelStats(globalXp + xp); @@ -403,17 +403,6 @@ namespace NadekoBot.Modules.Xp.Services return _rewardedUsers.Add(userId); } - public LevelStats GetGlobalUserStats(ulong userId) - { - int totalXp; - using (var uow = _db.UnitOfWork) - { - totalXp = uow.Xp.GetTotalUserXp(userId); - } - - return new LevelStats(totalXp); - } - public FullUserStats GetUserStats(IGuildUser user) { DiscordUser du; diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 587d643e..957e8059 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -3780,4 +3780,31 @@ Clears nsfw cache. + + clubadmin + + + `{0}clubadmin` + + + Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications. + + + autoboobs + + + Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable. + + + `{0}autoboobs 30` or `{0}autoboobs` + + + autobutts + + + Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable. + + + `{0}autobutts 30` or `{0}autobutts` + diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs index 77a573b6..654408e2 100644 --- a/src/NadekoBot/Services/Database/Models/DiscordUser.cs +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -10,6 +10,7 @@ namespace NadekoBot.Services.Database.Models public string AvatarId { get; set; } public ClubInfo Club { get; set; } + public bool IsClubAdmin { get; set; } public int TotalXp { get; set; } public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 5c04c816..0df96909 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -329,7 +329,7 @@ namespace NadekoBot.Services.Database #region ClubManytoMany modelBuilder.Entity() - .HasKey(t => new { t.ClubId, t.UserId }); + .HasKey(t => new { t.ClubId, t.UserId }); modelBuilder.Entity() .HasOne(pt => pt.User) @@ -340,7 +340,7 @@ namespace NadekoBot.Services.Database .WithMany(x => x.Applicants); modelBuilder.Entity() - .HasKey(t => new { t.ClubId, t.UserId }); + .HasKey(t => new { t.ClubId, t.UserId }); modelBuilder.Entity() .HasOne(pt => pt.User) diff --git a/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs index 086d638b..66ad3d92 100644 --- a/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs @@ -10,6 +10,7 @@ namespace NadekoBot.Services.Database.Repositories int GetNextDiscrim(string clubName); ClubInfo GetByName(string v, int discrim, Func, IQueryable> func = null); ClubInfo GetByOwner(ulong userId, Func, IQueryable> func = null); + ClubInfo GetByOwnerOrAdmin(ulong userId); ClubInfo GetByMember(ulong userId, Func, IQueryable> func = null); ClubInfo[] GetClubLeaderboardPage(int page); } diff --git a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs index 10345d5d..f62394d7 100644 --- a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs @@ -5,7 +5,6 @@ namespace NadekoBot.Services.Database.Repositories public interface IXpRepository : IRepository { UserXpStats GetOrCreateUser(ulong guildId, ulong userId); - int GetTotalUserXp(ulong userId); int GetUserGuildRanking(ulong userId, ulong guildId); UserXpStats[] GetUsersFor(ulong guildId, int page); } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs index eb09e8b0..7eabbb1c 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs @@ -24,6 +24,30 @@ namespace NadekoBot.Services.Database.Repositories.Impl return func(_set).FirstOrDefault(x => x.Owner.UserId == userId); } + public ClubInfo GetByOwnerOrAdmin(ulong userId) + { + return _set + .Include(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Owner) + .FirstOrDefault(x => x.Owner.UserId == userId) ?? + _context.Set() + .Include(x => x.Club) + .ThenInclude(x => x.Users) + .Include(x => x.Club) + .ThenInclude(x => x.Bans) + .ThenInclude(x => x.User) + .Include(x => x.Club) + .ThenInclude(x => x.Applicants) + .ThenInclude(x => x.User) + .Include(x => x.Club) + .ThenInclude(x => x.Owner) + .FirstOrDefault(x => x.UserId == userId && x.IsClubAdmin) + ?.Club; + } + public ClubInfo GetByName(string name, int discrim, Func, IQueryable> func = null) { if (func == null) diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs index cc026b6a..b411cdad 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs @@ -29,11 +29,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl return usr; } - public int GetTotalUserXp(ulong userId) - { - return _set.Where(x => x.UserId == userId).Sum(x => x.Xp); - } - public UserXpStats[] GetUsersFor(ulong guildId, int page) { return _set.Where(x => x.GuildId == guildId) diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 1ee47eed..ebd9c435 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -13,7 +13,6 @@ "customreactions_stats_not_found": "No stats for that trigger found, no action taken.", "customreactions_trigger": "Trigger", "customreactions_redacted_too_long": "Redecated because it's too long.", - "nsfw_autohentai_stopped": "Autohentai stopped.", "nsfw_not_found": "No results found.", "nsfw_blacklisted_tag_list": "List of blacklisted tags:", "nsfw_blacklisted_tag": "One or more tags you've used are blacklisted", @@ -868,5 +867,10 @@ "xp_club_icon_set": "New club icon set.", "xp_club_bans_for": "Bans for {0} club", "xp_club_apps_for": "Applicants for {0} club", - "xp_club_leaderboard": "Club leaderboard - page {0}" + "xp_club_leaderboard": "Club leaderboard - page {0}", + "xp_club_admin_add": "{0} is now a club admin.", + "xp_club_admin_remove": "{0} is no longer club admin.", + "xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.", + "nsfw_started": "Started. Reposting every {0}s.", + "nsfw_stopped": "Stopped reposting." } \ No newline at end of file From 9cff3b59c1965a6a280176e61cff622a7db07fe3 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 15 Sep 2017 22:19:31 +0200 Subject: [PATCH 11/16] clubapps and clubbans now have ok color line --- src/NadekoBot/Modules/Xp/Club.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs index 06fd5336..6ef7b5b5 100644 --- a/src/NadekoBot/Modules/Xp/Club.cs +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -159,7 +159,8 @@ namespace NadekoBot.Modules.Xp return new EmbedBuilder() .WithTitle(GetText("club_bans_for", club.ToString())) - .WithDescription(toShow); + .WithDescription(toShow) + .WithOkColor(); }, bans.Length / 10); } @@ -190,7 +191,8 @@ namespace NadekoBot.Modules.Xp return new EmbedBuilder() .WithTitle(GetText("club_apps_for", club.ToString())) - .WithDescription(toShow); + .WithDescription(toShow) + .WithOkColor(); }, apps.Length / 10); } From a127e43dc0b137986bef355decd5af58d19062f5 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Fri, 15 Sep 2017 22:22:03 +0200 Subject: [PATCH 12/16] Version upped --- 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 8e94ceec..9b65c5f7 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "1.8.4"; + public const string BotVersion = "1.8.5"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From 6bea4b9f029f19d7d48e9460b786d534ed908f18 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 16 Sep 2017 02:10:22 +0200 Subject: [PATCH 13/16] Upgraded to .net core 2.0 --- global.json | 2 +- src/NadekoBot/NadekoBot.csproj | 73 ++++++------------- src/NadekoBot/NadekoBot.nuspec | 15 ---- src/NadekoBot/NadekoBot.xproj.DotSettings | 7 -- src/NadekoBot/Properties/AssemblyInfo.cs | 19 ----- .../Services/Database/NadekoContext.cs | 23 ++---- src/NadekoBot/Services/DbService.cs | 8 +- src/NadekoBot/Services/LogSetup.cs | 2 +- 8 files changed, 36 insertions(+), 113 deletions(-) delete mode 100644 src/NadekoBot/NadekoBot.nuspec delete mode 100644 src/NadekoBot/NadekoBot.xproj.DotSettings delete mode 100644 src/NadekoBot/Properties/AssemblyInfo.cs diff --git a/global.json b/global.json index 3b965cc4..68be1221 100644 --- a/global.json +++ b/global.json @@ -1,3 +1,3 @@ { - "sdk": { "version": "1.0.1" } + "sdk": { "version": "2.0.0" } } \ No newline at end of file diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 1a5bf786..3834ef6e 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -1,31 +1,18 @@  - General purpose Discord bot written in C#. - Kwoth - Kwoth - Kwoth - netcoreapp1.1 - true - NadekoBot - Exe - NadekoBot - 1.1.1 - $(PackageTargetFallback);dnxcore50;portable-net45+win8+wpa81 - false - false - false - false - False - 1.0.0.0 - 1.0.0.0 + netcoreapp2.0 + 2.0.0 + exe + $(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81 nadeko_icon.ico win7-x64 Debug;Release;global_nadeko + latest - 1.4.1 + 1.9.1 $(VersionPrefix).$(VersionSuffix) $(VersionPrefix) @@ -60,28 +47,24 @@ - - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + - - + @@ -89,21 +72,13 @@ $(NoWarn);CS1573;CS1591 - - latest - - - latest - - - - latest + true - - + + diff --git a/src/NadekoBot/NadekoBot.nuspec b/src/NadekoBot/NadekoBot.nuspec deleted file mode 100644 index 109f83d5..00000000 --- a/src/NadekoBot/NadekoBot.nuspec +++ /dev/null @@ -1,15 +0,0 @@ - - - - NadekoBot - 1.4.0-2$suffix$ - NadekoBot - Kwoth - Kwoth - General purpose discord chat bot written in C#. - nadeko;bot;nadekobot;discord bot - https://github.com/Kwoth/NadekoBot - https://choosealicense.com/licenses/unlicense/ - false - - \ No newline at end of file diff --git a/src/NadekoBot/NadekoBot.xproj.DotSettings b/src/NadekoBot/NadekoBot.xproj.DotSettings deleted file mode 100644 index c5dd7773..00000000 --- a/src/NadekoBot/NadekoBot.xproj.DotSettings +++ /dev/null @@ -1,7 +0,0 @@ - - True - True - True - True - True - True \ No newline at end of file diff --git a/src/NadekoBot/Properties/AssemblyInfo.cs b/src/NadekoBot/Properties/AssemblyInfo.cs deleted file mode 100644 index ca3bd293..00000000 --- a/src/NadekoBot/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NadekoBot")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyInformationalVersion("1.0")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f8225ac4-3cbc-40b4-bcf3-1cacf276bf29")] diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 0df96909..7fd511de 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -3,22 +3,16 @@ using System.Collections.Generic; using System.Linq; using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; -using Microsoft.EntityFrameworkCore.Infrastructure; using System; +using Microsoft.EntityFrameworkCore.Design; namespace NadekoBot.Services.Database { - - public class NadekoContextFactory : IDbContextFactory + public class NadekoContextFactory : IDesignTimeDbContextFactory { - /// - /// :\ Used for migrations - /// - /// - /// - public NadekoContext Create(DbContextFactoryOptions options) + public NadekoContext CreateDbContext(string[] args) { - var optionsBuilder = new DbContextOptionsBuilder(); + var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); var ctx = new NadekoContext(optionsBuilder.Options); ctx.Database.SetCommandTimeout(60); @@ -58,12 +52,7 @@ namespace NadekoBot.Services.Database public DbSet ModulePrefixes { get; set; } public DbSet RewardedUsers { get; set; } - public NadekoContext() : base() - { - - } - - public NadekoContext(DbContextOptions options) : base(options) + public NadekoContext(DbContextOptions options) : base(options) { } @@ -231,7 +220,7 @@ namespace NadekoBot.Services.Database musicPlaylistEntity .HasMany(p => p.Songs) .WithOne() - .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade); #endregion diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index 5bc9f6bf..277bc33d 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -6,19 +6,19 @@ namespace NadekoBot.Services { public class DbService { - private readonly DbContextOptions options; - private readonly DbContextOptions migrateOptions; + private readonly DbContextOptions options; + private readonly DbContextOptions migrateOptions; private readonly string _connectionString; public DbService(IBotCredentials creds) { _connectionString = creds.Db.ConnectionString; - var optionsBuilder = new DbContextOptionsBuilder(); + var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(creds.Db.ConnectionString); options = optionsBuilder.Options; - optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(creds.Db.ConnectionString, x => x.SuppressForeignKeyEnforcement()); migrateOptions = optionsBuilder.Options; } diff --git a/src/NadekoBot/Services/LogSetup.cs b/src/NadekoBot/Services/LogSetup.cs index 0d3234ee..9cf85799 100644 --- a/src/NadekoBot/Services/LogSetup.cs +++ b/src/NadekoBot/Services/LogSetup.cs @@ -11,7 +11,7 @@ namespace NadekoBot.Services var logConfig = new LoggingConfiguration(); var consoleTarget = new ColoredConsoleTarget() { - Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" + Layout = @"${date:format=HH\:mm\:ss} ${logger:shortName=True} | ${message}" }; logConfig.AddTarget("Console", consoleTarget); From 9f2d9d6710a2f84eead7fa8e55913ecd512e056b Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sat, 16 Sep 2017 04:35:02 +0200 Subject: [PATCH 14/16] version upped --- 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 9b65c5f7..24bc273e 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "1.8.5"; + public const string BotVersion = "1.9.0"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; From bdc6974451f88e1bc2334bdc0ba6bd047dcb7f38 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 17 Sep 2017 07:28:48 +0200 Subject: [PATCH 15/16] Commands strings are now in data/command_strings.json. Database path no longer has ./ prefix and filename -> Data Source. Fixed permissions migration due to new EF behaviour. Although commands will now error if they don't have their entry in the command strings - needs fixing. --- src/NadekoBot/Common/Attributes/Aliases.cs | 4 +- .../Common/Attributes/Description.cs | 2 +- .../Common/Attributes/NadekoCommand.cs | 2 +- src/NadekoBot/Common/Attributes/Usage.cs | 2 +- src/NadekoBot/Common/CommandData.cs | 9 + src/NadekoBot/Modules/Games/Games.cs | 2 +- .../Services/PermissionsService.cs | 4 +- src/NadekoBot/NadekoBot.csproj | 6 - .../Services/Database/NadekoContext.cs | 8 +- src/NadekoBot/Services/DbService.cs | 15 +- src/NadekoBot/Services/Impl/BotCredentials.cs | 4 +- src/NadekoBot/Services/Impl/Localization.cs | 17 +- src/NadekoBot/credentials_example.json | 2 +- src/NadekoBot/data/command_strings.json | 2057 +++++++++++++++++ 14 files changed, 2107 insertions(+), 27 deletions(-) create mode 100644 src/NadekoBot/Common/CommandData.cs create mode 100644 src/NadekoBot/data/command_strings.json diff --git a/src/NadekoBot/Common/Attributes/Aliases.cs b/src/NadekoBot/Common/Attributes/Aliases.cs index 7f365078..b6ebbf81 100644 --- a/src/NadekoBot/Common/Attributes/Aliases.cs +++ b/src/NadekoBot/Common/Attributes/Aliases.cs @@ -3,11 +3,13 @@ using System.Runtime.CompilerServices; using Discord.Commands; using NadekoBot.Services.Impl; +//todo what if it doesn't exist + namespace NadekoBot.Common.Attributes { public class Aliases : AliasAttribute { - public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ').Skip(1).ToArray()) + public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ').Skip(1).ToArray()) { } } diff --git a/src/NadekoBot/Common/Attributes/Description.cs b/src/NadekoBot/Common/Attributes/Description.cs index 1b0e7957..7ebbac47 100644 --- a/src/NadekoBot/Common/Attributes/Description.cs +++ b/src/NadekoBot/Common/Attributes/Description.cs @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes { public class Description : SummaryAttribute { - public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_desc")) + public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Desc) { } diff --git a/src/NadekoBot/Common/Attributes/NadekoCommand.cs b/src/NadekoBot/Common/Attributes/NadekoCommand.cs index a471e007..eda997fd 100644 --- a/src/NadekoBot/Common/Attributes/NadekoCommand.cs +++ b/src/NadekoBot/Common/Attributes/NadekoCommand.cs @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes { public class NadekoCommand : CommandAttribute { - public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ')[0]) + public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd) { } diff --git a/src/NadekoBot/Common/Attributes/Usage.cs b/src/NadekoBot/Common/Attributes/Usage.cs index 2991c6aa..97d342ee 100644 --- a/src/NadekoBot/Common/Attributes/Usage.cs +++ b/src/NadekoBot/Common/Attributes/Usage.cs @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes { public class Usage : RemarksAttribute { - public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_usage")) + public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Usage) { } diff --git a/src/NadekoBot/Common/CommandData.cs b/src/NadekoBot/Common/CommandData.cs new file mode 100644 index 00000000..59712eae --- /dev/null +++ b/src/NadekoBot/Common/CommandData.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Common +{ + public class CommandData + { + public string Cmd { get; set; } + public string Usage { get; set; } + public string Desc { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 4d7a0ea3..07fd450e 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -38,7 +38,7 @@ namespace NadekoBot.Modules.Games } [NadekoCommand, Usage, Description, Aliases] - public async Task _8Ball([Remainder] string question = null) + public async Task EightBall([Remainder] string question = null) { if (string.IsNullOrWhiteSpace(question)) return; diff --git a/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs index 4329850c..9b218758 100644 --- a/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs +++ b/src/NadekoBot/Modules/Permissions/Services/PermissionsService.cs @@ -135,11 +135,11 @@ namespace NadekoBot.Modules.Permissions.Services { var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; uow._context.Database.ExecuteSqlCommand( - $@"UPDATE {nameof(Permissionv2)} + @"UPDATE Permissionv2 SET secondaryTargetName=trim(substr(secondaryTargetName, 3)) WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%'; -UPDATE {nameof(Permissionv2)} +UPDATE Permissionv2 SET secondaryTargetName=substr(secondaryTargetName, 2) WHERE secondaryTargetName LIKE '.%' OR secondaryTargetName LIKE '~%' OR diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 3834ef6e..da1a1e22 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -80,10 +80,4 @@ - - - - Designer - - diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 7fd511de..5142e8e5 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -5,15 +5,19 @@ using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; using System; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Data.Sqlite; +using System.IO; namespace NadekoBot.Services.Database { public class NadekoContextFactory : IDesignTimeDbContextFactory - { + { public NadekoContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); + var builder = new SqliteConnectionStringBuilder("Data Source=data/NadekoBot.db"); + builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); + optionsBuilder.UseSqlite(builder.ToString()); var ctx = new NadekoContext(optionsBuilder.Options); ctx.Database.SetCommandTimeout(60); return ctx; diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index 277bc33d..56c6940a 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -1,5 +1,8 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; +using System; +using System.IO; using System.Linq; namespace NadekoBot.Services @@ -9,17 +12,17 @@ namespace NadekoBot.Services private readonly DbContextOptions options; private readonly DbContextOptions migrateOptions; - private readonly string _connectionString; - public DbService(IBotCredentials creds) { - _connectionString = creds.Db.ConnectionString; + var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString); + builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); + var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite(creds.Db.ConnectionString); + optionsBuilder.UseSqlite(builder.ToString()); options = optionsBuilder.Options; optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite(creds.Db.ConnectionString, x => x.SuppressForeignKeyEnforcement()); + optionsBuilder.UseSqlite(builder.ToString(), x => x.SuppressForeignKeyEnforcement()); migrateOptions = optionsBuilder.Options; } diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index e8a96de5..341c947e 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -101,7 +101,7 @@ namespace NadekoBot.Services.Impl ? "sqlite" : dbSection["Type"], string.IsNullOrWhiteSpace(dbSection["ConnectionString"]) - ? "Filename=./data/NadekoBot.db" + ? "Data Source=data/NadekoBot.db" : dbSection["ConnectionString"]); } catch (Exception ex) @@ -125,7 +125,7 @@ namespace NadekoBot.Services.Impl public string SoundCloudClientId { get; set; } = ""; public string CleverbotApiKey { get; } = ""; public string CarbonKey { get; set; } = ""; - public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); + public DBConfig Db { get; set; } = new DBConfig("sqlite", "Data Source=data/NadekoBot.db"); public int TotalShards { get; set; } = 1; public string PatreonAccessToken { get; set; } = ""; public string PatreonCampaignId { get; set; } = "334038"; diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs index d781054b..b8a977ec 100644 --- a/src/NadekoBot/Services/Impl/Localization.cs +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -5,6 +5,9 @@ using System.Linq; using Discord; using NLog; using NadekoBot.Services.Database.Models; +using NadekoBot.Common; +using Newtonsoft.Json; +using System.IO; namespace NadekoBot.Services.Impl { @@ -16,6 +19,14 @@ namespace NadekoBot.Services.Impl public ConcurrentDictionary GuildCultureInfos { get; } public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; + private static readonly Dictionary _commandData; + + static Localization() + { + _commandData = JsonConvert.DeserializeObject>( + File.ReadAllText("./data/command_strings.json")); + } + private Localization() { } public Localization(IBotConfigProvider bcp, IEnumerable gcs, DbService db) { @@ -117,10 +128,10 @@ namespace NadekoBot.Services.Impl return info ?? DefaultCultureInfo; } - public static string LoadCommandString(string key) + public static CommandData LoadCommand(string key) { - string toReturn = Resources.CommandStrings.ResourceManager.GetString(key); - return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn; + _commandData.TryGetValue(key, out var toReturn); + return toReturn; } } } diff --git a/src/NadekoBot/credentials_example.json b/src/NadekoBot/credentials_example.json index 5260d8c0..5074f8e0 100644 --- a/src/NadekoBot/credentials_example.json +++ b/src/NadekoBot/credentials_example.json @@ -13,7 +13,7 @@ "CarbonKey": "", "Db": { "Type": "sqlite", - "ConnectionString": "Filename=./data/NadekoBot.db" + "ConnectionString": "Data Source=data/NadekoBot.db" }, "TotalShards": 1, "PatreonAccessToken": "", diff --git a/src/NadekoBot/data/command_strings.json b/src/NadekoBot/data/command_strings.json new file mode 100644 index 00000000..d8f0ccd2 --- /dev/null +++ b/src/NadekoBot/data/command_strings.json @@ -0,0 +1,2057 @@ +{ + "h": { + "Cmd": "help h", + "Desc": "Either shows a help for a single command, or DMs you help link if no arguments are specified.", + "Usage": "`{0}h {0}cmds` or `{0}h`" + }, + "hgit": { + "Cmd": "hgit", + "Desc": "Generates the commandlist.md file.", + "Usage": "`{0}hgit`" + }, + "donate": { + "Cmd": "donate", + "Desc": "Instructions for helping the project financially.", + "Usage": "`{0}donate`" + }, + "modules": { + "Cmd": "modules mdls", + "Desc": "Lists all bot modules.", + "Usage": "`{0}modules`" + }, + "commands": { + "Cmd": "commands cmds", + "Desc": "List all of the bot's commands from a certain module. You can either specify the full name or only the first few letters of the module name.", + "Usage": "`{0}commands Administration` or `{0}cmds Admin`" + }, + "greetdel": { + "Cmd": "greetdel grdel", + "Desc": "Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set it to 0 to disable automatic deletion.", + "Usage": "`{0}greetdel 0` or `{0}greetdel 30`" + }, + "greet": { + "Cmd": "greet", + "Desc": "Toggles anouncements on the current channel when someone joins the server.", + "Usage": "`{0}greet`" + }, + "greetmsg": { + "Cmd": "greetmsg", + "Desc": "Sets a new join announcement message which will be shown in the server's channel. Type `%user%` if you want to mention the new member. Using it with no message will show the current greet message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": "`{0}greetmsg Welcome, %user%.`" + }, + "bye": { + "Cmd": "bye", + "Desc": "Toggles anouncements on the current channel when someone leaves the server.", + "Usage": "`{0}bye`" + }, + "byemsg": { + "Cmd": "byemsg", + "Desc": "Sets a new leave announcement message. Type `%user%` if you want to show the name the user who left. Type `%id%` to show id. Using this command with no message will show the current bye message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": "`{0}byemsg %user% has left.`" + }, + "byedel": { + "Cmd": "byedel", + "Desc": "Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set it to `0` to disable automatic deletion.", + "Usage": "`{0}byedel 0` or `{0}byedel 30`" + }, + "greetdm": { + "Cmd": "greetdm", + "Desc": "Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled).", + "Usage": "`{0}greetdm`" + }, + "logserver": { + "Cmd": "logserver", + "Desc": "Enables or Disables ALL log events. If enabled, all log events will log to this channel.", + "Usage": "`{0}logserver enable` or `{0}logserver disable`" + }, + "logignore": { + "Cmd": "logignore", + "Desc": "Toggles whether the `.logserver` command ignores this channel. Useful if you have hidden admin channel and public log channel.", + "Usage": "`{0}logignore`" + }, + "userpresence": { + "Cmd": "userpresence", + "Desc": "Starts logging to this channel when someone from the server goes online/offline/idle.", + "Usage": "`{0}userpresence`" + }, + "voicepresence": { + "Cmd": "voicepresence", + "Desc": "Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in.", + "Usage": "`{0}voicepresence`" + }, + "repeatinvoke": { + "Cmd": "repeatinvoke repinv", + "Desc": "Immediately shows the repeat message on a certain index and restarts its timer.", + "Usage": "`{0}repinv 1`" + }, + "repeat": { + "Cmd": "repeat", + "Desc": "Repeat a message every `X` minutes in the current channel. You can instead specify time of day for the message to be repeated at daily (make sure you've set your server's timezone). You can have up to 5 repeating messages on the server in total.", + "Usage": "`{0}repeat 5 Hello there` or `{0}repeat 17:30 tea time`" + }, + "rotateplaying": { + "Cmd": "rotateplaying ropl", + "Desc": "Toggles rotation of playing status of the dynamic strings you previously specified.", + "Usage": "`{0}ropl`" + }, + "addplaying": { + "Cmd": "addplaying adpl", + "Desc": "Adds a specified string to the list of playing strings to rotate. Supported placeholders: `%servers%`, `%users%`, `%playing%`, `%queued%`, `%time%`, `%shardid%`, `%shardcount%`, `%shardguilds%`.", + "Usage": "`{0}adpl`" + }, + "listplaying": { + "Cmd": "listplaying lipl", + "Desc": "Lists all playing statuses with their corresponding number.", + "Usage": "`{0}lipl`" + }, + "removeplaying": { + "Cmd": "removeplaying rmpl repl", + "Desc": "Removes a playing string on a given number.", + "Usage": "`{0}rmpl`" + }, + "slowmode": { + "Cmd": "slowmode", + "Desc": "Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds.", + "Usage": "`{0}slowmode 1 5` or `{0}slowmode`" + }, + "cleanvplust": { + "Cmd": "cleanvplust cv+t", + "Desc": "Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk.", + "Usage": "`{0}cleanv+t`" + }, + "voiceplustext": { + "Cmd": "voice+text v+t", + "Desc": "Creates a text channel for each voice channel only users in that voice channel can see. If you are server owner, keep in mind you will see them all the time regardless.", + "Usage": "`{0}v+t`" + }, + "scsc": { + "Cmd": "scsc", + "Desc": "Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance.", + "Usage": "`{0}scsc`" + }, + "jcsc": { + "Cmd": "jcsc", + "Desc": "Joins current channel to an instance of cross server channel using the token.", + "Usage": "`{0}jcsc TokenHere`" + }, + "lcsc": { + "Cmd": "lcsc", + "Desc": "Leaves a cross server channel instance from this channel.", + "Usage": "`{0}lcsc`" + }, + "asar": { + "Cmd": "asar", + "Desc": "Adds a role to the list of self-assignable roles.", + "Usage": "`{0}asar Gamer`" + }, + "rsar": { + "Cmd": "rsar", + "Desc": "Removes a specified role from the list of self-assignable roles.", + "Usage": "`{0}rsar`" + }, + "lsar": { + "Cmd": "lsar", + "Desc": "Lists all self-assignable roles.", + "Usage": "`{0}lsar`" + }, + "tesar": { + "Cmd": "togglexclsar tesar", + "Desc": "Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles)", + "Usage": "`{0}tesar`" + }, + "iam": { + "Cmd": "iam", + "Desc": "Adds a role to you that you choose. Role must be on a list of self-assignable roles.", + "Usage": "`{0}iam Gamer`" + }, + "iamnot": { + "Cmd": "iamnot iamn", + "Desc": "Removes a specified role from you. Role must be on a list of self-assignable roles.", + "Usage": "`{0}iamn Gamer`" + }, + "addcustreact": { + "Cmd": "addcustreact acr", + "Desc": "Add a custom reaction with a trigger and a response. Running this command in server requires the Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: ", + "Usage": "`{0}acr \"hello\" Hi there %user%`" + }, + "listcustreact": { + "Cmd": "listcustreact lcr", + "Desc": "Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions.", + "Usage": "`{0}lcr 1` or `{0}lcr all`" + }, + "listcustreactg": { + "Cmd": "listcustreactg lcrg", + "Desc": "Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions.", + "Usage": "`{0}lcrg 1`" + }, + "showcustreact": { + "Cmd": "showcustreact scr", + "Desc": "Shows a custom reaction's response on a given ID.", + "Usage": "`{0}scr 1`" + }, + "delcustreact": { + "Cmd": "delcustreact dcr", + "Desc": "Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration privileges and removes server custom reaction.", + "Usage": "`{0}dcr 5`" + }, + "autoassignrole": { + "Cmd": "autoassignrole aar", + "Desc": "Automaticaly assigns a specified role to every user who joins the server.", + "Usage": "`{0}aar` to disable, `{0}aar Role Name` to enable" + }, + "leave": { + "Cmd": "leave", + "Desc": "Makes Nadeko leave the server. Either server name or server ID is required.", + "Usage": "`{0}leave 123123123331`" + }, + "delmsgoncmd": { + "Cmd": "delmsgoncmd", + "Desc": "Toggles the automatic deletion of the user's successful command message to prevent chat flood.", + "Usage": "`{0}delmsgoncmd`" + }, + "restart": { + "Cmd": "restart", + "Desc": "Restarts the bot. Might not work.", + "Usage": "`{0}restart`" + }, + "setrole": { + "Cmd": "setrole sr", + "Desc": "Sets a role for a given user.", + "Usage": "`{0}sr @User Guest`" + }, + "removerole": { + "Cmd": "removerole rr", + "Desc": "Removes a role from a given user.", + "Usage": "`{0}rr @User Admin`" + }, + "renamerole": { + "Cmd": "renamerole renr", + "Desc": "Renames a role. The role you are renaming must be lower than bot's highest role.", + "Usage": "`{0}renr \"First role\" SecondRole`" + }, + "removeallroles": { + "Cmd": "removeallroles rar", + "Desc": "Removes all roles from a mentioned user.", + "Usage": "`{0}rar @User`" + }, + "createrole": { + "Cmd": "createrole cr", + "Desc": "Creates a role with a given name.", + "Usage": "`{0}cr Awesome Role`" + }, + "rolecolor": { + "Cmd": "rolecolor roleclr", + "Desc": "Set a role's color to the hex or 0-255 rgb color value provided.", + "Usage": "`{0}roleclr Admin 255 200 100` or `{0}roleclr Admin ffba55`" + }, + "ban": { + "Cmd": "ban b", + "Desc": "Bans a user by ID or name with an optional message.", + "Usage": "`{0}b \"@some Guy\" Your behaviour is toxic.`" + }, + "softban": { + "Cmd": "softban sb", + "Desc": "Bans and then unbans a user by ID or name with an optional message.", + "Usage": "`{0}sb \"@some Guy\" Your behaviour is toxic.`" + }, + "kick": { + "Cmd": "kick k", + "Desc": "Kicks a mentioned user.", + "Usage": "`{0}k \"@some Guy\" Your behaviour is toxic.`" + }, + "mute": { + "Cmd": "mute", + "Desc": "Mutes a mentioned user both from speaking and chatting. You can also specify time in minutes (up to 1440) for how long the user should be muted.", + "Usage": "`{0}mute @Someone` or `{0}mute 30 @Someone`" + }, + "voiceunmute": { + "Cmd": "voiceunmute", + "Desc": "Gives a previously voice-muted user a permission to speak.", + "Usage": "`{0}voiceunmute @Someguy`" + }, + "deafen": { + "Cmd": "deafen deaf", + "Desc": "Deafens mentioned user or users.", + "Usage": "`{0}deaf \"@Someguy\"` or `{0}deaf \"@Someguy\" \"@Someguy\"`" + }, + "undeafen": { + "Cmd": "undeafen undef", + "Desc": "Undeafens mentioned user or users.", + "Usage": "`{0}undef \"@Someguy\"` or `{0}undef \"@Someguy\" \"@Someguy\"`" + }, + "delvoichanl": { + "Cmd": "delvoichanl dvch", + "Desc": "Deletes a voice channel with a given name.", + "Usage": "`{0}dvch VoiceChannelName`" + }, + "creatvoichanl": { + "Cmd": "creatvoichanl cvch", + "Desc": "Creates a new voice channel with a given name.", + "Usage": "`{0}cvch VoiceChannelName`" + }, + "deltxtchanl": { + "Cmd": "deltxtchanl dtch", + "Desc": "Deletes a text channel with a given name.", + "Usage": "`{0}dtch TextChannelName`" + }, + "creatxtchanl": { + "Cmd": "creatxtchanl ctch", + "Desc": "Creates a new text channel with a given name.", + "Usage": "`{0}ctch TextChannelName`" + }, + "settopic": { + "Cmd": "settopic st", + "Desc": "Sets a topic on the current channel.", + "Usage": "`{0}st My new topic`" + }, + "setchanlname": { + "Cmd": "setchanlname schn", + "Desc": "Changes the name of the current channel.", + "Usage": "`{0}schn NewName`" + }, + "prune": { + "Cmd": "prune clear", + "Desc": "`{0}prune` removes all Nadeko's messages in the last 100 messages. `{0}prune X` removes last `X` number of messages from the channel (up to 100). `{0}prune @Someone` removes all Someone's messages in the last 100 messages. `{0}prune @Someone X` removes last `X` number of 'Someone's' messages in the channel.", + "Usage": "`{0}prune` or `{0}prune 5` or `{0}prune @Someone` or `{0}prune @Someone X`" + }, + "die": { + "Cmd": "die", + "Desc": "Shuts the bot down.", + "Usage": "`{0}die`" + }, + "setname": { + "Cmd": "setname newnm", + "Desc": "Gives the bot a new name.", + "Usage": "`{0}newnm BotName`" + }, + "setnick": { + "Cmd": "setnick", + "Desc": "Changes the nickname of the bot on this server. You can also target other users to change their nickname.", + "Usage": "`{0}setnick BotNickname` or `{0}setnick @SomeUser New Nickname`" + }, + "setavatar": { + "Cmd": "setavatar setav", + "Desc": "Sets a new avatar image for the NadekoBot. Argument is a direct link to an image.", + "Usage": "`{0}setav http://i.imgur.com/xTG3a1I.jpg`" + }, + "setgame": { + "Cmd": "setgame", + "Desc": "Sets the bots game.", + "Usage": "`{0}setgame with snakes`" + }, + "send": { + "Cmd": "send", + "Desc": "Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prefix the channel id with `c:` and the user id with `u:`.", + "Usage": "`{0}send serverid|c:channelid message` or `{0}send serverid|u:userid message`" + }, + "mentionrole": { + "Cmd": "mentionrole menro", + "Desc": "Mentions every person from the provided role or roles (separated by a ',') on this server.", + "Usage": "`{0}menro RoleName`" + }, + "unstuck": { + "Cmd": "unstuck", + "Desc": "Clears the message queue.", + "Usage": "`{0}unstuck`" + }, + "donators": { + "Cmd": "donators", + "Desc": "List of the lovely people who donated to keep this project alive.", + "Usage": "`{0}donators`" + }, + "donadd": { + "Cmd": "donadd", + "Desc": "Add a donator to the database.", + "Usage": "`{0}donadd Donate Amount`" + }, + "savechat": { + "Cmd": "savechat", + "Desc": "Saves a number of messages to a text file and sends it to you.", + "Usage": "`{0}savechat 150`" + }, + "remind": { + "Cmd": "remind", + "Desc": "Sends a message to you or a channel after certain amount of time. First argument is `me`/`here`/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword) message.", + "Usage": "`{0}remind me 1d5h Do something` or `{0}remind #general 1m Start now!`" + }, + "remindtemplate": { + "Cmd": "remindtemplate", + "Desc": "Sets message for when the remind is triggered. Available placeholders are `%user%` - user who ran the command, `%message%` - Message specified in the remind, `%target%` - target channel of the remind.", + "Usage": "`{0}remindtemplate %user%, do %message%!`" + }, + "serverinfo": { + "Cmd": "serverinfo sinfo", + "Desc": "Shows info about the server the bot is on. If no server is supplied, it defaults to current one.", + "Usage": "`{0}sinfo Some Server`" + }, + "channelinfo": { + "Cmd": "channelinfo cinfo", + "Desc": "Shows info about the channel. If no channel is supplied, it defaults to current one.", + "Usage": "`{0}cinfo #some-channel`" + }, + "userinfo": { + "Cmd": "userinfo uinfo", + "Desc": "Shows info about the user. If no user is supplied, it defaults a user running the command.", + "Usage": "`{0}uinfo @SomeUser`" + }, + "whosplaying": { + "Cmd": "whosplaying whpl", + "Desc": "Shows a list of users who are playing the specified game.", + "Usage": "`{0}whpl Overwatch`" + }, + "inrole": { + "Cmd": "inrole", + "Desc": "Lists every person from the specified role on this server. You can use role ID, role name.", + "Usage": "`{0}inrole Some Role`" + }, + "checkmyperms": { + "Cmd": "checkmyperms", + "Desc": "Checks your user-specific permissions on this channel.", + "Usage": "`{0}checkmyperms`" + }, + "stats": { + "Cmd": "stats", + "Desc": "Shows some basic stats for Nadeko.", + "Usage": "`{0}stats`" + }, + "userid": { + "Cmd": "userid uid", + "Desc": "Shows user ID.", + "Usage": "`{0}uid` or `{0}uid @SomeGuy`" + }, + "channelid": { + "Cmd": "channelid cid", + "Desc": "Shows current channel ID.", + "Usage": "`{0}cid`" + }, + "serverid": { + "Cmd": "serverid sid", + "Desc": "Shows current server ID.", + "Usage": "`{0}sid`" + }, + "roles": { + "Cmd": "roles", + "Desc": "List roles on this server or a roles of a specific user if specified. Paginated, 20 roles per page.", + "Usage": "`{0}roles 2` or `{0}roles @Someone`" + }, + "channeltopic": { + "Cmd": "channeltopic ct", + "Desc": "Sends current channel's topic as a message.", + "Usage": "`{0}ct`" + }, + "chnlfilterinv": { + "Cmd": "chnlfilterinv cfi", + "Desc": "Toggles automatic deletion of invites posted in the channel. Does not negate the `{0}srvrfilterinv` enabled setting. Does not affect the Bot Owner.", + "Usage": "`{0}cfi`" + }, + "srvrfilterinv": { + "Cmd": "srvrfilterinv sfi", + "Desc": "Toggles automatic deletion of invites posted in the server. Does not affect the Bot Owner.", + "Usage": "`{0}sfi`" + }, + "chnlfilterwords": { + "Cmd": "chnlfilterwords cfw", + "Desc": "Toggles automatic deletion of messages containing filtered words on the channel. Does not negate the `{0}srvrfilterwords` enabled setting. Does not affect the Bot Owner.", + "Usage": "`{0}cfw`" + }, + "filterword": { + "Cmd": "fw", + "Desc": "Adds or removes (if it exists) a word from the list of filtered words. Use`{0}sfw` or `{0}cfw` to toggle filtering.", + "Usage": "`{0}fw poop`" + }, + "lstfilterwords": { + "Cmd": "lstfilterwords lfw", + "Desc": "Shows a list of filtered words.", + "Usage": "`{0}lfw`" + }, + "srvrfilterwords": { + "Cmd": "srvrfilterwords sfw", + "Desc": "Toggles automatic deletion of messages containing filtered words on the server. Does not affect the Bot Owner.", + "Usage": "`{0}sfw`" + }, + "permrole": { + "Cmd": "permrole pr", + "Desc": "Sets a role which can change permissions. Supply no parameters to see the current one. Default is 'Nadeko'.", + "Usage": "`{0}pr role`" + }, + "verbose": { + "Cmd": "verbose v", + "Desc": "Sets whether to show when a command/module is blocked.", + "Usage": "`{0}verbose true`" + }, + "srvrmdl": { + "Cmd": "srvrmdl sm", + "Desc": "Sets a module's permission at the server level.", + "Usage": "`{0}sm ModuleName enable`" + }, + "srvrcmd": { + "Cmd": "srvrcmd sc", + "Desc": "Sets a command's permission at the server level.", + "Usage": "`{0}sc \"command name\" disable`" + }, + "rolemdl": { + "Cmd": "rolemdl rm", + "Desc": "Sets a module's permission at the role level.", + "Usage": "`{0}rm ModuleName enable MyRole`" + }, + "rolecmd": { + "Cmd": "rolecmd rc", + "Desc": "Sets a command's permission at the role level.", + "Usage": "`{0}rc \"command name\" disable MyRole`" + }, + "chnlmdl": { + "Cmd": "chnlmdl cm", + "Desc": "Sets a module's permission at the channel level.", + "Usage": "`{0}cm ModuleName enable SomeChannel`" + }, + "chnlcmd": { + "Cmd": "chnlcmd cc", + "Desc": "Sets a command's permission at the channel level.", + "Usage": "`{0}cc \"command name\" enable SomeChannel`" + }, + "usrmdl": { + "Cmd": "usrmdl um", + "Desc": "Sets a module's permission at the user level.", + "Usage": "`{0}um ModuleName enable SomeUsername`" + }, + "usrcmd": { + "Cmd": "usrcmd uc", + "Desc": "Sets a command's permission at the user level.", + "Usage": "`{0}uc \"command name\" enable SomeUsername`" + }, + "allsrvrmdls": { + "Cmd": "allsrvrmdls asm", + "Desc": "Enable or disable all modules for your server.", + "Usage": "`{0}asm [enable/disable]`" + }, + "allchnlmdls": { + "Cmd": "allchnlmdls acm", + "Desc": "Enable or disable all modules in a specified channel.", + "Usage": "`{0}acm enable #SomeChannel`" + }, + "allrolemdls": { + "Cmd": "allrolemdls arm", + "Desc": "Enable or disable all modules for a specific role.", + "Usage": "`{0}arm [enable/disable] MyRole`" + }, + "userblacklist": { + "Cmd": "ubl", + "Desc": "Either [add]s or [rem]oves a user specified by a Mention or an ID from a blacklist.", + "Usage": "`{0}ubl add @SomeUser` or `{0}ubl rem 12312312313`" + }, + "channelblacklist": { + "Cmd": "cbl", + "Desc": "Either [add]s or [rem]oves a channel specified by an ID from a blacklist.", + "Usage": "`{0}cbl rem 12312312312`" + }, + "serverblacklist": { + "Cmd": "sbl", + "Desc": "Either [add]s or [rem]oves a server specified by a Name or an ID from a blacklist.", + "Usage": "`{0}sbl add 12312321312` or `{0}sbl rem SomeTrashServer`" + }, + "cmdcooldown": { + "Cmd": "cmdcooldown cmdcd", + "Desc": "Sets a cooldown per user for a command. Set it to 0 to remove the cooldown.", + "Usage": "`{0}cmdcd \"some cmd\" 5`" + }, + "allcmdcooldowns": { + "Cmd": "allcmdcooldowns acmdcds", + "Desc": "Shows a list of all commands and their respective cooldowns.", + "Usage": "`{0}acmdcds`" + }, + "addquote": { + "Cmd": ".", + "Desc": "Adds a new quote with the specified name and message.", + "Usage": "`{0}. sayhi Hi`" + }, + "showquote": { + "Cmd": "..", + "Desc": "Shows a random quote with a specified name.", + "Usage": "`{0}.. abc`" + }, + "quotesearch": { + "Cmd": "qsearch", + "Desc": "Shows a random quote for a keyword that contains any text specified in the search.", + "Usage": "`{0}qsearch keyword text`" + }, + "quoteid": { + "Cmd": "quoteid qid", + "Desc": "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `.liqu [num]` where `[num]` is a number of a page which contains 15 quotes.", + "Usage": "`{0}qid 123456`" + }, + "quotedelete": { + "Cmd": "quotedel qdel", + "Desc": "Deletes a quote with the specified ID. You have to be either server Administrator or the creator of the quote to delete it.", + "Usage": "`{0}qdel 123456`" + }, + "draw": { + "Cmd": "draw", + "Desc": "Draws a card from this server's deck. You can draw up to 10 cards by supplying a number of cards to draw.", + "Usage": "`{0}draw` or `{0}draw 5`" + }, + "drawnew": { + "Cmd": "drawnew", + "Desc": "Draws a card from the NEW deck of cards. You can draw up to 10 cards by supplying a number of cards to draw.", + "Usage": "`{0}drawnew` or `{0}drawnew 5`" + }, + "shuffleplaylist": { + "Cmd": "shuffle sh plsh", + "Desc": "Shuffles the current playlist.", + "Usage": "`{0}plsh`" + }, + "flip": { + "Cmd": "flip", + "Desc": "Flips coin(s) - heads or tails, and shows an image.", + "Usage": "`{0}flip` or `{0}flip 3`" + }, + "betflip": { + "Cmd": "betflip bf", + "Desc": "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.", + "Usage": "`{0}bf 5 heads` or `{0}bf 3 t`" + }, + "roll": { + "Cmd": "roll", + "Desc": "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.", + "Usage": "`{0}roll` or `{0}roll 7` or `{0}roll 3d5` or `{0}roll 5dF`" + }, + "rolluo": { + "Cmd": "rolluo", + "Desc": "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`.", + "Usage": "`{0}rolluo` or `{0}rolluo 7` or `{0}rolluo 3d5`" + }, + "nroll": { + "Cmd": "nroll", + "Desc": "Rolls in a given range.", + "Usage": "`{0}nroll 5` (rolls 0-5) or `{0}nroll 5-15`" + }, + "race": { + "Cmd": "race", + "Desc": "Starts a new animal race.", + "Usage": "`{0}race`" + }, + "joinrace": { + "Cmd": "joinrace jr", + "Desc": "Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win.", + "Usage": "`{0}jr` or `{0}jr 5`" + }, + "nunchi": { + "Cmd": "nunchi", + "Desc": "Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrent number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required.", + "Usage": "`{0}nunchi`" + }, + "connect4": { + "Cmd": "connect4 con4", + "Desc": "Creates or joins an existing connect4 game. 2 players are required for the game. Objective of the game is to get 4 of your pieces next to each other in a vertical, horizontal or diagonal line.", + "Usage": "`{0}connect4`" + }, + "raffle": { + "Cmd": "raffle", + "Desc": "Prints a name and ID of a random user from the online list from the (optional) role.", + "Usage": "`{0}raffle` or `{0}raffle RoleName`" + }, + "give": { + "Cmd": "give", + "Desc": "Give someone a certain amount of currency.", + "Usage": "`{0}give 1 @SomeGuy`" + }, + "award": { + "Cmd": "award", + "Desc": "Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role.", + "Usage": "`{0}award 100 @person` or `{0}award 5 Role Of Gamblers`" + }, + "take": { + "Cmd": "take", + "Desc": "Takes a certain amount of currency from someone.", + "Usage": "`{0}take 1 @SomeGuy`" + }, + "betroll": { + "Cmd": "betroll br", + "Desc": "Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10.", + "Usage": "`{0}br 5`" + }, + "wheeloffortune": { + "Cmd": "wheeloffortune wheel", + "Desc": "Bets a certain amount of currency on the wheel of fortune. Wheel can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number.", + "Usage": "`{0}wheel 10`" + }, + "leaderboard": { + "Cmd": "leaderboard lb", + "Desc": "Displays the bot's currency leaderboard.", + "Usage": "`{0}lb`" + }, + "trivia": { + "Cmd": "trivia t", + "Desc": "Starts a game of trivia. You can add `nohint` to prevent hints. First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question.", + "Usage": "`{0}t` or `{0}t 5 nohint`" + }, + "tl": { + "Cmd": "tl", + "Desc": "Shows a current trivia leaderboard.", + "Usage": "`{0}tl`" + }, + "tq": { + "Cmd": "tq", + "Desc": "Quits current trivia after current question.", + "Usage": "`{0}tq`" + }, + "typestart": { + "Cmd": "typestart", + "Desc": "Starts a typing contest.", + "Usage": "`{0}typestart`" + }, + "typestop": { + "Cmd": "typestop", + "Desc": "Stops a typing contest on the current channel.", + "Usage": "`{0}typestop`" + }, + "typeadd": { + "Cmd": "typeadd", + "Desc": "Adds a new article to the typing contest.", + "Usage": "`{0}typeadd wordswords`" + }, + "pollend": { + "Cmd": "pollend", + "Desc": "Stops active poll on this server and prints the results in this channel.", + "Usage": "`{0}pollend`" + }, + "pick": { + "Cmd": "pick", + "Desc": "Picks the currency planted in this channel. 60 seconds cooldown.", + "Usage": "`{0}pick`" + }, + "plant": { + "Cmd": "plant", + "Desc": "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)", + "Usage": "`{0}plant` or `{0}plant 5`" + }, + "gencurrency": { + "Cmd": "gencurrency gc", + "Desc": "Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%)", + "Usage": "`{0}gc`" + }, + "leet": { + "Cmd": "leet", + "Desc": "Converts a text to leetspeak with 6 (1-6) severity levels", + "Usage": "`{0}leet 3 Hello`" + }, + "choose": { + "Cmd": "choose", + "Desc": "Chooses a thing from a list of things", + "Usage": "`{0}choose Get up;Sleep;Sleep more`" + }, + "": { + "Cmd": null, + "Desc": null, + "Usage": null + }, + "rps": { + "Cmd": "rps", + "Desc": "Play a game of Rocket-Paperclip-Scissors with Nadeko.", + "Usage": "`{0}rps scissors`" + }, + "linux": { + "Cmd": "linux", + "Desc": "Prints a customizable Linux interjection", + "Usage": "`{0}linux Spyware Windows`" + }, + "next": { + "Cmd": "next n", + "Desc": "Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if {0}rcs or {0}rpl is enabled.", + "Usage": "`{0}n` or `{0}n 5`" + }, + "play": { + "Cmd": "play start", + "Desc": "If no arguments are specified, acts as `{0}next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `{0}q` command", + "Usage": "`{0}play` or `{0}play 5` or `{0}play Dream Of Venice`" + }, + "stop": { + "Cmd": "stop s", + "Desc": "Stops the music and preserves the current song index. Stays in the channel.", + "Usage": "`{0}s`" + }, + "destroy": { + "Cmd": "destroy d", + "Desc": "Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour)", + "Usage": "`{0}d`" + }, + "pause": { + "Cmd": "pause p", + "Desc": "Pauses or Unpauses the song.", + "Usage": "`{0}p`" + }, + "queue": { + "Cmd": "queue q yq", + "Desc": "Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": "`{0}q Dream Of Venice`" + }, + "queuenext": { + "Cmd": "queuenext qn", + "Desc": "Works the same as `{0}queue` command, except it enqueues the new song after the current one. **You must be in a voice channel**.", + "Usage": "`{0}qn Dream Of Venice`" + }, + "queuesearch": { + "Cmd": "queuesearch qs yqs", + "Desc": "Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": "`{0}qs Dream Of Venice`" + }, + "soundcloudqueue": { + "Cmd": "soundcloudqueue sq", + "Desc": "Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**.", + "Usage": "`{0}sq Dream Of Venice`" + }, + "listqueue": { + "Cmd": "listqueue lq", + "Desc": "Lists 10 currently queued songs per page. Default page is 1.", + "Usage": "`{0}lq` or `{0}lq 2`" + }, + "nowplaying": { + "Cmd": "nowplaying np", + "Desc": "Shows the song that the bot is currently playing.", + "Usage": "`{0}np`" + }, + "volume": { + "Cmd": "volume vol", + "Desc": "Sets the music playback volume (0-100%)", + "Usage": "`{0}vol 50`" + }, + "defvol": { + "Cmd": "defvol dv", + "Desc": "Sets the default music volume when music playback is started (0-100). Persists through restarts.", + "Usage": "`{0}dv 80`" + }, + "max": { + "Cmd": "max", + "Desc": "Sets the music playback volume to 100%.", + "Usage": "`{0}max`" + }, + "half": { + "Cmd": "half", + "Desc": "Sets the music playback volume to 50%.", + "Usage": "`{0}half`" + }, + "playlist": { + "Cmd": "playlist pl", + "Desc": "Queues up to 500 songs from a youtube playlist specified by a link, or keywords.", + "Usage": "`{0}pl playlist link or name`" + }, + "soundcloudpl": { + "Cmd": "soundcloudpl scpl", + "Desc": "Queue a Soundcloud playlist using a link.", + "Usage": "`{0}scpl soundcloudseturl`" + }, + "localpl": { + "Cmd": "localplaylst lopl", + "Desc": "Queues all songs from a directory.", + "Usage": "`{0}lopl C:/music/classical`" + }, + "radio": { + "Cmd": "radio ra", + "Desc": "Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: )", + "Usage": "`{0}ra radio link here`" + }, + "local": { + "Cmd": "local lo", + "Desc": "Queues a local file by specifying a full path.", + "Usage": "`{0}lo C:/music/mysong.mp3`" + }, + "move": { + "Cmd": "move mv", + "Desc": "Moves the bot to your voice channel. (works only if music is already playing)", + "Usage": "`{0}mv`" + }, + "songremove": { + "Cmd": "songremove srm", + "Desc": "Remove a song by its # in the queue, or 'all' to remove all songs from the queue and reset the song index.", + "Usage": "`{0}srm 5`" + }, + "movesong": { + "Cmd": "movesong ms", + "Desc": "Moves a song from one position to another.", + "Usage": "`{0}ms 5>3`" + }, + "setmaxqueue": { + "Cmd": "setmaxqueue smq", + "Desc": "Sets a maximum queue size. Supply 0 or no argument to have no limit.", + "Usage": "`{0}smq 50` or `{0}smq`" + }, + "cleanup": { + "Cmd": "cleanup", + "Desc": "Cleans up hanging voice connections.", + "Usage": "`{0}cleanup`" + }, + "reptcursong": { + "Cmd": "reptcursong rcs", + "Desc": "Toggles repeat of current song.", + "Usage": "`{0}rcs`" + }, + "repeatpl": { + "Cmd": "rpeatplaylst rpl", + "Desc": "Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).", + "Usage": "`{0}rpl`" + }, + "save": { + "Cmd": "save", + "Desc": "Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes.", + "Usage": "`{0}save classical1`" + }, + "streamrole": { + "Cmd": "streamrole", + "Desc": "Sets a role which is monitored for streamers (FromRole), and a role to add if a user from 'FromRole' is streaming (AddRole). When a user from 'FromRole' starts streaming, they will receive an 'AddRole'. Provide no arguments to disable", + "Usage": "`{0}streamrole \"Eligible Streamers\" \"Featured Streams\"`" + }, + "load": { + "Cmd": "load", + "Desc": "Loads a saved playlist using its ID. Use `{0}pls` to list all saved playlists and `{0}save` to save new ones.", + "Usage": "`{0}load 5`" + }, + "playlists": { + "Cmd": "playlists pls", + "Desc": "Lists all playlists. Paginated, 20 per page. Default page is 0.", + "Usage": "`{0}pls 1`" + }, + "deleteplaylist": { + "Cmd": "deleteplaylist delpls", + "Desc": "Deletes a saved playlist. Works only if you made it or if you are the bot owner.", + "Usage": "`{0}delpls animu-5`" + }, + "goto": { + "Cmd": "goto", + "Desc": "Goes to a specific time in seconds in a song.", + "Usage": "`{0}goto 30`" + }, + "autoplay": { + "Cmd": "autoplay ap", + "Desc": "Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs and when queue is empty)", + "Usage": "`{0}ap`" + }, + "lolchamp": { + "Cmd": "lolchamp", + "Desc": "Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role.", + "Usage": "`{0}lolchamp Riven` or `{0}lolchamp Annie sup`" + }, + "lolban": { + "Cmd": "lolban", + "Desc": "Shows top banned champions ordered by ban rate.", + "Usage": "`{0}lolban`" + }, + "smashcast": { + "Cmd": "smashcast hb", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": "`{0}smashcast SomeStreamer`" + }, + "twitch": { + "Cmd": "twitch tw", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": "`{0}twitch SomeStreamer`" + }, + "mixer": { + "Cmd": "mixer bm", + "Desc": "Notifies this channel when a certain user starts streaming.", + "Usage": "`{0}mixer SomeStreamer`" + }, + "removestream": { + "Cmd": "removestream rms", + "Desc": "Removes notifications of a certain streamer from a certain platform on this channel.", + "Usage": "`{0}rms Twitch SomeGuy` or `{0}rms mixer SomeOtherGuy`" + }, + "liststreams": { + "Cmd": "liststreams ls", + "Desc": "Lists all streams you are following on this server.", + "Usage": "`{0}ls`" + }, + "convert": { + "Cmd": "convert", + "Desc": "Convert quantities. Use `{0}convertlist` to see supported dimensions and currencies.", + "Usage": "`{0}convert m km 1000`" + }, + "convertlist": { + "Cmd": "convertlist", + "Desc": "List of the convertible dimensions and currencies.", + "Usage": "`{0}convertlist`" + }, + "wowjoke": { + "Cmd": "wowjoke", + "Desc": "Get one of Kwoth's penultimate WoW jokes.", + "Usage": "`{0}wowjoke`" + }, + "calculate": { + "Cmd": "calculate calc", + "Desc": "Evaluate a mathematical expression.", + "Usage": "`{0}calc 1+1`" + }, + "osu": { + "Cmd": "osu", + "Desc": "Shows osu stats for a player.", + "Usage": "`{0}osu Name` or `{0}osu Name taiko`" + }, + "osub": { + "Cmd": "osub", + "Desc": "Shows information about an osu beatmap.", + "Usage": "`{0}osub https://osu.ppy.sh/s/127712`" + }, + "osu5": { + "Cmd": "osu5", + "Desc": "Displays a user's top 5 plays.", + "Usage": "`{0}osu5 Name`" + }, + "pokemon": { + "Cmd": "pokemon poke", + "Desc": "Searches for a pokemon.", + "Usage": "`{0}poke Sylveon`" + }, + "pokemonability": { + "Cmd": "pokemonability pokeab", + "Desc": "Searches for a pokemon ability.", + "Usage": "`{0}pokeab overgrow`" + }, + "memelist": { + "Cmd": "memelist", + "Desc": "Pulls a list of memes you can use with `{0}memegen` from http://memegen.link/templates/", + "Usage": "`{0}memelist`" + }, + "memegen": { + "Cmd": "memegen", + "Desc": "Generates a meme from memelist with top and bottom text.", + "Usage": "`{0}memegen biw \"gets iced coffee\" \"in the winter\"`" + }, + "weather": { + "Cmd": "weather we", + "Desc": "Shows weather data for a specified city. You can also specify a country after a comma.", + "Usage": "`{0}we Moscow, RU`" + }, + "youtube": { + "Cmd": "youtube yt", + "Desc": "Searches youtubes and shows the first result", + "Usage": "`{0}yt query`" + }, + "anime": { + "Cmd": "anime ani aq", + "Desc": "Queries anilist for an anime and shows the first result.", + "Usage": "`{0}ani aquarion evol`" + }, + "imdb": { + "Cmd": "imdb omdb", + "Desc": "Queries omdb for movies or series, show first result.", + "Usage": "`{0}imdb Batman vs Superman`" + }, + "manga": { + "Cmd": "manga mang mq", + "Desc": "Queries anilist for a manga and shows the first result.", + "Usage": "`{0}mq Shingeki no kyojin`" + }, + "randomcat": { + "Cmd": "randomcat meow", + "Desc": "Shows a random cat image.", + "Usage": "`{0}meow`" + }, + "randomdog": { + "Cmd": "randomdog woof", + "Desc": "Shows a random dog image.", + "Usage": "`{0}woof`" + }, + "image": { + "Cmd": "image img", + "Desc": "Pulls the first image found using a search parameter. Use `{0}rimg` for different results.", + "Usage": "`{0}img cute kitten`" + }, + "randomimage": { + "Cmd": "randomimage rimg", + "Desc": "Pulls a random image using a search parameter.", + "Usage": "`{0}rimg cute kitten`" + }, + "lmgtfy": { + "Cmd": "lmgtfy", + "Desc": "Google something for an idiot.", + "Usage": "`{0}lmgtfy query`" + }, + "google": { + "Cmd": "google g", + "Desc": "Get a Google search link for some terms.", + "Usage": "`{0}google query`" + }, + "hearthstone": { + "Cmd": "hearthstone hs", + "Desc": "Searches for a Hearthstone card and shows its image. Takes a while to complete.", + "Usage": "`{0}hs Ysera`" + }, + "urbandict": { + "Cmd": "urbandict ud", + "Desc": "Searches Urban Dictionary for a word.", + "Usage": "`{0}ud Pineapple`" + }, + "hashtag": { + "Cmd": "#", + "Desc": "Searches Tagdef.com for a hashtag.", + "Usage": "`{0}# ff`" + }, + "catfact": { + "Cmd": "catfact", + "Desc": "Shows a random catfact from ", + "Usage": "`{0}catfact`" + }, + "yomama": { + "Cmd": "yomama ym", + "Desc": "Shows a random joke from ", + "Usage": "`{0}ym`" + }, + "randjoke": { + "Cmd": "randjoke rj", + "Desc": "Shows a random joke from ", + "Usage": "`{0}rj`" + }, + "chucknorris": { + "Cmd": "chucknorris cn", + "Desc": "Shows a random Chuck Norris joke from ", + "Usage": "`{0}cn`" + }, + "magicitem": { + "Cmd": "magicitem mi", + "Desc": "Shows a random magic item from ", + "Usage": "`{0}mi`" + }, + "revav": { + "Cmd": "revav", + "Desc": "Returns a Google reverse image search for someone's avatar.", + "Usage": "`{0}revav @SomeGuy`" + }, + "revimg": { + "Cmd": "revimg", + "Desc": "Returns a Google reverse image search for an image from a link.", + "Usage": "`{0}revimg Image link`" + }, + "safebooru": { + "Cmd": "safebooru", + "Desc": "Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": "`{0}safebooru yuri+kissing`" + }, + "wiki": { + "Cmd": "wikipedia wiki", + "Desc": "Gives you back a wikipedia link", + "Usage": "`{0}wiki query`" + }, + "color": { + "Cmd": "color", + "Desc": "Shows you what color corresponds to that hex.", + "Usage": "`{0}color 00ff00`" + }, + "videocall": { + "Cmd": "videocall", + "Desc": "Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message.", + "Usage": "`{0}videocall \"@the First\" \"@Xyz\"`" + }, + "avatar": { + "Cmd": "avatar av", + "Desc": "Shows a mentioned person's avatar.", + "Usage": "`{0}av @SomeGuy`" + }, + "hentai": { + "Cmd": "hentai", + "Desc": "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.", + "Usage": "`{0}hentai yuri`" + }, + "danbooru": { + "Cmd": "danbooru", + "Desc": "Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": "`{0}danbooru yuri+kissing`" + }, + "atfbooru": { + "Cmd": "atfbooru atf", + "Desc": "Shows a random hentai image from atfbooru with a given tag. Tag is optional but preferred.", + "Usage": "`{0}atfbooru yuri+kissing`" + }, + "gelbooru": { + "Cmd": "gelbooru", + "Desc": "Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": "`{0}gelbooru yuri+kissing`" + }, + "rule34": { + "Cmd": "rule34", + "Desc": "Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": "`{0}rule34 yuri+kissing`" + }, + "e621": { + "Cmd": "e621", + "Desc": "Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags.", + "Usage": "`{0}e621 yuri kissing`" + }, + "boobs": { + "Cmd": "boobs", + "Desc": "Real adult content.", + "Usage": "`{0}boobs`" + }, + "butts": { + "Cmd": "butts ass butt", + "Desc": "Real adult content.", + "Usage": "`{0}butts` or `{0}ass`" + }, + "translate": { + "Cmd": "translate trans", + "Desc": "Translates from>to text. From the given language to the destination language.", + "Usage": "`{0}trans en>fr Hello`" + }, + "translangs": { + "Cmd": "translangs", + "Desc": "Lists the valid languages for translation.", + "Usage": "`{0}translangs`" + }, + "guide": { + "Cmd": "readme guide", + "Desc": "Sends a readme and a guide links to the channel.", + "Usage": "`{0}readme` or `{0}guide`" + }, + "calcops": { + "Cmd": "calcops", + "Desc": "Shows all available operations in the `{0}calc` command", + "Usage": "`{0}calcops`" + }, + "delallquotes": { + "Cmd": "delallq daq", + "Desc": "Deletes all quotes on a specified keyword.", + "Usage": "`{0}delallq kek`" + }, + "greetdmmsg": { + "Cmd": "greetdmmsg", + "Desc": "Sets a new join announcement message which will be sent to the user who joined. Type `%user%` if you want to mention the new member. Using it with no message will show the current DM greet message. You can use embed json from instead of a regular text, if you want the message to be embedded.", + "Usage": "`{0}greetdmmsg Welcome to the server, %user%`." + }, + "cash": { + "Cmd": "$ currency $$ $$$ cash cur", + "Desc": "Check how much currency a person has. (Defaults to yourself)", + "Usage": "`{0}$` or `{0}$ @SomeGuy`" + }, + "listperms": { + "Cmd": "listperms lp", + "Desc": "Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions.", + "Usage": "`{0}lp` or `{0}lp 3`" + }, + "allusrmdls": { + "Cmd": "allusrmdls aum", + "Desc": "Enable or disable all modules for a specific user.", + "Usage": "`{0}aum enable @someone`" + }, + "moveperm": { + "Cmd": "moveperm mp", + "Desc": "Moves permission from one position to another in the Permissions list.", + "Usage": "`{0}mp 2 4`" + }, + "removeperm": { + "Cmd": "removeperm rp", + "Desc": "Removes a permission from a given position in the Permissions list.", + "Usage": "`{0}rp 1`" + }, + "migratedata": { + "Cmd": "migratedata", + "Desc": "Migrate data from old bot configuration", + "Usage": "`{0}migratedata`" + }, + "checkstream": { + "Cmd": "checkstream cs", + "Desc": "Checks if a user is online on a certain streaming platform.", + "Usage": "`{0}cs twitch MyFavStreamer`" + }, + "showemojis": { + "Cmd": "showemojis se", + "Desc": "Shows a name and a link to every SPECIAL emoji in the message.", + "Usage": "`{0}se A message full of SPECIAL emojis`" + }, + "deckshuffle": { + "Cmd": "deckshuffle dsh", + "Desc": "Reshuffles all cards back into the deck.", + "Usage": "`{0}dsh`" + }, + "forwardmessages": { + "Cmd": "fwmsgs", + "Desc": "Toggles forwarding of non-command messages sent to bot's DM to the bot owners", + "Usage": "`{0}fwmsgs`" + }, + "forwardtoall": { + "Cmd": "fwtoall", + "Desc": "Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file", + "Usage": "`{0}fwtoall`" + }, + "resetpermissions": { + "Cmd": "resetperms", + "Desc": "Resets the bot's permissions module on this server to the default value.", + "Usage": "`{0}resetperms`" + }, + "antiraid": { + "Cmd": "antiraid", + "Desc": "Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute)", + "Usage": "`{0}antiraid 5 20 Kick`" + }, + "antispam": { + "Cmd": "antispam", + "Desc": "Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10.", + "Usage": "`{0}antispam 3 Mute` or `{0}antispam 4 Kick` or `{0}antispam 6 Ban`" + }, + "chatmute": { + "Cmd": "chatmute", + "Desc": "Prevents a mentioned user from chatting in text channels.", + "Usage": "`{0}chatmute @Someone`" + }, + "voicemute": { + "Cmd": "voicemute", + "Desc": "Prevents a mentioned user from speaking in voice channels.", + "Usage": "`{0}voicemute @Someone`" + }, + "konachan": { + "Cmd": "konachan", + "Desc": "Shows a random hentai image from konachan with a given tag. Tag is optional but preferred.", + "Usage": "`{0}konachan yuri`" + }, + "setmuterole": { + "Cmd": "setmuterole", + "Desc": "Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute.", + "Usage": "`{0}setmuterole Silenced`" + }, + "adsarm": { + "Cmd": "adsarm", + "Desc": "Toggles the automatic deletion of confirmations for `{0}iam` and `{0}iamn` commands.", + "Usage": "`{0}adsarm`" + }, + "setstream": { + "Cmd": "setstream", + "Desc": "Sets the bots stream. First argument is the twitch link, second argument is stream name.", + "Usage": "`{0}setstream TWITCHLINK Hello`" + }, + "chatunmute": { + "Cmd": "chatunmute", + "Desc": "Removes a mute role previously set on a mentioned user with `{0}chatmute` which prevented him from chatting in text channels.", + "Usage": "`{0}chatunmute @Someone`" + }, + "unmute": { + "Cmd": "unmute", + "Desc": "Unmutes a mentioned user previously muted with `{0}mute` command.", + "Usage": "`{0}unmute @Someone`" + }, + "xkcd": { + "Cmd": "xkcd", + "Desc": "Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and \"latest\" will get the latest one.", + "Usage": "`{0}xkcd` or `{0}xkcd 1400` or `{0}xkcd latest`" + }, + "placelist": { + "Cmd": "placelist", + "Desc": "Shows the list of available tags for the `{0}place` command.", + "Usage": "`{0}placelist`" + }, + "place": { + "Cmd": "place", + "Desc": "Shows a placeholder image of a given tag. Use `{0}placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments.", + "Usage": "`{0}place Cage` or `{0}place steven 500 400`" + }, + "togethertube": { + "Cmd": "togethertube totube", + "Desc": "Creates a new room on and shows the link in the chat.", + "Usage": "`{0}totube`" + }, + "poll": { + "Cmd": "poll ppoll", + "Desc": "Creates a public poll which requires users to type a number of the voting option in the channel command is ran in.", + "Usage": "`{0}ppoll Question?;Answer1;Answ 2;A_3`" + }, + "autotranslang": { + "Cmd": "autotranslang atl", + "Desc": "Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value.", + "Usage": "`{0}atl en>fr`" + }, + "autotranslate": { + "Cmd": "autotrans at", + "Desc": "Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set \"del\" argument to automatically delete all translated user messages.", + "Usage": "`{0}at` or `{0}at del`" + }, + "listquotes": { + "Cmd": "listquotes liqu", + "Desc": "Lists all quotes on the server ordered alphabetically. 15 Per page.", + "Usage": "`{0}liqu` or `{0}liqu 3`" + }, + "typedel": { + "Cmd": "typedel", + "Desc": "Deletes a typing article given the ID.", + "Usage": "`{0}typedel 3`" + }, + "typelist": { + "Cmd": "typelist", + "Desc": "Lists added typing articles with their IDs. 15 per page.", + "Usage": "`{0}typelist` or `{0}typelist 3`" + }, + "listservers": { + "Cmd": "listservers", + "Desc": "Lists servers the bot is on with some basic info. 15 per page.", + "Usage": "`{0}listservers 3`" + }, + "hentaibomb": { + "Cmd": "hentaibomb", + "Desc": "Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred.", + "Usage": "`{0}hentaibomb yuri`" + }, + "cleverbot": { + "Cmd": "cleverbot", + "Desc": "Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled.", + "Usage": "`{0}cleverbot`" + }, + "shorten": { + "Cmd": "shorten", + "Desc": "Attempts to shorten an URL, if it fails, returns the input URL.", + "Usage": "`{0}shorten https://google.com`" + }, + "mcping": { + "Cmd": "minecraftping mcping", + "Desc": "Pings a minecraft server.", + "Usage": "`{0}mcping 127.0.0.1:25565`" + }, + "mcq": { + "Cmd": "minecraftquery mcq", + "Desc": "Finds information about a minecraft server.", + "Usage": "`{0}mcq server:ip`" + }, + "wikia": { + "Cmd": "wikia", + "Desc": "Gives you back a wikia link", + "Usage": "`{0}wikia mtg Vigilance` or `{0}wikia mlp Dashy`" + }, + "yandere": { + "Cmd": "yandere", + "Desc": "Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +)", + "Usage": "`{0}yandere tag1+tag2`" + }, + "magicthegathering": { + "Cmd": "magicthegathering mtg", + "Desc": "Searches for a Magic The Gathering card.", + "Usage": "`{0}magicthegathering about face` or `{0}mtg about face`" + }, + "yodify": { + "Cmd": "yodify yoda", + "Desc": "Translates your normal sentences into Yoda styled sentences!", + "Usage": "`{0}yoda my feelings hurt`" + }, + "attack": { + "Cmd": "attack", + "Desc": "Attacks a target with the given move. Use `{0}movelist` to see a list of moves your type can use.", + "Usage": "`{0}attack \"vine whip\" @someguy`" + }, + "heal": { + "Cmd": "heal", + "Desc": "Heals someone. Revives those who fainted. Costs a NadekoFlower. ", + "Usage": "`{0}heal @someone`" + }, + "movelist": { + "Cmd": "movelist ml", + "Desc": "Lists the moves you are able to use", + "Usage": "`{0}ml`" + }, + "settype": { + "Cmd": "settype", + "Desc": "Set your poketype. Costs a NadekoFlower. Provide no arguments to see a list of available types.", + "Usage": "`{0}settype fire` or `{0}settype`" + }, + "type": { + "Cmd": "type", + "Desc": "Get the poketype of the target.", + "Usage": "`{0}type @someone`" + }, + "hangmanlist": { + "Cmd": "hangmanlist", + "Desc": "Shows a list of hangman term types.", + "Usage": "`{0}hangmanlist`" + }, + "hangman": { + "Cmd": "hangman", + "Desc": "Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to 'all'.", + "Usage": "`{0}hangman` or `{0}hangman movies`" + }, + "hangmanstop": { + "Cmd": "hangmanstop", + "Desc": "Stops the active hangman game on this channel if it exists.", + "Usage": "`{0}hangmanstop`" + }, + "crstatsclear": { + "Cmd": "crstatsclear", + "Desc": "Resets the counters on `{0}crstats`. You can specify a trigger to clear stats only for that trigger.", + "Usage": "`{0}crstatsclear` or `{0}crstatsclear rng`" + }, + "crstats": { + "Cmd": "crstats", + "Desc": "Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `{0}crstatsclear` to reset the counters.", + "Usage": "`{0}crstats` or `{0}crstats 3`" + }, + "overwatch": { + "Cmd": "overwatch ow", + "Desc": "Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr`", + "Usage": "`{0}ow us Battletag#1337` or `{0}overwatch eu Battletag#2016`" + }, + "acro": { + "Cmd": "acrophobia acro", + "Desc": "Starts an Acrophobia game. Second argument is optional round length in seconds. (default is 60)", + "Usage": "`{0}acro` or `{0}acro 30`" + }, + "logevents": { + "Cmd": "logevents", + "Desc": "Shows a list of all events you can subscribe to with `{0}log`", + "Usage": "`{0}logevents`" + }, + "log": { + "Cmd": "log", + "Desc": "Toggles logging event. Disables it if it is active anywhere on the server. Enables if it isn't active. Use `{0}logevents` to see a list of all events you can subscribe to.", + "Usage": "`{0}log userpresence` or `{0}log userbanned`" + }, + "fairplay": { + "Cmd": "fairplay fp", + "Desc": "Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue.", + "Usage": "`{0}fp`" + }, + "songautodelete": { + "Cmd": "songautodelete sad", + "Desc": "Toggles whether the song should be automatically removed from the music queue when it finishes playing.", + "Usage": "`{0}sad`" + }, + "define": { + "Cmd": "define def", + "Desc": "Finds a definition of a word.", + "Usage": "`{0}def heresy`" + }, + "setmaxplaytime": { + "Cmd": "setmaxplaytime smp", + "Desc": "Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit.", + "Usage": "`{0}smp 0` or `{0}smp 270`" + }, + "activity": { + "Cmd": "activity", + "Desc": "Checks for spammers.", + "Usage": "`{0}activity`" + }, + "autohentai": { + "Cmd": "autohentai", + "Desc": "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.", + "Usage": "`{0}autohentai 30 yuri|tail|long_hair` or `{0}autohentai`" + }, + "setstatus": { + "Cmd": "setstatus", + "Desc": "Sets the bot's status. (Online/Idle/Dnd/Invisible)", + "Usage": "`{0}setstatus Idle`" + }, + "rotaterolecolor": { + "Cmd": "rotaterolecolor rrc", + "Desc": "Rotates a roles color on an interval with a list of supplied colors. First argument is interval in seconds (Minimum 60). Second argument is a role, followed by a space-separated list of colors in hex. Provide a rolename with a 0 interval to disable.", + "Usage": "`{0}rrc 60 MyLsdRole #ff0000 #00ff00 #0000ff` or `{0}rrc 0 MyLsdRole`" + }, + "createinvite": { + "Cmd": "createinvite crinv", + "Desc": "Creates a new invite which has infinite max uses and never expires.", + "Usage": "`{0}crinv`" + }, + "pollstats": { + "Cmd": "pollstats", + "Desc": "Shows the poll results without stopping the poll on this server.", + "Usage": "`{0}pollstats`" + }, + "repeatlist": { + "Cmd": "repeatlist replst", + "Desc": "Shows currently repeating messages and their indexes.", + "Usage": "`{0}repeatlist`" + }, + "repeatremove": { + "Cmd": "repeatremove reprm", + "Desc": "Removes a repeating message on a specified index. Use `{0}repeatlist` to see indexes.", + "Usage": "`{0}reprm 2`" + }, + "antilist": { + "Cmd": "antilist antilst", + "Desc": "Shows currently enabled protection features.", + "Usage": "`{0}antilist`" + }, + "antispamignore": { + "Cmd": "antispamignore", + "Desc": "Toggles whether antispam ignores current channel. Antispam must be enabled.", + "Usage": "`{0}antispamignore`" + }, + "cmdcosts": { + "Cmd": "cmdcosts", + "Desc": "Shows a list of command costs. Paginated with 9 commands per page.", + "Usage": "`{0}cmdcosts` or `{0}cmdcosts 2`" + }, + "commandcost": { + "Cmd": "commandcost cmdcost", + "Desc": "Sets a price for a command. Running that command will take currency from users. Set 0 to remove the price.", + "Usage": "`{0}cmdcost 0 !!q` or `{0}cmdcost 1 {0}8ball`" + }, + "startevent": { + "Cmd": "startevent", + "Desc": "Starts one of the events seen on public nadeko.", + "Usage": "`{0}startevent flowerreaction`" + }, + "slotstats": { + "Cmd": "slotstats", + "Desc": "Shows the total stats of the slot command for this bot's session.", + "Usage": "`{0}slotstats`" + }, + "slottest": { + "Cmd": "slottest", + "Desc": "Tests to see how much slots payout for X number of plays.", + "Usage": "`{0}slottest 1000`" + }, + "slot": { + "Cmd": "slot", + "Desc": "Play Nadeko slots. Max bet is 9999. 1.5 second cooldown per user.", + "Usage": "`{0}slot 5`" + }, + "waifuclaimeraffinity": { + "Cmd": "affinity", + "Desc": "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.", + "Usage": "`{0}affinity @MyHusband` or `{0}affinity`" + }, + "waifuclaim": { + "Cmd": "claimwaifu claim", + "Desc": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `{0}affinity` towards you.", + "Usage": "`{0}claim 50 @Himesama`" + }, + "waifugift": { + "Cmd": "waifugift gift gifts", + "Desc": "Gift an item to someone. This will increase their waifu value by 50% of the gifted item's value if they don't have affinity set towards you, or 100% if they do. Provide no arguments to see a list of items that you can gift.", + "Usage": "`{0}gifts` or `{0}gift Rose @Himesama`" + }, + "waifuleaderboard": { + "Cmd": "waifus waifulb", + "Desc": "Shows top 9 waifus. You can specify another page to show other waifus.", + "Usage": "`{0}waifus` or `{0}waifulb 3`" + }, + "divorce": { + "Cmd": "divorce", + "Desc": "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.", + "Usage": "`{0}divorce @CheatingSloot`" + }, + "waifuinfo": { + "Cmd": "waifuinfo waifustats", + "Desc": "Shows waifu stats for a target person. Defaults to you if no user is provided.", + "Usage": "`{0}waifuinfo @MyCrush` or `{0}waifuinfo`" + }, + "mal": { + "Cmd": "mal", + "Desc": "Shows basic info from a MyAnimeList profile.", + "Usage": "`{0}mal straysocks`" + }, + "setmusicchannel": { + "Cmd": "setmusicchannel smch", + "Desc": "Sets the current channel as the default music output channel. This will output playing, finished, paused and removed songs to that channel instead of the channel where the first song was queued in.", + "Usage": "`{0}smch`" + }, + "reloadimages": { + "Cmd": "reloadimages", + "Desc": "Reloads images bot is using. Safe to use even when bot is being used heavily.", + "Usage": "`{0}reloadimages`" + }, + "shardstats": { + "Cmd": "shardstats", + "Desc": "Stats for shards. Paginated with 25 shards per page.", + "Usage": "`{0}shardstats` or `{0}shardstats 2`" + }, + "restartshard": { + "Cmd": "restartshard", + "Desc": "Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors.", + "Usage": "`{0}restartshard 2`" + }, + "shardid": { + "Cmd": "shardid", + "Desc": "Shows which shard is a certain guild on, by guildid.", + "Usage": "`{0}shardid 117523346618318850`" + }, + "tictactoe": { + "Cmd": "tictactoe ttt", + "Desc": "Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move.", + "Usage": "{0}ttt" + }, + "timezones": { + "Cmd": "timezones", + "Desc": "Lists all timezones available on the system to be used with `{0}timezone`.", + "Usage": "`{0}timezones`" + }, + "timezone": { + "Cmd": "timezone", + "Desc": "Sets this guilds timezone. This affects bot's time output in this server (logs, etc..)", + "Usage": "`{0}timezone` or `{0}timezone GMT Standard Time`" + }, + "languagesetdefault": { + "Cmd": "langsetdefault langsetd", + "Desc": "Sets the bot's default response language. All servers which use a default locale will use this one. Setting to `default` will use the host's current culture. Provide no arguments to see currently set language.", + "Usage": "`{0}langsetd en-US` or `{0}langsetd default`" + }, + "languageset": { + "Cmd": "languageset langset", + "Desc": "Sets this server's response language. If bot's response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language.", + "Usage": "`{0}langset de-DE ` or `{0}langset default`" + }, + "languageslist": { + "Cmd": "languageslist langli", + "Desc": "List of languages for which translation (or part of it) exist atm.", + "Usage": "`{0}langli`" + }, + "rategirl": { + "Cmd": "rategirl", + "Desc": "Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart.", + "Usage": "`{0}rategirl @SomeGurl`" + }, + "lucky7test": { + "Cmd": "lucky7test l7t", + "Desc": "Tests the l7 command.", + "Usage": "`{0}l7t 10000`" + }, + "lucky7": { + "Cmd": "lucky7 l7", + "Desc": "Bet currency on the game and start rolling 3 sided dice. At any point you can choose to [m]ove (roll again) or [s]tay (get the amount bet times the current multiplier).", + "Usage": "`{0}l7 10` or `{0}l7 move` or `{0}l7 s`" + }, + "vcrolelist": { + "Cmd": "vcrolelist", + "Desc": "Shows a list of currently set voice channel roles.", + "Usage": "`{0}vcrolelist`" + }, + "vcrole": { + "Cmd": "vcrole", + "Desc": "Sets or resets a role which will be given to users who join the voice channel you're in when you run this command. Provide no role name to disable. You must be in a voice channel to run this command.", + "Usage": "`{0}vcrole SomeRole` or `{0}vcrole`" + }, + "crad": { + "Cmd": "crad", + "Desc": "Toggles whether the message triggering the custom reaction will be automatically deleted.", + "Usage": "`{0}crad 59`" + }, + "crdm": { + "Cmd": "crdm", + "Desc": "Toggles whether the response message of the custom reaction will be sent as a direct message.", + "Usage": "`{0}crdm 44`" + }, + "crca": { + "Cmd": "crca", + "Desc": "Toggles whether the custom reaction will trigger if the triggering message contains the keyword (instead of only starting with it).", + "Usage": "`{0}crca 44`" + }, + "aliaslist": { + "Cmd": "aliaslist cmdmaplist aliases", + "Desc": "Shows the list of currently set aliases. Paginated.", + "Usage": "`{0}aliaslist` or `{0}aliaslist 3`" + }, + "alias": { + "Cmd": "alias cmdmap", + "Desc": "Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one.", + "Usage": "`{0}alias allin $bf 100 h` or `{0}alias \"linux thingy\" >loonix Spyware Windows`" + }, + "warnlog": { + "Cmd": "warnlog", + "Desc": "See a list of warnings of a certain user.", + "Usage": "`{0}warnlog @b1nzy`" + }, + "warnlogall": { + "Cmd": "warnlogall", + "Desc": "See a list of all warnings on the server. 15 users per page.", + "Usage": "`{0}warnlogall` or `{0}warnlogall 2`" + }, + "warn": { + "Cmd": "warn", + "Desc": "Warns a user.", + "Usage": "`{0}warn @b1nzy Very rude person`" + }, + "startupcommandadd": { + "Cmd": "scadd", + "Desc": "Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up.", + "Usage": "`{0}scadd .stats`" + }, + "startupcommandremove": { + "Cmd": "scrm", + "Desc": "Removes a startup command with the provided command text.", + "Usage": "`{0}scrm .stats`" + }, + "startupcommandsclear": { + "Cmd": "scclr", + "Desc": "Removes all startup commands.", + "Usage": "`{0}scclr`" + }, + "startupcommands": { + "Cmd": "sclist", + "Desc": "Lists all startup commands in the order they will be executed in.", + "Usage": "`{0}sclist`" + }, + "unban": { + "Cmd": "unban", + "Desc": "Unbans a user with the provided user#discrim or id.", + "Usage": "`{0}unban kwoth#1234` or `{0}unban 123123123`" + }, + "wait": { + "Cmd": "wait", + "Desc": "Used only as a startup command. Waits a certain number of miliseconds before continuing the execution of the following startup commands.", + "Usage": "`{0}wait 3000`" + }, + "warnclear": { + "Cmd": "warnclear warnc", + "Desc": "Clears all warnings from a certain user.", + "Usage": "`{0}warnclear @PoorDude`" + }, + "warnpunishlist": { + "Cmd": "warnpunishlist warnpl", + "Desc": "Lists punishments for warnings.", + "Usage": "`{0}warnpunishlist`" + }, + "warnpunish": { + "Cmd": "warnpunish warnp", + "Desc": "Sets a punishment for a certain number of warnings. Provide no punishment to remove.", + "Usage": "`{0}warnpunish 5 Ban` or `{0}warnpunish 3`" + }, + "claimpatreonrewards": { + "Cmd": "clparew", + "Desc": "Claim patreon rewards. If you're subscribed to bot owner's patreon you can use this command to claim your rewards - assuming bot owner did setup has their patreon key.", + "Usage": "`{0}clparew`" + }, + "ping": { + "Cmd": "ping", + "Desc": "Ping the bot to see if there are latency issues.", + "Usage": "`{0}ping`" + }, + "slowmodewhitelist": { + "Cmd": "slowmodewl", + "Desc": "Ignores a role or a user from the slowmode feature.", + "Usage": "`{0}slowmodewl SomeRole` or `{0}slowmodewl AdminDude`" + }, + "time": { + "Cmd": "time", + "Desc": "Shows the current time and timezone in the specified location.", + "Usage": "`{0}time London, UK`" + }, + "patreonrewardsreload": { + "Cmd": "parewrel", + "Desc": "Forces the update of the list of patrons who are eligible for the reward.", + "Usage": "`{0}parewrel`" + }, + "shopadd": { + "Cmd": "shopadd", + "Desc": "Adds an item to the shop by specifying type price and name. Available types are role and list.", + "Usage": "`{0}shopadd role 1000 Rich`" + }, + "shopremove": { + "Cmd": "shoprem shoprm", + "Desc": "Removes an item from the shop by its ID.", + "Usage": "`{0}shoprm 1`" + }, + "shop": { + "Cmd": "shop", + "Desc": "Lists this server's administrators' shop. Paginated.", + "Usage": "`{0}shop` or `{0}shop 2`" + }, + "rolehoist": { + "Cmd": "rolehoist rh", + "Desc": "Toggles whether this role is displayed in the sidebar or not.", + "Usage": "`{0}rh Guests` or `{0}rh \"Space Wizards\"`" + }, + "buy": { + "Cmd": "buy", + "Desc": "Buys an item from the shop on a given index. If buying items, make sure that the bot can DM you.", + "Usage": "`{0}buy 2`" + }, + "gamevoicechannel": { + "Cmd": "gvc", + "Desc": "Toggles game voice channel feature in the voice channel you're currently in. Users who join the game voice channel will get automatically redirected to the voice channel with the name of their current game, if it exists. Can't move users to channels that the bot has no connect permission for. One per server.", + "Usage": "`{0}gvc`" + }, + "shoplistadd": { + "Cmd": "shoplistadd", + "Desc": "Adds an item to the list of items for sale in the shop entry given the index. You usually want to run this command in the secret channel, so that the unique items are not leaked.", + "Usage": "`{0}shoplistadd 1 Uni-que-Steam-Key`" + }, + "gcmd": { + "Cmd": "globalcommand gcmd", + "Desc": "Toggles whether a command can be used on any server.", + "Usage": "`{0}gcmd .stats`" + }, + "gmod": { + "Cmd": "globalmodule gmod", + "Desc": "Toggles whether a module can be used on any server.", + "Usage": "`{0}gmod nsfw`" + }, + "lgp": { + "Cmd": "listglobalperms lgp", + "Desc": "Lists global permissions set by the bot owner.", + "Usage": "`{0}lgp`" + }, + "resetglobalpermissions": { + "Cmd": "resetglobalperms", + "Desc": "Resets global permissions set by bot owner.", + "Usage": "`{0}resetglobalperms`" + }, + "prefix": { + "Cmd": "prefix", + "Desc": "Sets this server's prefix for all bot commands. Provide no arguments to see the current server prefix.", + "Usage": "`{0}prefix +`" + }, + "defprefix": { + "Cmd": "defprefix", + "Desc": "Sets bot's default prefix for all bot commands. Provide no arguments to see the current default prefix. This will not change this server's current prefix.", + "Usage": "`{0}defprefix +`" + }, + "verboseerror": { + "Cmd": "verboseerror ve", + "Desc": "Toggles whether the bot should print command errors when a command is incorrectly used.", + "Usage": "`{0}ve`" + }, + "streamrolekeyword": { + "Cmd": "streamrolekw srkw", + "Desc": "Sets keyword which is required in the stream's title in order for the streamrole to apply. Provide no keyword in order to reset.", + "Usage": "`{0}srkw` or `{0}srkw PUBG`" + }, + "streamroleblacklist": { + "Cmd": "streamrolebl srbl", + "Desc": "Adds or removes a blacklisted user. Blacklisted users will never receive the stream role.", + "Usage": "`{0}srbl add @b1nzy#1234` or `{0}srbl rem @b1nzy#1234`" + }, + "streamrolewhitelist": { + "Cmd": "streamrolewl srwl", + "Desc": "Adds or removes a whitelisted user. Whitelisted users will receive the stream role even if they don't have the specified keyword in their stream title.", + "Usage": "`{0}srwl add @b1nzy#1234` or `{0}srwl rem @b1nzy#1234`" + }, + "botconfigedit": { + "Cmd": "botconfigedit bce", + "Desc": "Sets one of available bot config settings to a specified value. Use the command without any parameters to get a list of available settings.", + "Usage": "`{0}bce CurrencyName b1nzy` or `{0}bce`" + }, + "nsfwtagblacklist": { + "Cmd": "nsfwtagbl nsfwtbl", + "Desc": "Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags.", + "Usage": "`{0}nsfwtbl poop`" + }, + "experience": { + "Cmd": "experience xp", + "Desc": "Shows your xp stats. Specify the user to show that user's stats instead.", + "Usage": "`{0}xp`" + }, + "xpexclusionlist": { + "Cmd": "xpexclusionlist xpexl", + "Desc": "Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded.", + "Usage": "`{0}xpexl`" + }, + "xpexclude": { + "Cmd": "xpexclude xpex", + "Desc": "Exclude a channel, role or current server from the xp system.", + "Usage": "`{0}xpex Role Excluded-Role` `{0}xpex Server`" + }, + "xpnotify": { + "Cmd": "xpnotify xpn", + "Desc": "Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable.", + "Usage": "`{0}xpn global dm` `{0}xpn server channel`" + }, + "xprolerewards": { + "Cmd": "xprolerewards xprrs", + "Desc": "Shows currently set role rewards.", + "Usage": "`{0}xprrs`" + }, + "xprolereward": { + "Cmd": "xprolereward xprr", + "Desc": "Sets a role reward on a specified level. Provide no role name in order to remove the role reward.", + "Usage": "`{0}xprr 3 Social`" + }, + "xpleaderboard": { + "Cmd": "xpleaderboard xplb", + "Desc": "Shows current server's xp leaderboard.", + "Usage": "`{0}xplb`" + }, + "xpgloballeaderboard": { + "Cmd": "xpgleaderboard xpglb", + "Desc": "Shows the global xp leaderboard.", + "Usage": "`{0}xpglb`" + }, + "xpadd": { + "Cmd": "xpadd", + "Desc": "Adds xp to a user on the server. This does not affect their global ranking. You can use negative values.", + "Usage": "`{0}xpadd 100 @b1nzy`" + }, + "clubcreate": { + "Cmd": "clubcreate", + "Desc": "Creates a club. You must be atleast level 5 and not be in the club already.", + "Usage": "`{0}clubcreate b1nzy's friends`" + }, + "clubinformation": { + "Cmd": "clubinfo", + "Desc": "Shows information about the club.", + "Usage": "`{0}clubinfo b1nzy's friends#123`" + }, + "clubapply": { + "Cmd": "clubapply", + "Desc": "Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list.", + "Usage": "`{0}clubapply b1nzy's friends#123`" + }, + "clubaccept": { + "Cmd": "clubaccept", + "Desc": "Accept a user who applied to your club.", + "Usage": "`{0}clubaccept b1nzy#1337`" + }, + "clubleave": { + "Cmd": "clubleave", + "Desc": "Leaves the club you're currently in.", + "Usage": "`{0}clubleave`" + }, + "clubdisband": { + "Cmd": "clubdisband", + "Desc": "Disbands the club you're the owner of. This action is irreversible.", + "Usage": "`{0}clubdisband`" + }, + "clubkick": { + "Cmd": "clubkick", + "Desc": "Kicks the user from the club. You must be the club owner. They will be able to apply again.", + "Usage": "`{0}clubkick b1nzy#1337`" + }, + "clubban": { + "Cmd": "clubban", + "Desc": "Bans the user from the club. You must be the club owner. They will not be able to apply again.", + "Usage": "`{0}clubban b1nzy#1337`" + }, + "clubunban": { + "Cmd": "clubunban", + "Desc": "Unbans the previously banned user from the club. You must be the club owner.", + "Usage": "`{0}clubunban b1nzy#1337`" + }, + "clublevelreq": { + "Cmd": "clublevelreq", + "Desc": "Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5.", + "Usage": "`{0}clublevelreq 7`" + }, + "clubicon": { + "Cmd": "clubicon", + "Desc": "Sets the club icon.", + "Usage": "`{0}clubicon https://i.imgur.com/htfDMfU.png`" + }, + "clubapps": { + "Cmd": "clubapps", + "Desc": "Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command.", + "Usage": "`{0}clubapps 2`" + }, + "clubbans": { + "Cmd": "clubbans", + "Desc": "Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command.", + "Usage": "`{0}clubbans 2`" + }, + "clubleaderboard": { + "Cmd": "clublb", + "Desc": "Shows club rankings on the specified page.", + "Usage": "`{0}clublb 2`" + }, + "nsfwclearcache": { + "Cmd": "nsfwcc", + "Desc": "Clears nsfw cache.", + "Usage": "`{0}nsfwcc`" + }, + "clubadmin": { + "Cmd": "clubadmin", + "Desc": "Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications.", + "Usage": "`{0}clubadmin`" + }, + "autoboobs": { + "Cmd": "autoboobs", + "Desc": "Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable.", + "Usage": "`{0}autoboobs 30` or `{0}autoboobs`" + }, + "autobutts": { + "Cmd": "autobutts", + "Desc": "Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable.", + "Usage": "`{0}autobutts 30` or `{0}autobutts`" + }, + "eightball": { + "cmd": "8ball", + "desc": "Ask the 8ball a yes/no question.", + "usage": "`{0}8ball`" + } +} \ No newline at end of file From 038f17c3a438f5cdcc7d7a8e8f96e89a3d942dd1 Mon Sep 17 00:00:00 2001 From: Master Kwoth Date: Sun, 17 Sep 2017 08:33:16 +0200 Subject: [PATCH 16/16] Commands which have no command strings should no longer throw an exception --- src/NadekoBot/Common/Attributes/Aliases.cs | 3 --- src/NadekoBot/Services/Impl/BotCredentials.cs | 5 ----- src/NadekoBot/Services/Impl/Localization.cs | 9 +++++++++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/NadekoBot/Common/Attributes/Aliases.cs b/src/NadekoBot/Common/Attributes/Aliases.cs index b6ebbf81..e6c95f67 100644 --- a/src/NadekoBot/Common/Attributes/Aliases.cs +++ b/src/NadekoBot/Common/Attributes/Aliases.cs @@ -2,9 +2,6 @@ using System.Runtime.CompilerServices; using Discord.Commands; using NadekoBot.Services.Impl; - -//todo what if it doesn't exist - namespace NadekoBot.Common.Attributes { public class Aliases : AliasAttribute diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 341c947e..0ee0dea9 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -90,11 +90,6 @@ namespace NadekoBot.Services.Impl ulong.TryParse(data[nameof(ClientId)], out ulong clId); ClientId = clId; - //var scId = data[nameof(SoundCloudClientId)]; - //SoundCloudClientId = scId; - //SoundCloudClientId = string.IsNullOrWhiteSpace(scId) - // ? - // : scId; CarbonKey = data[nameof(CarbonKey)]; var dbSection = data.GetSection("db"); Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"]) diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs index b8a977ec..438153a0 100644 --- a/src/NadekoBot/Services/Impl/Localization.cs +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -131,6 +131,15 @@ namespace NadekoBot.Services.Impl public static CommandData LoadCommand(string key) { _commandData.TryGetValue(key, out var toReturn); + + if (toReturn == null) + return new CommandData + { + Cmd = key, + Desc = key, + Usage = key, + }; + return toReturn; } }