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
This commit is contained in:
Toastie 2024-08-21 20:22:31 +12:00
parent 2e541eebac
commit 3a25433ec8
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
6 changed files with 222 additions and 146 deletions

View file

@ -1,6 +1,50 @@
namespace EllieBot.Modules.Utility; using EllieBot.Db.Models;
namespace EllieBot.Modules.Utility;
public interface IQuoteService public interface IQuoteService
{ {
/// <summary>
/// Delete all quotes created by the author in a guild
/// </summary>
/// <param name="guildId">ID of the guild</param>
/// <param name="userId">ID of the user</param>
/// <returns>Number of deleted qutoes</returns>
Task<int> DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId); Task<int> DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId);
/// <summary>
/// Delete all quotes in a guild
/// </summary>
/// <param name="guildId">ID of the guild</param>
/// <returns>Number of deleted qutoes</returns>
Task<int> DeleteAllQuotesAsync(ulong guildId);
Task<IReadOnlyCollection<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order);
Task<Quote?> GetQuoteByKeywordAsync(ulong guildId, string keyword);
Task<IReadOnlyCollection<Quote>> SearchQuoteKeywordTextAsync(
ulong guildId,
string? keyword,
string text);
Task<IReadOnlyCollection<Quote>> GetGuildQuotesAsync(ulong guildId);
Task<int> RemoveAllByKeyword(ulong guildId, string keyword);
Task<Quote?> GetQuoteByIdAsync(ulong guildId, kwum quoteId);
Task<Quote> AddQuoteAsync(
ulong guildId,
ulong authorId,
string authorName,
string keyword,
string text);
Task<Quote?> EditQuoteAsync(ulong authorId, int quoteId, string text);
Task<bool> DeleteQuoteAsync(
ulong guildId,
ulong authorId,
bool isQuoteManager,
int quoteId);
Task<bool> ImportQuotesAsync(ulong guildId, string input);
} }

View file

