diff --git a/src/NadekoBot/Bot.cs b/src/NadekoBot/Bot.cs index 347dbb000..f3820b616 100644 --- a/src/NadekoBot/Bot.cs +++ b/src/NadekoBot/Bot.cs @@ -3,14 +3,12 @@ using Microsoft.Extensions.DependencyInjection; using NadekoBot.Common.Configs; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; -using NadekoBot.Modules.Administration; using NadekoBot.Modules.Utility; using NadekoBot.Services.Database.Models; using System.Collections.Immutable; using System.Diagnostics; using System.Net; using System.Reflection; -using Nadeko.Common; using RunMode = Discord.Commands.RunMode; namespace NadekoBot; diff --git a/src/NadekoBot/Modules/Utility/Quote/IQuoteService.cs b/src/NadekoBot/Modules/Utility/Quote/IQuoteService.cs new file mode 100644 index 000000000..952caa9bb --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Quote/IQuoteService.cs @@ -0,0 +1,6 @@ +namespace NadekoBot.Modules.Utility; + +public interface IQuoteService +{ + Task DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Quote/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Quote/QuoteCommands.cs index 8b781040a..faabf1691 100644 --- a/src/NadekoBot/Modules/Utility/Quote/QuoteCommands.cs +++ b/src/NadekoBot/Modules/Utility/Quote/QuoteCommands.cs @@ -1,5 +1,4 @@ -#nullable disable warnings -using Nadeko.Common; +#nullable disable warnings using NadekoBot.Common.Yml; using NadekoBot.Db; using NadekoBot.Services.Database.Models; @@ -22,23 +21,25 @@ public partial class Utility "; private static readonly ISerializer _exportSerializer = new SerializerBuilder() - .WithEventEmitter(args - => new MultilineScalarFlowStyleEmitter(args)) - .WithNamingConvention( - CamelCaseNamingConvention.Instance) - .WithIndentedSequences() - .ConfigureDefaultValuesHandling(DefaultValuesHandling - .OmitDefaults) - .DisableAliases() - .Build(); + .WithEventEmitter(args + => new MultilineScalarFlowStyleEmitter(args)) + .WithNamingConvention( + CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .ConfigureDefaultValuesHandling(DefaultValuesHandling + .OmitDefaults) + .DisableAliases() + .Build(); private readonly DbService _db; private readonly IHttpClientFactory _http; + private readonly IQuoteService _qs; - public QuoteCommands(DbService db, IHttpClientFactory http) + public QuoteCommands(DbService db, IQuoteService qs, IHttpClientFactory http) { _db = db; _http = http; + _qs = qs; } [Cmd] @@ -108,7 +109,7 @@ public partial class Utility [RequireContext(ContextType.Guild)] public async Task QuoteShow(int id) { - Quote quote; + Quote? quote; await using (var uow = _db.GetDbContext()) { quote = uow.Quotes.GetById(id); @@ -127,13 +128,13 @@ public partial class Utility private async Task ShowQuoteData(Quote data) => await ctx.Channel.EmbedAsync(_eb.Create(ctx) - .WithOkColor() - .WithTitle(GetText(strs.quote_id($"#{data.Id}"))) - .AddField(GetText(strs.trigger), data.Keyword) - .AddField(GetText(strs.response), - Format.Sanitize(data.Text).Replace("](", "]\\(")) - .WithFooter( - GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))); + .WithOkColor() + .WithTitle(GetText(strs.quote_id($"#{data.Id}"))) + .AddField(GetText(strs.trigger), data.Keyword) + .AddField(GetText(strs.response), + Format.Sanitize(data.Text).Replace("](", "]\\(")) + .WithFooter( + GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))); private async Task QuoteSearchinternalAsync(string? keyword, string textOrAuthor) { @@ -256,6 +257,28 @@ public partial class Utility await SendErrorAsync(response); } + [Cmd] + [RequireContext(ContextType.Guild)] + public Task QuoteDeleteAuthor(IUser user) + => QuoteDeleteAuthor(user.Id); + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task QuoteDeleteAuthor(ulong userId) + { + var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages; + + if (userId == ctx.User.Id || hasManageMessages) + { + var deleted = await _qs.DeleteAllAuthorQuotesAsync(ctx.Guild.Id, ctx.User.Id); + await ReplyConfirmLocalizedAsync(strs.quotes_deleted_count(deleted)); + } + else + { + await ReplyErrorLocalizedAsync(strs.insuf_perms_u); + } + } + [Cmd] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] @@ -288,7 +311,7 @@ public partial class Utility } 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)); var text = PREPEND_EXPORT + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints(); @@ -303,7 +326,7 @@ public partial class Utility #if GLOBAL_NADEKO [OwnerOnly] #endif - public async Task QuotesImport([Leftover] string input = null) + public async Task QuotesImport([Leftover] string? input = null) { input = input?.Trim(); @@ -357,14 +380,14 @@ public partial class Utility { var keyword = entry.Key; await uow.Quotes.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 - })); + .Select(quote => new Quote + { + GuildId = guildId, + Keyword = keyword, + Text = quote.Txt, + AuthorId = quote.Aid, + AuthorName = quote.An + })); } await uow.SaveChangesAsync(); diff --git a/src/NadekoBot/Modules/Utility/Quote/QuoteService.cs b/src/NadekoBot/Modules/Utility/Quote/QuoteService.cs new file mode 100644 index 000000000..7e247d7c2 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Quote/QuoteService.cs @@ -0,0 +1,33 @@ +#nullable disable warnings +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using Nadeko.Common; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Utility; + +public sealed class QuoteService : IQuoteService, INService +{ + private readonly DbService _db; + + public QuoteService(DbService db) + { + _db = db; + } + + /// + /// Delete all quotes created by the author in a guild + /// + /// ID of the guild + /// ID of the user + /// Number of deleted qutoes + public async Task DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId) + { + await using var ctx = _db.GetDbContext(); + var deleted = await ctx.GetTable() + .Where(x => x.GuildId == guildId && x.AuthorId == userId) + .DeleteAsync(); + + return deleted; + } +} \ No newline at end of file diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index a9c339d8e..b08dfa4c9 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -342,6 +342,9 @@ quoteid: quotedelete: - quotedelete - qdel +quotedeleteauthor: + - quotedeleteauthor + - qdelauth draw: - draw drawnew: diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index 4dba72a7b..cadf7b644 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -628,6 +628,10 @@ quotedelete: desc: "Deletes a quote with the specified ID. You have to either have the Manage Messages permission or be the creator of the quote to delete it." args: - "123456" +quotedeleteauthor: + desc: "Deletes all quotes by the specified author. If the author is not you, then ManageMessage server permission is required." + args: + - "@QuoteSpammer" 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." args: diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 3d89d8532..2a42ae7bd 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -595,6 +595,7 @@ "presence": "Presence", "presence_txt": "{0} Servers\n{1} Text Channels\n{2} Voice Channels", "quotes_deleted": "Deleted all quotes with {0} keyword.", + "quotes_deleted_count": "Deleted {0} quotes.", "quotes_page": "Page {0} of quotes", "quotes_page_none": "No quotes found on that page.", "quotes_remove_none": "No quotes found which you can remove.",