From 3a25433ec83c87d15d3d3ec5fff50cc6193c614b Mon Sep 17 00:00:00 2001 From: Toastie Date: Wed, 21 Aug 2024 20:22:31 +1200 Subject: [PATCH] Quote commands slightly changed and some of them renamed. Added a lot of new aliases. Notable rename is .liqu to .qli Quotes now follow the same naming pattern as Expression commands Code vastly improved --- .../Modules/Utility/Quote/IQuoteService.cs | 46 +++++- .../Modules/Utility/Quote/QuoteCommands.cs | 152 +++--------------- .../Modules/Utility/Quote/QuoteService.cs | 110 +++++++++++-- .../Utility/Quote/_common/ExportedQuote.cs | 20 +++ src/EllieBot/data/aliases.yml | 38 +++-- .../data/strings/commands/commands.en-US.yml | 2 +- 6 files changed, 222 insertions(+), 146 deletions(-) create mode 100644 src/EllieBot/Modules/Utility/Quote/_common/ExportedQuote.cs diff --git a/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs b/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs index ccf360e..20bec68 100644 --- a/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs +++ b/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs @@ -1,6 +1,50 @@ -namespace EllieBot.Modules.Utility; +using EllieBot.Db.Models; + +namespace EllieBot.Modules.Utility; public interface IQuoteService { + /// + /// Delete all quotes created by the author in a guild + /// + /// ID of the guild + /// ID of the user + /// Number of deleted qutoes Task DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId); + + /// + /// Delete all quotes in a guild + /// + /// ID of the guild + /// Number of deleted qutoes + Task DeleteAllQuotesAsync(ulong guildId); + + Task> GetAllQuotesAsync(ulong guildId, int page, OrderType order); + Task GetQuoteByKeywordAsync(ulong guildId, string keyword); + + Task> SearchQuoteKeywordTextAsync( + ulong guildId, + string? keyword, + string text); + + Task> GetGuildQuotesAsync(ulong guildId); + Task RemoveAllByKeyword(ulong guildId, string keyword); + Task GetQuoteByIdAsync(ulong guildId, kwum quoteId); + + Task AddQuoteAsync( + ulong guildId, + ulong authorId, + string authorName, + string keyword, + string text); + + Task EditQuoteAsync(ulong authorId, int quoteId, string text); + + Task DeleteQuoteAsync( + ulong guildId, + ulong authorId, + bool isQuoteManager, + int quoteId); + + Task ImportQuotesAsync(ulong guildId, string input); } \ No newline at end of file diff --git a/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs b/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs index 27c7b23..bf50b9c 100644 --- a/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs +++ b/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs @@ -1,6 +1,4 @@ #nullable disable warnings -using LinqToDB; -using LinqToDB.EntityFrameworkCore; using EllieBot.Common.Yml; using EllieBot.Db.Models; using YamlDotNet.Serialization; @@ -11,7 +9,7 @@ namespace EllieBot.Modules.Utility; public partial class Utility { [Group] - public partial class QuoteCommands : EllieModule + public partial class QuoteCommands : EllieModule { private const string PREPEND_EXPORT = """ @@ -48,19 +46,19 @@ public partial class Utility [Cmd] [RequireContext(ContextType.Guild)] [Priority(1)] - public Task ListQuotes(OrderType order = OrderType.Keyword) - => ListQuotes(1, order); + public Task QuoteList(OrderType order = OrderType.Keyword) + => QuoteList(1, order); [Cmd] [RequireContext(ContextType.Guild)] [Priority(0)] - public async Task ListQuotes(int page = 1, OrderType order = OrderType.Keyword) + public async Task QuoteList(int page = 1, OrderType order = OrderType.Keyword) { page -= 1; if (page < 0) return; - var quotes = await _service.GetAllQuotesAsync(ctx.Guild.Id, page, order); + var quotes = await _qs.GetAllQuotesAsync(ctx.Guild.Id, page, order); if (quotes.Count == 0) { @@ -85,7 +83,7 @@ public partial class Utility keyword = keyword.ToUpperInvariant(); - var quote = await _service.GetQuoteByKeywordAsync(ctx.Guild.Id, keyword); + var quote = await _qs.GetQuoteByKeywordAsync(ctx.Guild.Id, keyword); if (quote is null) return; @@ -97,7 +95,6 @@ public partial class Utility await Response() .Text($"`{new kwum(quote.Id)}` 📣 " + text) - .Sanitize() .SendAsync(); } @@ -106,7 +103,7 @@ public partial class Utility [RequireContext(ContextType.Guild)] public async Task QuoteShow(kwum quoteId) { - var quote = await _service.GetQuoteByIdAsync(ctx.Guild.Id, quoteId); + var quote = await _qs.GetQuoteByIdAsync(ctx.Guild.Id, quoteId); if (quote is null) { @@ -179,7 +176,7 @@ public partial class Utility keyword = keyword?.ToUpperInvariant(); - var quotes = await _service.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor); + var quotes = await _qs.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor); await Response() .Paginated() @@ -218,7 +215,7 @@ public partial class Utility if (quoteId < 0) return; - var quote = await _service.GetQuoteByIdAsync(ctx.Guild.Id, quoteId); + var quote = await _qs.GetQuoteByIdAsync(ctx.Guild.Id, quoteId); if (quote is null) { @@ -226,8 +223,8 @@ public partial class Utility return; } - var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName.SanitizeAllMentions()}* 🗯️ " - + quote.Keyword.ToLowerInvariant().SanitizeAllMentions() + var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName}* 🗯️ " + + quote.Keyword.ToLowerInvariant() + ":\n"; @@ -236,7 +233,6 @@ public partial class Utility text = await repSvc.ReplaceAsync(text, repCtx); await Response() .Text(infoText + text) - .Sanitize() .SendAsync(); } @@ -248,26 +244,14 @@ public partial class Utility if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text)) return; - keyword = keyword.ToUpperInvariant(); + var quote = await _qs.AddQuoteAsync(ctx.Guild.Id, ctx.User.Id, ctx.User.Username, keyword, text); - Quote q; - await using (var uow = _db.GetDbContext()) - { - uow.Set() - .Add(q = new() - { - AuthorId = ctx.Message.Author.Id, - AuthorName = ctx.Message.Author.Username, - GuildId = ctx.Guild.Id, - Keyword = keyword, - Text = text - }); - await uow.SaveChangesAsync(); - } - - await Response().Confirm(strs.quote_added_new(Format.Code(new kwum(q.Id).ToString()))).SendAsync(); + await Response() + .Confirm(strs.quote_added_new(Format.Code(new kwum(quote.Id).ToString()))) + .SendAsync(); } + [Cmd] [RequireContext(ContextType.Guild)] public async Task QuoteEdit(kwum quoteId, [Leftover] string text) @@ -277,19 +261,7 @@ public partial class Utility return; } - Quote q; - await using (var uow = _db.GetDbContext()) - { - var intId = (int)quoteId; - var result = await uow.GetTable() - .Where(x => x.Id == intId && x.AuthorId == ctx.User.Id) - .Set(x => x.Text, text) - .UpdateWithOutputAsync((del, ins) => ins); - - q = result.FirstOrDefault(); - - await uow.SaveChangesAsync(); - } + var q = await _qs.EditQuoteAsync(ctx.User.Id, quoteId, text); if (q is not null) { @@ -309,33 +281,19 @@ public partial class Utility } } + [Cmd] [RequireContext(ContextType.Guild)] public async Task QuoteDelete(kwum quoteId) { var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages; - var success = false; - string response; - await using (var uow = _db.GetDbContext()) - { - var q = uow.Set().GetById(quoteId); - - if (q?.GuildId != ctx.Guild.Id || (!hasManageMessages && q.AuthorId != ctx.Message.Author.Id)) - response = GetText(strs.quotes_remove_none); - else - { - uow.Set().Remove(q); - await uow.SaveChangesAsync(); - success = true; - response = GetText(strs.quote_deleted(new kwum(quoteId))); - } - } + var success = await _qs.DeleteQuoteAsync(ctx.Guild.Id, ctx.User.Id, hasManageMessages, quoteId); if (success) - await Response().Confirm(response).SendAsync(); + await Response().Confirm(strs.quote_deleted(quoteId)).SendAsync(); else - await Response().Error(response).SendAsync(); + await Response().Error(strs.quotes_remove_none).SendAsync(); } [Cmd] @@ -368,16 +326,9 @@ public partial class Utility if (string.IsNullOrWhiteSpace(keyword)) return; - keyword = keyword.ToUpperInvariant(); + await _qs.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant()); - await using (var uow = _db.GetDbContext()) - { - await _service.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant()); - - await uow.SaveChangesAsync(); - } - - await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions()))).SendAsync(); + await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword))).SendAsync(); } [Cmd] @@ -385,7 +336,7 @@ public partial class Utility [UserPerm(GuildPerm.Administrator)] public async Task QuotesExport() { - var quotes = _service.GetForGuild(ctx.Guild.Id).ToList(); + var quotes = await _qs.GetGuildQuotesAsync(ctx.Guild.Id); var exprsDict = quotes.GroupBy(x => x.Keyword) .ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel)); @@ -400,7 +351,7 @@ public partial class Utility [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] [Ratelimit(300)] -#if GLOBAL_ELLIE +#if GLOBAL_NADEKO [OwnerOnly] #endif public async Task QuotesImport([Leftover] string? input = null) @@ -428,7 +379,7 @@ public partial class Utility } } - var succ = await ImportExprsAsync(ctx.Guild.Id, input); + var succ = await _qs.ImportQuotesAsync(ctx.Guild.Id, input); if (!succ) { await Response().Error(strs.expr_import_invalid_data).SendAsync(); @@ -437,56 +388,5 @@ public partial class Utility await ctx.OkAsync(); } - - private async Task ImportExprsAsync(ulong guildId, string input) - { - Dictionary> data; - try - { - data = Yaml.Deserializer.Deserialize>>(input); - if (data.Sum(x => x.Value.Count) == 0) - return false; - } - catch - { - return false; - } - - await using var uow = _db.GetDbContext(); - foreach (var entry in data) - { - var keyword = entry.Key; - await uow.Set() - .AddRangeAsync(entry.Value.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt)) - .Select(quote => new Quote - { - GuildId = guildId, - Keyword = keyword, - Text = quote.Txt, - AuthorId = quote.Aid, - AuthorName = quote.An - })); - } - - await uow.SaveChangesAsync(); - return true; - } - - public class ExportedQuote - { - public string Id { get; set; } - public string An { get; set; } - public ulong Aid { get; set; } - public string Txt { get; set; } - - public static ExportedQuote FromModel(Quote quote) - => new() - { - Id = ((kwum)quote.Id).ToString(), - An = quote.AuthorName, - Aid = quote.AuthorId, - Txt = quote.Text - }; - } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Utility/Quote/QuoteService.cs b/src/EllieBot/Modules/Utility/Quote/QuoteService.cs index 2f75ce2..8cf8d62 100644 --- a/src/EllieBot/Modules/Utility/Quote/QuoteService.cs +++ b/src/EllieBot/Modules/Utility/Quote/QuoteService.cs @@ -1,6 +1,8 @@ #nullable disable warnings using LinqToDB; +using LinqToDB.Data; using LinqToDB.EntityFrameworkCore; +using EllieBot.Common.Yml; using EllieBot.Db.Models; namespace EllieBot.Modules.Utility; @@ -45,19 +47,19 @@ public sealed class QuoteService : IQuoteService, IEService return deleted; } - public async Task> GetAllQuotesAsync(ulong guildId, int page, OrderType order) + public async Task> GetAllQuotesAsync(ulong guildId, int page, OrderType order) { await using var uow = _db.GetDbContext(); var q = uow.Set() .ToLinqToDBTable() .Where(x => x.GuildId == guildId); - + if (order == OrderType.Keyword) q = q.OrderBy(x => x.Keyword); else q = q.OrderBy(x => x.Id); - - return await q.Skip(15 * page).Take(15).ToArrayAsync(); + + return await q.Skip(15 * page).Take(15).ToArrayAsyncLinqToDB(); } public async Task GetQuoteByKeywordAsync(ulong guildId, string keyword) @@ -97,11 +99,12 @@ public sealed class QuoteService : IQuoteService, IEService return toReturn; } - public IEnumerable GetForGuild(ulong guildId) + public async Task> GetGuildQuotesAsync(ulong guildId) { - using var uow = _db.GetDbContext(); - var quotes = uow.GetTable() - .Where(x => x.GuildId == guildId); + await using var uow = _db.GetDbContext(); + var quotes = await uow.GetTable() + .Where(x => x.GuildId == guildId) + .ToListAsyncLinqToDB(); return quotes; } @@ -124,7 +127,96 @@ public sealed class QuoteService : IQuoteService, IEService var quote = await uow.GetTable() .Where(x => x.Id == quoteId && x.GuildId == guildId) - .FirstAsyncLinqToDB(); + .FirstOrDefaultAsyncLinqToDB(); + return quote; } + + public async Task AddQuoteAsync( + ulong guildId, + ulong authorId, + string authorName, + string keyword, + string text) + { + keyword = keyword.ToUpperInvariant(); + + Quote q; + await using var uow = _db.GetDbContext(); + uow.Set() + .Add(q = new() + { + AuthorId = authorId, + AuthorName = authorName, + GuildId = guildId, + Keyword = keyword, + Text = text + }); + await uow.SaveChangesAsync(); + + return q; + } + + public async Task EditQuoteAsync(ulong authorId, int quoteId, string text) + { + await using var uow = _db.GetDbContext(); + var result = await uow.GetTable() + .Where(x => x.Id == quoteId && x.AuthorId == authorId) + .Set(x => x.Text, text) + .UpdateWithOutputAsync((del, ins) => ins); + + var q = result.FirstOrDefault(); + return q; + } + + public async Task DeleteQuoteAsync( + ulong guildId, + ulong authorId, + bool isQuoteManager, + int quoteId) + { + await using var uow = _db.GetDbContext(); + var q = uow.Set().GetById(quoteId); + + + var count = await uow.GetTable() + .Where(x => x.GuildId == guildId && x.Id == quoteId) + .Where(x => isQuoteManager || (x.AuthorId == authorId)) + .DeleteAsync(); + + + return count > 0; + } + + public async Task ImportQuotesAsync(ulong guildId, string input) + { + Dictionary> data; + try + { + data = Yaml.Deserializer.Deserialize>>(input); + } + catch (Exception ex) + { + Log.Warning(ex, "Quote import failed: {Message}", ex.Message); + return false; + } + + + var toImport = data.SelectMany(x => x.Value.Select(v => (Key: x.Key, Value: v))) + .Where(x => !string.IsNullOrWhiteSpace(x.Key) && !string.IsNullOrWhiteSpace(x.Value?.Txt)); + + await using var uow = _db.GetDbContext(); + await uow.GetTable() + .BulkCopyAsync(toImport + .Select(q => new Quote + { + GuildId = guildId, + Keyword = q.Key, + Text = q.Value.Txt, + AuthorId = q.Value.Aid, + AuthorName = q.Value.An + })); + + return true; + } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Utility/Quote/_common/ExportedQuote.cs b/src/EllieBot/Modules/Utility/Quote/_common/ExportedQuote.cs new file mode 100644 index 0000000..c0ee68d --- /dev/null +++ b/src/EllieBot/Modules/Utility/Quote/_common/ExportedQuote.cs @@ -0,0 +1,20 @@ +using EllieBot.Db.Models; + +namespace EllieBot.Modules.Utility; + +public class ExportedQuote +{ + public required string Id { get; init; } + public required string An { get; init; } + public required ulong Aid { get; init; } + public required string Txt { get; init; } + + public static ExportedQuote FromModel(Quote quote) + => new() + { + Id = ((kwum)quote.Id).ToString(), + An = quote.AuthorName, + Aid = quote.AuthorId, + Txt = quote.Text + }; +} \ No newline at end of file diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index b6f5924..af897a8 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -343,28 +343,57 @@ allcmdcooldowns: - cmdcds quoteadd: - quoteadd + - qa + - qadd + - quadd - . quoteedit: - quoteedit + - qe + - que - qedit quoteprint: - quoteprint + - qp + - qup - .. + - qprint quoteshow: - quoteshow + - qsh - qshow + - qushow quotesearch: - quotesearch + - qs + - qse - qsearch quoteid: - quoteid - qid quotedelete: - quotedelete + - qd - qdel + - qdelete quotedeleteauthor: - quotedeleteauthor + - qda - qdelauth +quotesexport: + - quotesexport + - qex + - qexport +quotesimport: + - quotesimport + - qim + - qimp + - qimport +quotelist: + - quotelist + - qli + - quli + - qulist draw: - draw drawnew: @@ -761,9 +790,6 @@ autotranslate: - autotranslate - at - autotrans -listquotes: - - listquotes - - liqu typedel: - typedel typelist: @@ -1210,12 +1236,6 @@ linkonlychannel: - linkssonly coordreload: - coordreload -quotesexport: - - quotesexport - - qexport -quotesimport: - - quotesimport - - qimport showembed: - showembed # EllieExpressions diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index 3f96439..f82206d 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -2502,7 +2502,7 @@ autotranslate: params: - autoDelete: desc: "The option to automatically remove translated messages from the chat." -listquotes: +quotelist: desc: Lists all quotes on the server ordered alphabetically or by ID. 15 Per page. ex: - 3