@ -1,6 +1,4 @@
#nullable disable warnings #nullable disable warnings
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.Yml; using EllieBot.Common.Yml;
using EllieBot.Db.Models; using EllieBot.Db.Models;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
@ -11,7 +9,7 @@ namespace EllieBot.Modules.Utility;
public partial class Utility public partial class Utility
{ {
[Group] [Group]
public partial class QuoteCommands : EllieModule<QuoteService> public partial class QuoteCommands : EllieModule
{ {
private const string PREPEND_EXPORT = private const string PREPEND_EXPORT =
""" """
@ -48,19 +46,19 @@ public partial class Utility
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[Priority(1)] [Priority(1)]
public Task ListQuotes(OrderType order = OrderType.Keyword) public Task QuoteList(OrderType order = OrderType.Keyword)
=> ListQuotes(1, order); => QuoteList(1, order);
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[Priority(0)] [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; page -= 1;
if (page < 0) if (page < 0)
return; 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) if (quotes.Count == 0)
{ {
@ -85,7 +83,7 @@ public partial class Utility
keyword = keyword.ToUpperInvariant(); 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) if (quote is null)
return; return;
@ -97,7 +95,6 @@ public partial class Utility
await Response() await Response()
.Text($"`{new kwum(quote.Id)}` 📣 " + text) .Text($"`{new kwum(quote.Id)}` 📣 " + text)
.Sanitize()
.SendAsync(); .SendAsync();
} }
@ -106,7 +103,7 @@ public partial class Utility
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task QuoteShow(kwum quoteId) 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) if (quote is null)
{ {
@ -179,7 +176,7 @@ public partial class Utility
keyword = keyword?.ToUpperInvariant(); 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() await Response()
.Paginated() .Paginated()
@ -218,7 +215,7 @@ public partial class Utility
if (quoteId < 0) if (quoteId < 0)
return; return;
var quote = await _service.GetQuoteByIdAsync(ctx.Guild.Id, quoteId); var quote = await _qs.GetQuoteByIdAsync(ctx.Guild.Id, quoteId);
if (quote is null) if (quote is null)
{ {
@ -226,8 +223,8 @@ public partial class Utility
return; return;
} }
var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName.SanitizeAllMentions()}* 🗯️ " var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName}* 🗯️ "
+ quote.Keyword.ToLowerInvariant().SanitizeAllMentions() + quote.Keyword.ToLowerInvariant()
+ ":\n"; + ":\n";
@ -236,7 +233,6 @@ public partial class Utility
text = await repSvc.ReplaceAsync(text, repCtx); text = await repSvc.ReplaceAsync(text, repCtx);
await Response() await Response()
.Text(infoText + text) .Text(infoText + text)
.Sanitize()
.SendAsync(); .SendAsync();
} }
@ -248,26 +244,14 @@ public partial class Utility
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
return; return;
keyword = keyword.ToUpperInvariant(); var quote = await _qs.AddQuoteAsync(ctx.Guild.Id, ctx.User.Id, ctx.User.Username, keyword, text);
Quote q; await Response()
await using (var uow = _db.GetDbContext()) .Confirm(strs.quote_added_new(Format.Code(new kwum(quote.Id).ToString())))
{ .SendAsync();
uow.Set<Quote>()
.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();
} }
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task QuoteEdit(kwum quoteId, [Leftover] string text) public async Task QuoteEdit(kwum quoteId, [Leftover] string text)
@ -277,19 +261,7 @@ public partial class Utility
return; return;
} }
Quote q; var q = await _qs.EditQuoteAsync(ctx.User.Id, quoteId, text);
await using (var uow = _db.GetDbContext())
{
var intId = (int)quoteId;
var result = await uow.GetTable<Quote>()
.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();
}
if (q is not null) if (q is not null)
{ {
@ -309,33 +281,19 @@ public partial class Utility
} }
} }
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task QuoteDelete(kwum quoteId) public async Task QuoteDelete(kwum quoteId)
{ {
var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages; var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages;
var success = false; var success = await _qs.DeleteQuoteAsync(ctx.Guild.Id, ctx.User.Id, hasManageMessages, quoteId);
string response;
await using (var uow = _db.GetDbContext())
{
var q = uow.Set<Quote>().GetById(quoteId);
if (q?.GuildId != ctx.Guild.Id || (!hasManageMessages && q.AuthorId != ctx.Message.Author.Id))
response = GetText(strs.quotes_remove_none);
else
{
uow.Set<Quote>().Remove(q);
await uow.SaveChangesAsync();
success = true;
response = GetText(strs.quote_deleted(new kwum(quoteId)));
}
}
if (success) if (success)
await Response().Confirm(response).SendAsync(); await Response().Confirm(strs.quote_deleted(quoteId)).SendAsync();
else else
await Response().Error(response).SendAsync(); await Response().Error(strs.quotes_remove_none).SendAsync();
} }
[Cmd] [Cmd]
@ -368,16 +326,9 @@ public partial class Utility
if (string.IsNullOrWhiteSpace(keyword)) if (string.IsNullOrWhiteSpace(keyword))
return; return;
keyword = keyword.ToUpperInvariant(); await _qs.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant());
await using (var uow = _db.GetDbContext()) await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword))).SendAsync();
{
await _service.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant());
await uow.SaveChangesAsync();
}
await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions()))).SendAsync();
} }
[Cmd] [Cmd]
@ -385,7 +336,7 @@ public partial class Utility
[UserPerm(GuildPerm.Administrator)] [UserPerm(GuildPerm.Administrator)]
public async Task QuotesExport() 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) var exprsDict = quotes.GroupBy(x => x.Keyword)
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel)); .ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
@ -400,7 +351,7 @@ public partial class Utility
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)] [UserPerm(GuildPerm.Administrator)]
[Ratelimit(300)] [Ratelimit(300)]
#if GLOBAL_ELLIE #if GLOBAL_NADEKO
[OwnerOnly] [OwnerOnly]
#endif #endif
public async Task QuotesImport([Leftover] string? input = null) 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) if (!succ)
{ {
await Response().Error(strs.expr_import_invalid_data).SendAsync(); await Response().Error(strs.expr_import_invalid_data).SendAsync();
@ -437,56 +388,5 @@ public partial class Utility
await ctx.OkAsync(); await ctx.OkAsync();
} }
private async Task<bool> ImportExprsAsync(ulong guildId, string input)
{
Dictionary<string, List<ExportedQuote>> data;
try
{
data = Yaml.Deserializer.Deserialize<Dictionary<string, List<ExportedQuote>>>(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<Quote>()
.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
};
}
} }
} }

View file

