diff --git a/src/EllieBot/Modules/Administration/UserPunish/UserPunishCommands.cs b/src/EllieBot/Modules/Administration/UserPunish/UserPunishCommands.cs index 27a3f12..3c43b1f 100644 --- a/src/EllieBot/Modules/Administration/UserPunish/UserPunishCommands.cs +++ b/src/EllieBot/Modules/Administration/UserPunish/UserPunishCommands.cs @@ -273,6 +273,31 @@ public partial class Administration .SendAsync(); } + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.Administrator)] + public Task WarnDelete(IGuildUser user, int index) + => WarnDelete(user.Id, index); + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPermission.Administrator)] + public async Task WarnDelete(ulong userId, int index) + { + if (--index < 0) + return; + + var warn = await _service.WarnDelete(userId, index); + + if (warn is null) + { + await Response().Error(strs.warning_not_found).SendAsync(); + return; + } + + await Response().Confirm(strs.warning_deleted(Format.Bold(index.ToString()))).SendAsync(); + } + [Cmd] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.BanMembers)] @@ -286,6 +311,7 @@ public partial class Administration { if (index < 0) return; + var success = await _service.WarnClearAsync(ctx.Guild.Id, userId, index, ctx.User.ToString()); var userStr = Format.Bold((ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString()); if (index == 0) diff --git a/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs index cdc9900..9ab0b73 100644 --- a/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -89,9 +89,10 @@ public class UserPunishService : IEService, IReadyExecutor { ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments; - previousCount = uow.Set().ForId(guildId, userId) - .Where(w => !w.Forgiven && w.UserId == userId) - .Sum(x => x.Weight); + previousCount = uow.Set() + .ForId(guildId, userId) + .Where(w => !w.Forgiven && w.UserId == userId) + .Sum(x => x.Weight); uow.Set().Add(warn); @@ -103,7 +104,7 @@ public class UserPunishService : IEService, IReadyExecutor var totalCount = previousCount + weight; var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount) - .MaxBy(x => x.Count); + .MaxBy(x => x.Count); if (p is not null) { @@ -244,33 +245,33 @@ public class UserPunishService : IEService, IReadyExecutor { await using var uow = _db.GetDbContext(); var cleared = await uow.Set() - .Where(x => uow.Set() - .Any(y => y.GuildId == x.GuildId - && y.WarnExpireHours > 0 - && y.WarnExpireAction == WarnExpireAction.Clear) - && x.Forgiven == false - && x.DateAdded - < DateTime.UtcNow.AddHours(-uow.Set() - .Where(y => x.GuildId == y.GuildId) - .Select(y => y.WarnExpireHours) - .First())) - .UpdateAsync(_ => new() - { - Forgiven = true, - ForgivenBy = "expiry" - }); + .Where(x => uow.Set() + .Any(y => y.GuildId == x.GuildId + && y.WarnExpireHours > 0 + && y.WarnExpireAction == WarnExpireAction.Clear) + && x.Forgiven == false + && x.DateAdded + < DateTime.UtcNow.AddHours(-uow.Set() + .Where(y => x.GuildId == y.GuildId) + .Select(y => y.WarnExpireHours) + .First())) + .UpdateAsync(_ => new() + { + Forgiven = true, + ForgivenBy = "expiry" + }); var deleted = await uow.Set() - .Where(x => uow.Set() - .Any(y => y.GuildId == x.GuildId - && y.WarnExpireHours > 0 - && y.WarnExpireAction == WarnExpireAction.Delete) - && x.DateAdded - < DateTime.UtcNow.AddHours(-uow.Set() - .Where(y => x.GuildId == y.GuildId) - .Select(y => y.WarnExpireHours) - .First())) - .DeleteAsync(); + .Where(x => uow.Set() + .Any(y => y.GuildId == x.GuildId + && y.WarnExpireHours > 0 + && y.WarnExpireAction == WarnExpireAction.Delete) + && x.DateAdded + < DateTime.UtcNow.AddHours(-uow.Set() + .Where(y => x.GuildId == y.GuildId) + .Select(y => y.WarnExpireHours) + .First())) + .DeleteAsync(); if (cleared > 0 || deleted > 0) { @@ -293,21 +294,21 @@ public class UserPunishService : IEService, IReadyExecutor if (config.WarnExpireAction == WarnExpireAction.Clear) { await uow.Set() - .Where(x => x.GuildId == guildId - && x.Forgiven == false - && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours)) - .UpdateAsync(_ => new() - { - Forgiven = true, - ForgivenBy = "expiry" - }); + .Where(x => x.GuildId == guildId + && x.Forgiven == false + && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours)) + .UpdateAsync(_ => new() + { + Forgiven = true, + ForgivenBy = "expiry" + }); } else if (config.WarnExpireAction == WarnExpireAction.Delete) { await uow.Set() - .Where(x => x.GuildId == guildId - && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours)) - .DeleteAsync(); + .Where(x => x.GuildId == guildId + && x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours)) + .DeleteAsync(); } await uow.SaveChangesAsync(); @@ -425,8 +426,8 @@ public class UserPunishService : IEService, IReadyExecutor { using var uow = _db.GetDbContext(); return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments)) - .WarnPunishments.OrderBy(x => x.Count) - .ToArray(); + .WarnPunishments.OrderBy(x => x.Count) + .ToArray(); } public (IReadOnlyCollection<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill( @@ -436,20 +437,20 @@ public class UserPunishService : IEService, IReadyExecutor var gusers = guild.Users; //get user objects and reasons var bans = people.Split("\n") - .Select(x => - { - var split = x.Trim().Split(" "); + .Select(x => + { + var split = x.Trim().Split(" "); - var reason = string.Join(" ", split.Skip(1)); + var reason = string.Join(" ", split.Skip(1)); - if (ulong.TryParse(split[0], out var id)) - return (Original: split[0], Id: id, Reason: reason); + if (ulong.TryParse(split[0], out var id)) + return (Original: split[0], Id: id, Reason: reason); - return (Original: split[0], - gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id, - Reason: reason); - }) - .ToArray(); + return (Original: split[0], + gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id, + Reason: reason); + }) + .ToArray(); //if user is null, means that person couldn't be found var missing = bans.Count(x => !x.Id.HasValue); @@ -483,11 +484,12 @@ public class UserPunishService : IEService, IReadyExecutor } else if (template is null) { - uow.Set().Add(new() - { - GuildId = guildId, - Text = text - }); + uow.Set() + .Add(new() + { + GuildId = guildId, + Text = text + }); } else template.Text = text; @@ -499,31 +501,31 @@ public class UserPunishService : IEService, IReadyExecutor { await using var ctx = _db.GetDbContext(); await ctx.Set() - .ToLinqToDBTable() - .InsertOrUpdateAsync(() => new() - { - GuildId = guildId, - Text = null, - DateAdded = DateTime.UtcNow, - PruneDays = pruneDays - }, - old => new() - { - PruneDays = pruneDays - }, - () => new() - { - GuildId = guildId - }); + .ToLinqToDBTable() + .InsertOrUpdateAsync(() => new() + { + GuildId = guildId, + Text = null, + DateAdded = DateTime.UtcNow, + PruneDays = pruneDays + }, + old => new() + { + PruneDays = pruneDays + }, + () => new() + { + GuildId = guildId + }); } public async Task GetBanPruneAsync(ulong guildId) { await using var ctx = _db.GetDbContext(); return await ctx.Set() - .Where(x => x.GuildId == guildId) - .Select(x => x.PruneDays) - .FirstOrDefaultAsyncLinqToDB(); + .Where(x => x.GuildId == guildId) + .Select(x => x.PruneDays) + .FirstOrDefaultAsyncLinqToDB(); } public Task GetBanUserDmEmbed( @@ -554,18 +556,18 @@ public class UserPunishService : IEService, IReadyExecutor banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason; var repCtx = new ReplacementContext(client, guild) - .WithOverride("%ban.mod%", () => moderator.ToString()) - .WithOverride("%ban.mod.fullname%", () => moderator.ToString()) - .WithOverride("%ban.mod.name%", () => moderator.Username) - .WithOverride("%ban.mod.discrim%", () => moderator.Discriminator) - .WithOverride("%ban.user%", () => target.ToString()) - .WithOverride("%ban.user.fullname%", () => target.ToString()) - .WithOverride("%ban.user.name%", () => target.Username) - .WithOverride("%ban.user.discrim%", () => target.Discriminator) - .WithOverride("%reason%", () => banReason) - .WithOverride("%ban.reason%", () => banReason) - .WithOverride("%ban.duration%", - () => duration?.ToString(@"d\.hh\:mm") ?? "perma"); + .WithOverride("%ban.mod%", () => moderator.ToString()) + .WithOverride("%ban.mod.fullname%", () => moderator.ToString()) + .WithOverride("%ban.mod.name%", () => moderator.Username) + .WithOverride("%ban.mod.discrim%", () => moderator.Discriminator) + .WithOverride("%ban.user%", () => target.ToString()) + .WithOverride("%ban.user.fullname%", () => target.ToString()) + .WithOverride("%ban.user.name%", () => target.Username) + .WithOverride("%ban.user.discrim%", () => target.Discriminator) + .WithOverride("%reason%", () => banReason) + .WithOverride("%ban.reason%", () => banReason) + .WithOverride("%ban.duration%", + () => duration?.ToString(@"d\.hh\:mm") ?? "perma"); // if template isn't set, use the old message style @@ -594,4 +596,24 @@ public class UserPunishService : IEService, IReadyExecutor var output = SmartText.CreateFrom(template); return await _repSvc.ReplaceAsync(output, repCtx); } + + public async Task WarnDelete(ulong userId, int index) + { + await using var uow = _db.GetDbContext(); + + var warn = await uow.GetTable() + .Where(x => x.UserId == userId) + .OrderByDescending(x => x.DateAdded) + .Skip(index) + .FirstOrDefaultAsyncLinqToDB(); + + if (warn is not null) + { + await uow.GetTable() + .Where(x => x.Id == warn.Id) + .DeleteAsync(); + } + + return warn; + } } \ No newline at end of file diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index e8c0b4c..8e9ff14 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -947,6 +947,10 @@ warnexpire: warnclear: - warnclear - warnc +warndelete: + - warndelete + - warnrm + - warnd warnpunishlist: - warnpunishlist - warnpl diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index f7a93b1..f5d5a6d 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -576,7 +576,7 @@ deleterole: - Awesome Role params: - role: - desc: "The role being deleted, as identified by its unique identifier." + desc: "The role being deleted, as identified by its id." rolecolor: desc: Set a role's color using its hex value. Provide no color in order to see the hex value of the color of the specified role. The role you specify has to be lower in the role hierarchy than your highest role. ex: @@ -605,11 +605,11 @@ ban: - time: desc: "The duration of the temporary ban." userId: - desc: "The unique identifier of the user being banned." + desc: "The id of the user being banned." msg: desc: "The reason for the ban is provided in this message." - userId: - desc: "The unique identifier of the user being banned." + desc: "The id of the user being banned." msg: desc: "The reason for the ban is provided in this message." - user: @@ -627,7 +627,7 @@ softban: msg: desc: "The reason for the ban is described in this string." - userId: - desc: "The unique identifier for the user being banned and then unbanned." + desc: "The id of the user being banned and then unbanned." msg: desc: "The reason for the ban is described in this string." kick: @@ -1203,7 +1203,7 @@ userblacklist: - action: desc: "The type of operation to perform on the user, either adding or removing them from the blacklist." id: - desc: "The unique identifier of the user to be added, removed, or listed." + desc: "The id of the user to be added, removed, or listed." - action: desc: "The type of operation to perform on the user, either adding or removing them from the blacklist." usr: @@ -1223,7 +1223,7 @@ channelblacklist: - action: desc: "The type of operation to perform on the channel, either adding it to the blacklist or removing it from it." id: - desc: "The unique identifier of the channel being added, removed, or listed." + desc: "The id of the channel being added, removed, or listed." serverblacklist: desc: |- Either [add]s or [rem]oves a server, or servers specified by an ID from a blacklist. @@ -1239,7 +1239,7 @@ serverblacklist: - action: desc: "The type of operation to perform on the server(s). It can be either adding or removing them from the blacklist." id: - desc: "The unique identifier of the server being added, removed, or listed." + desc: "The id of the server being added, removed, or listed." - action: desc: "The type of operation to perform on the server(s). It can be either adding or removing them from the blacklist." guild: @@ -1296,7 +1296,7 @@ quoteshow: - 123 params: - quoteId: - desc: "The unique identifier for the quote being queried." + desc: "The id of the quote being queried." quotesearch: desc: 'Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author' ex: @@ -1317,14 +1317,14 @@ quoteid: - 123456 params: - quoteId: - desc: "The unique identifier for the quote to be displayed." + desc: "The id of the quote to be displayed." 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. ex: - 123456 params: - quoteId: - desc: "The unique identifier for the quote being deleted." + desc: "The id of the quote being deleted." quotedeleteauthor: desc: Deletes all quotes by the specified author. If the author is not you, then ManageMessage server permission is required. ex: @@ -1822,7 +1822,7 @@ load: - 5 params: - id: - desc: "The unique identifier of the playlist to be loaded." + desc: "The id of the playlist to be loaded." playlists: desc: Lists all playlists. Paginated, 20 per page. ex: @@ -1836,7 +1836,7 @@ playlistshow: - 1 params: - id: - desc: "The unique identifier for the playlist to retrieve songs from." + desc: "The id of the playlist to retrieve songs from." page: desc: "The current page number for the pagination." deleteplaylist: @@ -2232,7 +2232,7 @@ currencytransaction: - 3yvd params: - id: - desc: "The unique identifier for the transaction being queried." + desc: "The id of the transaction being queried." listperms: desc: Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. ex: @@ -2721,6 +2721,7 @@ slot: You can specify 'all', 'half' or 'X%' instead of the amount to bet that part of your current balance. ex: - 5 + - 'all' params: - amount: desc: "The amount of currency to bet." @@ -3177,6 +3178,13 @@ warnclear: desc: "The ID of the user whose warnings are being cleared." index: desc: "The index of the warning to be cleared, or 0 to clear all warnings." +warndelete: + desc: Deletes a warning from a user by its index. + ex: + - 3 + params: + - index: + desc: "The index of the warning to be deleted." warnpunishlist: desc: Lists punishments for warnings. ex: @@ -3767,7 +3775,7 @@ expredit: - 123 I'm a magical girl params: - id: - desc: "The unique identifier for the expression being edited." + desc: "The id of the expression being edited." message: desc: "The text that will replace the original response in the expression's output." say: @@ -4080,7 +4088,7 @@ xpshopbuy: - type: desc: "The type of item to purchase, such as a skill or a cosmetic." key: - desc: "The unique identifier for the item being purchased." + desc: "The id of the item being purchased." xpshopuse: desc: Use a previously purchased item from the xp shop by specifying the type and the key of the item. ex: @@ -4090,7 +4098,7 @@ xpshopuse: - type: desc: "The type of item to be used, such as an experience point or a skill upgrade." key: - desc: "The unique identifier for the item in the XP shop that you want to use." + desc: "The id of the item in the XP shop that you want to use." bible: desc: Shows bible verse. You need to supply book name and chapter:verse ex: @@ -4118,13 +4126,13 @@ edit: - '#other-channel 771562360594628608 {{"description":"hello"}}' params: - messageId: - desc: "The unique identifier of the message being edited." + desc: "The id of the message being edited." text: desc: "The new text content of the edited message." - channel: desc: "The target channel where the edited message will be sent or updated in." messageId: - desc: "The unique identifier of the message being edited." + desc: "The id of the message being edited." text: desc: "The new text content of the edited message." delete: @@ -4135,13 +4143,13 @@ delete: - 771562360594628608 5m params: - messageId: - desc: "The unique identifier of a specific message within a channel, used to target the deletion operation." + desc: "The id of a specific message within a channel, used to target the deletion operation." time: desc: "The duration after which the message should be automatically deleted." - channel: desc: "The channel where the message is located or should be searched for." messageId: - desc: "The unique identifier of a specific message within a channel, used to target the deletion operation." + desc: "The id of a specific message within a channel, used to target the deletion operation." time: desc: "The duration after which the message should be automatically deleted." roleid: @@ -4294,7 +4302,7 @@ banktake: - amount: desc: "The total value of funds being withdrawn." userId: - desc: "The unique identifier for the user whose account is being accessed." + desc: "The id of the user whose account is being accessed." bankaward: desc: Award the specified amount of currency to a user's bank ex: @@ -4485,7 +4493,7 @@ todoedit: - abc This is an updated entry params: - todoId: - desc: "The unique identifier for the todo item being edited." + desc: "The id of the todo item being edited." newMessage: desc: "The text of a new task description or update to an existing one." todocomplete: @@ -4494,14 +4502,14 @@ todocomplete: - 4a params: - todoId: - desc: "The unique identifier for the todo item being marked as completed." + desc: "The id of the todo item being marked as completed." tododelete: desc: Deletes a todo with the specified ID. ex: - abc params: - todoId: - desc: "The unique identifier for the todo item being deleted." + desc: "The id of the todo item being deleted." todoclear: desc: Deletes all unarchived todos. ex: @@ -4535,7 +4543,7 @@ todoshow: - 4a params: - todoId: - desc: "The unique identifier for the todo item being displayed." + desc: "The id of the todo item being displayed." todoarchivedelete: desc: Deletes the archived todo list with the specified ID. ex: diff --git a/src/EllieBot/data/strings/responses/responses.en-US.json b/src/EllieBot/data/strings/responses/responses.en-US.json index c7d5b7a..523fb07 100644 --- a/src/EllieBot/data/strings/responses/responses.en-US.json +++ b/src/EllieBot/data/strings/responses/responses.en-US.json @@ -702,6 +702,7 @@ "warn_count": "{0} current, {1} total", "warnlog_for": "Warnlog for {0}", "warnpl_none": "No punishments set.", + "warning_not_found": "Warning not found.", "warn_expire_set_delete": "Warnings will be deleted after {0} days.", "warn_expire_set_clear": "Warnings will be cleared after {0} days.", "warn_expire_reset": "Warnings will no longer expire.", @@ -712,6 +713,7 @@ "warn_punish_rem": "Having {0} warnings will no longer trigger a punishment.", "warn_punish_set": "I will apply {0} punishment to users with {1} warnings.", "warn_punish_set_timed": "I will apply {0} punishment for {2} to users with {1} warnings.", + "warning_deleted": "Warning {0} has been deleted.", "time_new": "Time", "timezone": "Timezone", "timezone_db_api_key": "You need to activate your TimezoneDB API key. You can do so by clicking on the link you've received in the email with your API key.",