diff --git a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs index 5604f06be..c990e307c 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs +++ b/src/NadekoBot/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(GuildPerm.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/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs index 08cb734f2..e2fb7a821 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -89,9 +89,10 @@ public class UserPunishService : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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 : INService, 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/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index 971881dab..0694e7f43 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -947,6 +947,10 @@ warnexpire: warnclear: - warnclear - warnc +warndelete: + - warndelete + - warnrm + - warnd warnpunishlist: - warnpunishlist - warnpl diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index 2a6edf93a..fef530136 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -3178,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: diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 120c6b3dc..1d201d5db 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/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.",