@ -1,6 +1,8 @@
#nullable disable warnings #nullable disable warnings
using LinqToDB; using LinqToDB;
using LinqToDB.Data;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.Yml;
using EllieBot.Db.Models; using EllieBot.Db.Models;
namespace EllieBot.Modules.Utility; namespace EllieBot.Modules.Utility;
@ -45,19 +47,19 @@ public sealed class QuoteService : IQuoteService, IEService
return deleted; return deleted;
} }
public async Task<IReadOnlyList<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order) public async Task<IReadOnlyCollection<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order)
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var q = uow.Set<Quote>() var q = uow.Set<Quote>()
.ToLinqToDBTable() .ToLinqToDBTable()
.Where(x => x.GuildId == guildId); .Where(x => x.GuildId == guildId);
if (order == OrderType.Keyword) if (order == OrderType.Keyword)
q = q.OrderBy(x => x.Keyword); q = q.OrderBy(x => x.Keyword);
else else
q = q.OrderBy(x => x.Id); 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<Quote?> GetQuoteByKeywordAsync(ulong guildId, string keyword) public async Task<Quote?> GetQuoteByKeywordAsync(ulong guildId, string keyword)
@ -97,11 +99,12 @@ public sealed class QuoteService : IQuoteService, IEService
return toReturn; return toReturn;
} }
public IEnumerable<Quote> GetForGuild(ulong guildId) public async Task<IReadOnlyCollection<Quote>> GetGuildQuotesAsync(ulong guildId)
{ {
using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var quotes = uow.GetTable<Quote>() var quotes = await uow.GetTable<Quote>()
.Where(x => x.GuildId == guildId); .Where(x => x.GuildId == guildId)
.ToListAsyncLinqToDB();
return quotes; return quotes;
} }
@ -124,7 +127,96 @@ public sealed class QuoteService : IQuoteService, IEService
var quote = await uow.GetTable<Quote>() var quote = await uow.GetTable<Quote>()
.Where(x => x.Id == quoteId && x.GuildId == guildId) .Where(x => x.Id == quoteId && x.GuildId == guildId)
.FirstAsyncLinqToDB(); .FirstOrDefaultAsyncLinqToDB();
return quote; return quote;
} }
public async Task<Quote> AddQuoteAsync(
ulong guildId,
ulong authorId,
string authorName,
string keyword,
string text)
{
keyword = keyword.ToUpperInvariant();
Quote q;
await using var uow = _db.GetDbContext();
uow.Set<Quote>()
.Add(q = new()
{
AuthorId = authorId,
AuthorName = authorName,
GuildId = guildId,
Keyword = keyword,
Text = text
});
await uow.SaveChangesAsync();
return q;
}
public async Task<Quote?> EditQuoteAsync(ulong authorId, int quoteId, string text)
{
await using var uow = _db.GetDbContext();
var result = await uow.GetTable<Quote>()
.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<bool> DeleteQuoteAsync(
ulong guildId,
ulong authorId,
bool isQuoteManager,
int quoteId)
{
await using var uow = _db.GetDbContext();
var q = uow.Set<Quote>().GetById(quoteId);
var count = await uow.GetTable<Quote>()
.Where(x => x.GuildId == guildId && x.Id == quoteId)
.Where(x => isQuoteManager || (x.AuthorId == authorId))
.DeleteAsync();
return count > 0;
}
public async Task<bool> ImportQuotesAsync(ulong guildId, string input)
{
Dictionary<string?, List<ExportedQuote?>> data;
try
{
data = Yaml.Deserializer.Deserialize<Dictionary<string?, List<ExportedQuote?>>>(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<Quote>()
.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;
}
} }

View file

@ -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
};
}

View file

@ -343,28 +343,57 @@ allcmdcooldowns:
- cmdcds - cmdcds
quoteadd: quoteadd:
- quoteadd - quoteadd
- qa
- qadd
- quadd
- . - .
quoteedit: quoteedit:
- quoteedit - quoteedit
- qe
- que
- qedit - qedit
quoteprint: quoteprint:
- quoteprint - quoteprint
- qp
- qup
- .. - ..
- qprint
quoteshow: quoteshow:
- quoteshow - quoteshow
- qsh
- qshow - qshow
- qushow
quotesearch: quotesearch:
- quotesearch - quotesearch
- qs
- qse
- qsearch - qsearch
quoteid: quoteid:
- quoteid - quoteid
- qid - qid
quotedelete: quotedelete:
- quotedelete - quotedelete
- qd
- qdel - qdel
- qdelete
quotedeleteauthor: quotedeleteauthor:
- quotedeleteauthor - quotedeleteauthor
- qda
- qdelauth - qdelauth
quotesexport:
- quotesexport
- qex
- qexport
quotesimport:
- quotesimport
- qim
- qimp
- qimport
quotelist:
- quotelist
- qli
- quli
- qulist
draw: draw:
- draw - draw
drawnew: drawnew:
@ -761,9 +790,6 @@ autotranslate:
- autotranslate - autotranslate
- at - at
- autotrans - autotrans
listquotes:
- listquotes
- liqu
typedel: typedel:
- typedel - typedel
typelist: typelist:
@ -1210,12 +1236,6 @@ linkonlychannel:
- linkssonly - linkssonly
coordreload: coordreload:
- coordreload - coordreload
quotesexport:
- quotesexport
- qexport
quotesimport:
- quotesimport
- qimport
showembed: showembed:
- showembed - showembed
# EllieExpressions # EllieExpressions

View file

@ -2502,7 +2502,7 @@ autotranslate:
params: params:
- autoDelete: - autoDelete:
desc: "The option to automatically remove translated messages from the chat." 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. desc: Lists all quotes on the server ordered alphabetically or by ID. 15 Per page.
ex: ex:
- 3 - 3