From defcf8a4fed4339bea094adbd69d11ffcbc0cacf Mon Sep 17 00:00:00 2001 From: 13xforever Date: Fri, 23 Jan 2026 17:22:35 +0500 Subject: [PATCH] add warning cooldown to prevent some duplicate warns --- CompatBot/Commands/Warnings.ContextMenus.cs | 13 +++++--- CompatBot/Commands/Warnings.cs | 31 +++++++++++++------ CompatBot/Database/Providers/ContentFilter.cs | 8 +++-- .../EventHandlers/DiscordInviteFilter.cs | 6 ++-- CompatBot/EventHandlers/LogParsingHandler.cs | 5 +-- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/CompatBot/Commands/Warnings.ContextMenus.cs b/CompatBot/Commands/Warnings.ContextMenus.cs index b68e86ad..149e55a7 100644 --- a/CompatBot/Commands/Warnings.ContextMenus.cs +++ b/CompatBot/Commands/Warnings.ContextMenus.cs @@ -3,6 +3,7 @@ using DSharpPlus.Commands.Processors.MessageCommands; using DSharpPlus.Commands.Processors.UserCommands; using DSharpPlus.Interactivity; using Microsoft.Extensions.DependencyInjection; +using ResultNet; namespace CompatBot.Commands; @@ -60,13 +61,17 @@ internal static class WarningsContextMenus ).ConfigureAwait(false); user ??= message?.Author!; var reason = ((TextInputModalSubmission)value).Value; - var (saved, suppress, recent, total) = await Warnings.AddAsync(user.Id, ctx.User, reason, message?.Content.Sanitize()).ConfigureAwait(false); - if (!saved) + var result = await Warnings.AddAsync(user.Id, ctx.User, reason, message?.Content.Sanitize()).ConfigureAwait(false); + if (result.IsFailure()) { - await ctx.RespondAsync($"{Config.Reactions.Failure} Couldn't save the warning, please try again", ephemeral: true).ConfigureAwait(false); + var response = new DiscordInteractionResponseBuilder() + .WithContent($"{Config.Reactions.Failure} {result.Message ?? "Couldn't save the warning, please try again"}") + .AsEphemeral(); + await interaction.EditOriginalResponseAsync(new(response)).ConfigureAwait(false); return; } + var(suppress, recent, total) = result.Data; if (!suppress) { var userMsgContent = await Warnings.GetDefaultWarningMessageAsync(ctx.Client, user, reason, recent, total, ctx.User).ConfigureAwait(false); @@ -85,7 +90,7 @@ internal static class WarningsContextMenus Config.Log.Error(e); var msg = new DiscordInteractionResponseBuilder() .AsEphemeral() - .WithContent($"{Config.Reactions.Failure} Failed to change nickname, check bot's permissions"); + .WithContent($"{Config.Reactions.Failure} Failed to save warning"); await interaction.EditOriginalResponseAsync(new(msg)).ConfigureAwait(false); } } diff --git a/CompatBot/Commands/Warnings.cs b/CompatBot/Commands/Warnings.cs index 93f7f140..b862b8cd 100644 --- a/CompatBot/Commands/Warnings.cs +++ b/CompatBot/Commands/Warnings.cs @@ -2,6 +2,7 @@ using CompatBot.Commands.AutoCompleteProviders; using CompatBot.Database; using Microsoft.EntityFrameworkCore; +using ResultNet; namespace CompatBot.Commands; @@ -20,13 +21,14 @@ internal static partial class Warnings ) { await ctx.DeferResponseAsync(ephemeral: true).ConfigureAwait(false); - var (saved, suppress, recent, total) = await AddAsync(user.Id, ctx.User, reason).ConfigureAwait(false); - if (!saved) + var result = await AddAsync(user.Id, ctx.User, reason).ConfigureAwait(false); + if (result.IsFailure()) { - await ctx.RespondAsync($"{Config.Reactions.Failure} Couldn't save the warning, please try again", ephemeral: true).ConfigureAwait(false); + await ctx.RespondAsync($"{Config.Reactions.Failure} {result.Message ?? "Couldn't save the warning, please try again"}", ephemeral: true).ConfigureAwait(false); return; } + var (suppress, recent, total) = result.Data; if (!suppress) { var userMsgContent = await GetDefaultWarningMessageAsync(ctx.Client, user, reason, recent, total, ctx.User).ConfigureAwait(false); @@ -193,9 +195,19 @@ internal static partial class Warnings """; } - internal static async ValueTask<(bool saved, bool suppress, int recentCount, int totalCount)> + internal static async ValueTask> AddAsync(ulong userId, DiscordUser issuer, string reason, string? fullReason = null) { + await using (var rdb = await BotDb.OpenReadAsync().ConfigureAwait(false)) + { + var lastWarn = await rdb.Warning.AsNoTracking() + .Where(w => w.DiscordId == userId && !w.Retracted) + .OrderByDescending(w => w.Timestamp) + .FirstOrDefaultAsync(); + if (lastWarn?.Timestamp > DateTime.UtcNow.AddSeconds(-20).Ticks) + return Result.Failure<(bool, int, int)>().WithMessage("Duplicate warning cooldown"); + } + try { await using var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false); @@ -215,15 +227,16 @@ internal static partial class Warnings var totalCount = wdb.Warning.Count(w => w.DiscordId == userId && !w.Retracted); var recentCount = wdb.Warning.Count(w => w.DiscordId == userId && !w.Retracted && w.Timestamp > threshold); if (recentCount < 4) - return (true, false, recentCount, totalCount); - + return Result.Success((false, recentCount, totalCount)); + Config.Log.Debug("Suicide behavior detected, not spamming with warning responses"); - return (true, true, recentCount, totalCount); + return Result.Success((true, recentCount, totalCount)); } catch (Exception e) { - Config.Log.Error(e, "Couldn't save the warning"); - return default; + const string msg = "Couldn't save the warning"; + Config.Log.Error(e, msg); + return Result.Failure<(bool, int, int)>().WithMessage(msg); } } diff --git a/CompatBot/Database/Providers/ContentFilter.cs b/CompatBot/Database/Providers/ContentFilter.cs index cf2ebd61..45080665 100644 --- a/CompatBot/Database/Providers/ContentFilter.cs +++ b/CompatBot/Database/Providers/ContentFilter.cs @@ -6,6 +6,7 @@ using CompatBot.Utils.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using NReco.Text; +using ResultNet; namespace CompatBot.Database.Providers; @@ -225,14 +226,17 @@ internal static class ContentFilter try { warningReason ??= "Mention of piracy"; - var (saved, suppress, recent, total) = await Warnings.AddAsync( + var result = await Warnings.AddAsync( message.Author!.Id, client.CurrentUser, warningReason, message.Content.Sanitize() ).ConfigureAwait(false); - if (saved && !suppress && message.Channel is not null) + if (result.IsSuccess() + && !result.Data.suppress + && message.Channel is not null) { + var (_, recent, total) = result.Data; var msgContent = await Warnings.GetDefaultWarningMessageAsync(client, message.Author, warningReason, recent, total, client.CurrentUser).ConfigureAwait(false); var msg = new DiscordMessageBuilder() .WithContent(msgContent) diff --git a/CompatBot/EventHandlers/DiscordInviteFilter.cs b/CompatBot/EventHandlers/DiscordInviteFilter.cs index cd100f9f..7a930859 100644 --- a/CompatBot/EventHandlers/DiscordInviteFilter.cs +++ b/CompatBot/EventHandlers/DiscordInviteFilter.cs @@ -10,6 +10,7 @@ using CompatBot.Database; using CompatBot.Database.Providers; using CompatBot.Utils.Extensions; using Microsoft.Extensions.Caching.Memory; +using ResultNet; namespace CompatBot.EventHandlers; @@ -125,14 +126,15 @@ internal static partial class DiscordInviteFilter if (circumventionAttempt) { var reason = "Attempted to circumvent discord invite filter"; - var (saved, suppress, recent, total) = await Warnings.AddAsync( + var result = await Warnings.AddAsync( message.Author.Id, client.CurrentUser, reason, codeResolveMsg ).ConfigureAwait(false); - if (saved && !suppress) + if (result.IsSuccess() && !result.Data.suppress) { + var (_, recent, total) = result.Data; var content = await Warnings.GetDefaultWarningMessageAsync(client, message.Author, reason, recent, total, client.CurrentUser).ConfigureAwait(false); var msg = new DiscordMessageBuilder() .WithContent(content) diff --git a/CompatBot/EventHandlers/LogParsingHandler.cs b/CompatBot/EventHandlers/LogParsingHandler.cs index fa135c6e..ebec89c7 100644 --- a/CompatBot/EventHandlers/LogParsingHandler.cs +++ b/CompatBot/EventHandlers/LogParsingHandler.cs @@ -225,14 +225,15 @@ public static class LogParsingHandler //if (!(message.Channel!.IsPrivate || message.Channel.Name.Contains("spam"))) { var reason = "Logs showing use of pirated content"; - var (saved, suppress, recent, total) = await Warnings.AddAsync( + var warnSaveResult = await Warnings.AddAsync( message.Author.Id, client.CurrentUser, reason, $"{result.SelectedFilter.String} - {result.SelectedFilterContext?.Sanitize()}" ); - if (saved && !suppress) + if (warnSaveResult.IsSuccess() && !warnSaveResult.Data.suppress) { + var (_, recent, total) = warnSaveResult.Data; var content = await Warnings.GetDefaultWarningMessageAsync(client, message.Author, reason, recent, total, client.CurrentUser).ConfigureAwait(false); var msg = new DiscordMessageBuilder() .WithContent(content)