diff --git a/KfChatDotNetBot/Commands/AdminCommands.cs b/KfChatDotNetBot/Commands/AdminCommands.cs index 878ede8..64f0280 100644 --- a/KfChatDotNetBot/Commands/AdminCommands.cs +++ b/KfChatDotNetBot/Commands/AdminCommands.cs @@ -20,6 +20,8 @@ public class SetRoleCommand : ICommand public string? HelpText => "Set a user's role"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; + public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); @@ -47,6 +49,7 @@ public class CacheClearAdminCommand : ICommand public string? HelpText => null; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -69,6 +72,7 @@ public class NewKickChannelCommand : ICommand public string? HelpText => "Add a Kick channel to the bot's database"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var autoCapture = false; @@ -115,6 +119,7 @@ public class RemoveStreamChannelCommand : ICommand public string? HelpText => "Remove a Kick channel from the bot's database"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); @@ -140,6 +145,7 @@ public class ReconnectKickCommand : ICommand public string? HelpText => "Disconnect from Kick so the watchdog can reconnect it"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { if (botInstance.BotServices.KickClient == null) @@ -163,6 +169,7 @@ public class NewPartiChannelCommand : ICommand public string? HelpText => "Add a Parti channel to the bot's database"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var autoCapture = false; @@ -210,6 +217,7 @@ public class NewDLiveChannelCommand : ICommand public string? HelpText => "Add a DLive channel to the bot's database"; public UserRight RequiredRight => UserRight.Admin; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var autoCapture = false; @@ -251,6 +259,7 @@ public class AddCourtHearingCommand : ICommand public string? HelpText => "Add a court hearing to the bot's calendar"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var hearings = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotCourtCalendar)).JsonDeserialize>(); @@ -281,6 +290,7 @@ public class RemoveCourtHearingCommand : ICommand public string? HelpText => "Remove a hearing from the bot's calendar"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var hearings = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotCourtCalendar)).JsonDeserialize>(); @@ -312,7 +322,7 @@ public class DeleteMessagesCommand : ICommand public string? HelpText => "Delete the most recent x number of messages"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -345,7 +355,7 @@ public class IgnoreCommand : ICommand public string? HelpText => "Ignore a user by ID"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -379,7 +389,7 @@ public class UnignoreCommand : ICommand public string? HelpText => "Unignore a user by ID"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -413,7 +423,7 @@ public class SetAlmanacTextCommand : ICommand public string? HelpText => "Set the almanac text to whatever"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -431,7 +441,7 @@ public class SetAlmanacIntervalCommand : ICommand public string? HelpText => "Set the almanac interval to whatever in seconds"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -461,7 +471,7 @@ public class StopAlmanacCommand : ICommand public string? HelpText => "Stop the almanac reminder"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -490,7 +500,7 @@ public class StartAlmanacCommand : ICommand public string? HelpText => "Start the almanac reminder"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { diff --git a/KfChatDotNetBot/Commands/HowlggCommands.cs b/KfChatDotNetBot/Commands/HowlggCommands.cs index d9bfa7b..66bf937 100644 --- a/KfChatDotNetBot/Commands/HowlggCommands.cs +++ b/KfChatDotNetBot/Commands/HowlggCommands.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using Humanizer; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; @@ -15,6 +16,7 @@ public class HowlggStatsCommand : ICommand public string? HelpText => "Get betting statistics in the given window"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var window = Convert.ToInt32(arguments["window"].Value); @@ -42,6 +44,7 @@ public class HowlggRecentBetCommand : ICommand public string? HelpText => "Get the most recent 3 bets"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await SettingsProvider.GetMultipleValuesAsync([ diff --git a/KfChatDotNetBot/Commands/ICommand.cs b/KfChatDotNetBot/Commands/ICommand.cs index 2cf5e0a..1a01f76 100644 --- a/KfChatDotNetBot/Commands/ICommand.cs +++ b/KfChatDotNetBot/Commands/ICommand.cs @@ -1,16 +1,18 @@ using System.Text.RegularExpressions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetWsClient.Models.Events; namespace KfChatDotNetBot.Commands; -internal interface ICommand +public interface ICommand { List Patterns { get; } // Set to null to disable help for a given command string? HelpText { get; } UserRight RequiredRight { get; } TimeSpan Timeout { get; } + RateLimitOptionsModel? RateLimitOptions { get; } Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx); } \ No newline at end of file diff --git a/KfChatDotNetBot/Commands/ImageCommands.cs b/KfChatDotNetBot/Commands/ImageCommands.cs index 15f23fe..5d67e07 100644 --- a/KfChatDotNetBot/Commands/ImageCommands.cs +++ b/KfChatDotNetBot/Commands/ImageCommands.cs @@ -21,7 +21,7 @@ public class AddImageCommand : ICommand public string? HelpText => "Add an image to the image rotation specified"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -61,7 +61,7 @@ public class RemoveImageCommand : ICommand public string? HelpText => "Remove an image from the image rotation specified"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -99,7 +99,7 @@ public class ListImageCommand : ICommand public string? HelpText => "Remove an image from the image rotation specified"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -138,7 +138,12 @@ public class GetRandomImage : ICommand public string? HelpText => "Get a random image"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromMinutes(10); - + public RateLimitOptionsModel? RateLimitOptions => new() + { + Window = TimeSpan.FromSeconds(10), + MaxInvocations = 3, + Flags = RateLimitFlags.NoResponse | RateLimitFlags.UseEntireMessage + }; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { diff --git a/KfChatDotNetBot/Commands/JuiceCommand.cs b/KfChatDotNetBot/Commands/JuiceCommand.cs index c58e40d..36bf7c6 100644 --- a/KfChatDotNetBot/Commands/JuiceCommand.cs +++ b/KfChatDotNetBot/Commands/JuiceCommand.cs @@ -16,6 +16,7 @@ public class JuiceCommand : ICommand public bool HideFromHelp => false; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(60); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); @@ -81,6 +82,7 @@ public class JuiceStatsCommand : ICommand public bool HideFromHelp => false; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { int top; diff --git a/KfChatDotNetBot/Commands/KasinoUserCommands.cs b/KfChatDotNetBot/Commands/KasinoUserCommands.cs index 08bb27f..99c5949 100644 --- a/KfChatDotNetBot/Commands/KasinoUserCommands.cs +++ b/KfChatDotNetBot/Commands/KasinoUserCommands.cs @@ -2,6 +2,7 @@ using Humanizer; using Humanizer.Localisation; using KfChatDotNetBot.Extensions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Services; using KfChatDotNetBot.Settings; @@ -20,7 +21,7 @@ public class GetBalanceCommand : ICommand public string? HelpText => "Get your gamba balance"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -39,7 +40,7 @@ public class GetExclusionCommand : ICommand public string? HelpText => "Get your exclusion status"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -72,7 +73,7 @@ public class SendJuiceCommand : ICommand public string? HelpText => "Send juice to somebody"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -118,7 +119,12 @@ public class RakebackCommand : ICommand public string? HelpText => "Collect your rakeback"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + { + MaxInvocations = 1, + Window = TimeSpan.FromSeconds(30), + Flags = RateLimitFlags.AutoDeleteCooldownResponse + }; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -167,7 +173,12 @@ public class LossbackCommand : ICommand public string? HelpText => "Collect your lossback"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + { + Window = TimeSpan.FromSeconds(30), + MaxInvocations = 1, + Flags = RateLimitFlags.AutoDeleteCooldownResponse + }; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -213,12 +224,11 @@ public class AbandonKasinoCommand : ICommand public List Patterns => [ new Regex(@"^abandon$", RegexOptions.IgnoreCase), new Regex(@"^abandon confirm$", RegexOptions.IgnoreCase) - ]; public string? HelpText => "Abandon your Keno Kasino gambler account"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { diff --git a/KfChatDotNetBot/Commands/MemeCommands.cs b/KfChatDotNetBot/Commands/MemeCommands.cs index 25b684b..6bca5b5 100644 --- a/KfChatDotNetBot/Commands/MemeCommands.cs +++ b/KfChatDotNetBot/Commands/MemeCommands.cs @@ -16,6 +16,7 @@ public class InsanityCommand : ICommand public string? HelpText => "Insanity"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo @@ -29,6 +30,7 @@ public class TwistedCommand : ICommand public string? HelpText => "Get it twisted"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo @@ -45,6 +47,7 @@ public class CrackedCommand : ICommand public string? HelpText => "Crackhead Zalgo text"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var logger = LogManager.GetCurrentClassLogger(); @@ -67,6 +70,7 @@ public class CleanCommand : ICommand public string? HelpText => "How long has Bossman been clean?"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = @@ -90,6 +94,7 @@ public class RehabCommand : ICommand public string? HelpText => "How long until rehab is over?"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = @@ -121,6 +126,7 @@ public class NextPoVisitCommand : ICommand public string? HelpText => "How long until the next PO visit?"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(120); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var time = await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotPoNextVisit); @@ -160,6 +166,7 @@ public class NextCourtHearingCommand : ICommand public string? HelpText => "How long until the next court hearing?"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(120); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var hearings = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotCourtCalendar)).JsonDeserialize>(); @@ -215,6 +222,7 @@ public class JailCommand : ICommand public string? HelpText => "How long has Bossman been in jail?"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.BotJailStartTime, BuiltIn.Keys.TwitchBossmanJackUsername]); @@ -235,6 +243,7 @@ public class LastStreamCommand : ICommand public string? HelpText => "How long ago did Austin Gambles last stream (on Twitch)?"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await SettingsProvider.GetMultipleValuesAsync([ @@ -270,6 +279,7 @@ public class AlmanacCommand : ICommand public string? HelpText => "Return details on how to submit almanac entries"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var text = await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotAlmanacText); diff --git a/KfChatDotNetBot/Commands/MomCommands.cs b/KfChatDotNetBot/Commands/MomCommands.cs index 110d95c..cf120cf 100644 --- a/KfChatDotNetBot/Commands/MomCommands.cs +++ b/KfChatDotNetBot/Commands/MomCommands.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using Humanizer; using Humanizer.Localisation; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; @@ -15,6 +16,7 @@ public class MomCommand : ICommand public bool HideFromHelp => false; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(60); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) diff --git a/KfChatDotNetBot/Commands/RainbetCommands.cs b/KfChatDotNetBot/Commands/RainbetCommands.cs index 7ab8d80..c777ce1 100644 --- a/KfChatDotNetBot/Commands/RainbetCommands.cs +++ b/KfChatDotNetBot/Commands/RainbetCommands.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using Humanizer; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; @@ -15,6 +16,7 @@ public class RainbetStatsCommand : ICommand public string? HelpText => "Get betting statistics in the given window"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var window = Convert.ToInt32(arguments["window"].Value); @@ -41,6 +43,7 @@ public class RainbetRecentBetCommand : ICommand public string? HelpText => "Get the most recent 3 bets"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await SettingsProvider.GetMultipleValuesAsync([ diff --git a/KfChatDotNetBot/Commands/RestreamCommands.cs b/KfChatDotNetBot/Commands/RestreamCommands.cs index 53e5465..ffc13a1 100644 --- a/KfChatDotNetBot/Commands/RestreamCommands.cs +++ b/KfChatDotNetBot/Commands/RestreamCommands.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; @@ -15,7 +16,7 @@ public class GetRestreamCommand : ICommand public string? HelpText => "Grab restream URL"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -33,7 +34,7 @@ public class SetRestreamCommand : ICommand public string? HelpText => "Set restream URL"; public UserRight RequiredRight => UserRight.TrueAndHonest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -51,7 +52,7 @@ public class SelfPromoCommand : ICommand public string? HelpText => "Promote your shit"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { @@ -80,7 +81,7 @@ public class GetRestreamPlainCommand : ICommand public string? HelpText => "Grab restream URL with plain prefixed"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); - + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { diff --git a/KfChatDotNetBot/Commands/TestCommands.cs b/KfChatDotNetBot/Commands/TestCommands.cs index cdcca24..d4d7976 100644 --- a/KfChatDotNetBot/Commands/TestCommands.cs +++ b/KfChatDotNetBot/Commands/TestCommands.cs @@ -17,6 +17,7 @@ public class EditTestCommand : ICommand public UserRight RequiredRight => UserRight.Admin; // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes public TimeSpan Timeout => TimeSpan.FromSeconds(60); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var logger = LogManager.GetCurrentClassLogger(); @@ -63,6 +64,7 @@ public class TimeoutTestCommand : ICommand public UserRight RequiredRight => UserRight.Admin; // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes public TimeSpan Timeout => TimeSpan.FromSeconds(15); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await Task.Delay(TimeSpan.FromMinutes(1), ctx); @@ -79,6 +81,7 @@ public class ExceptionTestCommand : ICommand public UserRight RequiredRight => UserRight.Admin; // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes public TimeSpan Timeout => TimeSpan.FromSeconds(15); + public RateLimitOptionsModel? RateLimitOptions => null; public Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { throw new Exception("Caused by the test exception command"); @@ -95,6 +98,7 @@ public class LengthLimitTestCommand : ICommand public UserRight RequiredRight => UserRight.Admin; // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes public TimeSpan Timeout => TimeSpan.FromSeconds(15); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var logger = LogManager.GetCurrentClassLogger(); @@ -110,13 +114,34 @@ public class LengthLimitTestCommand : ICommand await Task.Delay(TimeSpan.FromSeconds(5), ctx); logger.Info($"niceTruncation => {niceTruncation.Status}; exactTruncation => {exactTruncation.Status}; doNothing => {doNothing.Status}; refuseToSend => {refuseToSend.Status}"); if (niceTruncation.ChatMessageId != null) - botInstance.KfClient.DeleteMessage(niceTruncation.ChatMessageId.Value); + await botInstance.KfClient.DeleteMessageAsync(niceTruncation.ChatMessageId.Value); if (exactTruncation.ChatMessageId != null) - botInstance.KfClient.DeleteMessage(exactTruncation.ChatMessageId.Value); + await botInstance.KfClient.DeleteMessageAsync(exactTruncation.ChatMessageId.Value); if (doNothing.ChatMessageId != null) - botInstance.KfClient.DeleteMessage(doNothing.ChatMessageId.Value); + await botInstance.KfClient.DeleteMessageAsync(doNothing.ChatMessageId.Value); // Should never happen if (refuseToSend.ChatMessageId != null) - botInstance.KfClient.DeleteMessage(refuseToSend.ChatMessageId.Value); + await botInstance.KfClient.DeleteMessageAsync(refuseToSend.ChatMessageId.Value); + } +} + +public class RateLimitTestCommand : ICommand +{ + public List Patterns => [ + new Regex("^test ratelimit$") + ]; + + public string? HelpText => null; + public UserRight RequiredRight => UserRight.Admin; + // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes + public TimeSpan Timeout => TimeSpan.FromSeconds(15); + public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + { + MaxInvocations = 1, + Window = TimeSpan.FromSeconds(60) + }; + public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) + { + await botInstance.SendChatMessageAsync("Nigger", true); } } \ No newline at end of file diff --git a/KfChatDotNetBot/Commands/TimeCommand.cs b/KfChatDotNetBot/Commands/TimeCommand.cs index 1dd9c17..1d9d4ac 100644 --- a/KfChatDotNetBot/Commands/TimeCommand.cs +++ b/KfChatDotNetBot/Commands/TimeCommand.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetWsClient.Models.Events; @@ -7,9 +8,10 @@ namespace KfChatDotNetBot.Commands; public class TimeCommand : ICommand { public List Patterns => [new Regex("^time")]; - public string? HelpText => "Get current time in AGT"; + public string? HelpText => "Get current time in BMT"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var bmt = new DateTimeOffset(TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, diff --git a/KfChatDotNetBot/Commands/UtilityCommands.cs b/KfChatDotNetBot/Commands/UtilityCommands.cs index e6d1a34..05413c3 100644 --- a/KfChatDotNetBot/Commands/UtilityCommands.cs +++ b/KfChatDotNetBot/Commands/UtilityCommands.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Text.RegularExpressions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetWsClient.Models.Events; @@ -14,6 +15,7 @@ public class TempEnableDiscordRelayingCommand : ICommand public string? HelpText => null; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { botInstance.BotServices.TemporarilyBypassGambaSeshForDiscord = true; @@ -30,6 +32,7 @@ public class TempSuppressGambaMessages : ICommand public string? HelpText => null; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { botInstance.BotServices.TemporarilySuppressGambaMessages = true; @@ -46,6 +49,7 @@ public class EnableGambaMessages : ICommand public string? HelpText => null; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { botInstance.BotServices.TemporarilySuppressGambaMessages = false; @@ -62,6 +66,7 @@ public class GetVersionCommand : ICommand public string? HelpText => null; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var version = Assembly.GetEntryAssembly()? diff --git a/KfChatDotNetBot/Commands/WhoisCommand.cs b/KfChatDotNetBot/Commands/WhoisCommand.cs index adab392..1a5f1b0 100644 --- a/KfChatDotNetBot/Commands/WhoisCommand.cs +++ b/KfChatDotNetBot/Commands/WhoisCommand.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetWsClient.Models.Events; using Microsoft.EntityFrameworkCore; @@ -15,6 +16,7 @@ public class WhoisCommand : ICommand public string? HelpText => "Lookup user IDs by username"; public UserRight RequiredRight => UserRight.Guest; public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => null; public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); diff --git a/KfChatDotNetBot/Models/RateLimitModels.cs b/KfChatDotNetBot/Models/RateLimitModels.cs new file mode 100644 index 0000000..254aa9e --- /dev/null +++ b/KfChatDotNetBot/Models/RateLimitModels.cs @@ -0,0 +1,83 @@ +namespace KfChatDotNetBot.Models; + +public class RateLimitBucketEntryModel +{ + /// + /// Database user ID of the user whose entry this belongs to + /// + public required int UserId { get; set; } + /// + /// When the entry was created in the bucket + /// + public required DateTimeOffset EntryCreated { get; set; } + /// + /// When the entry is expected to expire based on the command's window + /// + public required DateTimeOffset EntryExpires { get; set; } + /// + /// String representation of the command using ICommand.GetType().Name + /// + public required string CommandInvoked { get; set; } + /// + /// Hashed contents of the message for if UseEntireMessage is enabled + /// + public required string MessageHash { get; set; } +} + +public class RateLimitOptionsModel +{ + /// + /// Window of time to count an invocation towards the rate limit + /// + public required TimeSpan Window { get; set; } + /// + /// Maximum number of permitted invocations within the window before triggering the rate limit + /// + public required int MaxInvocations { get; set; } + /// + /// Optional set of flags to configure the behavior of the rate limiter + /// + public RateLimitFlags Flags { get; set; } +} + +public class IsRateLimitedModel +{ + /// + /// Is the user's request rate limited? + /// + public required bool IsRateLimited { get; set; } + /// + /// When the oldest entry expires so users know when they can next use the command + /// This is set to null if the user is not rate limited + /// + public DateTimeOffset? OldestEntryExpires { get; set; } +} + +[Flags] +public enum RateLimitFlags +{ + /// + /// Silently ignore a user when they trigger a rate limit + /// + NoResponse, + /// + /// The default behavior is to rate limit based on command invoked. + /// UseEntireMessage changes it to consider dissimilar messages which invoke + /// the same command as being separate for the purposes of rate limiting. + /// With this, only identical messages count towards the rate limit. + /// + UseEntireMessage, + /// + /// The rate limit is global instead of applying per-user + /// + Global, + /// + /// Exempt users with a higher than default level from rate limiting + /// + ExemptPrivilegedUsers, + /// + /// Automatically clean up the cooldown response sent to a user + /// Mutually exclusive with NoResponse + /// + AutoDeleteCooldownResponse +} \ No newline at end of file diff --git a/KfChatDotNetBot/Services/BotCommands.cs b/KfChatDotNetBot/Services/BotCommands.cs index 05b251b..e6b011b 100644 --- a/KfChatDotNetBot/Services/BotCommands.cs +++ b/KfChatDotNetBot/Services/BotCommands.cs @@ -1,7 +1,9 @@ using System.Text.RegularExpressions; using Humanizer; +using Humanizer.Localisation; using KfChatDotNetBot.Commands; using KfChatDotNetBot.Extensions; +using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; @@ -34,6 +36,8 @@ internal class BotCommands { _logger.Debug($"Found command {command.GetType().Name}"); } + + _ = CleanupExpiredRateLimitEntriesTask(); } internal void ProcessMessage(MessageModel message) @@ -89,12 +93,26 @@ internal class BotCommands return; } } + if (user.UserRight < command.RequiredRight) { _bot.SendChatMessage($"@{message.Author.Username}, you do not have access to use this command. Your rank: {user.UserRight.Humanize()}; Required rank: {command.RequiredRight.Humanize()}", true); if (continueAfterProcess) continue; break; } + + if (command.RateLimitOptions != null) + { + var isRateLimited = RateLimitService.IsRateLimited(user, command, message.MessageRawHtmlDecoded); + if (isRateLimited.IsRateLimited) + { + _ = SendCooldownResponse(user, command.RateLimitOptions, isRateLimited.OldestEntryExpires!.Value, command.GetType().Name); + } + else + { + RateLimitService.AddEntry(user, command, message.MessageRawHtmlDecoded); + } + } _ = ProcessMessageAsync(command, message, user, match.Groups); if (!continueAfterProcess) break; } @@ -136,6 +154,48 @@ internal class BotCommands $"🤑🤑 {user.KfUsername} has leveled up to to {newLevel.VipLevel.Icon} {newLevel.VipLevel.Name} Tier {newLevel.Tier} " + $"and received a bonus of {await payout.FormatKasinoCurrencyAsync()}", true); } + + private async Task SendCooldownResponse(UserDbModel user, RateLimitOptionsModel options, DateTimeOffset oldestEntryExpires, string commandName) + { + if (options.Flags.HasFlag(RateLimitFlags.NoResponse)) return; + var timeRemaining = oldestEntryExpires - DateTimeOffset.UtcNow; + var message = await _bot.SendChatMessageAsync($"{user.FormatUsername()}, please wait {timeRemaining.Humanize(maxUnit: TimeUnit.Minute, minUnit: TimeUnit.Millisecond, precision: 2)} before attempting to run {commandName} again.", true); + if (!options.Flags.HasFlag(RateLimitFlags.AutoDeleteCooldownResponse)) return; + var i = 0; + while (message.ChatMessageId == null) + { + i++; + await Task.Delay(250, _cancellationToken); + if (i > 30) + { + _logger.Error("Gave up waiting for Sneedchat to give us the message ID for removing a cooldown notification"); + return; + } + + if (message.Status is SentMessageTrackerStatus.NotSending or SentMessageTrackerStatus.Lost) + { + _logger.Error("Cooldown message was suppressed or lost"); + return; + } + } + + var autoDeleteInterval = + (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotRateLimitCooldownAutoDeleteDelay)).ToType(); + await Task.Delay(autoDeleteInterval, _cancellationToken); + await _bot.KfClient.DeleteMessageAsync(message.ChatMessageId.Value); + } + + private async Task CleanupExpiredRateLimitEntriesTask() + { + while (!_cancellationToken.IsCancellationRequested) + { + var interval = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.BotRateLimitExpiredEntryCleanupInterval)) + .ToType(); + await Task.Delay(TimeSpan.FromSeconds(interval), _cancellationToken); + _logger.Info("Cleaning up expired rate limit entries"); + RateLimitService.CleanupExpiredEntries(); + } + } private static bool HasAttribute(ICommand command) where T : Attribute { diff --git a/KfChatDotNetBot/Services/RateLimitService.cs b/KfChatDotNetBot/Services/RateLimitService.cs new file mode 100644 index 0000000..2a64f22 --- /dev/null +++ b/KfChatDotNetBot/Services/RateLimitService.cs @@ -0,0 +1,149 @@ +using System.Runtime.Caching; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using KfChatDotNetBot.Commands; +using KfChatDotNetBot.Models; +using KfChatDotNetBot.Models.DbModels; +using NLog; + +namespace KfChatDotNetBot.Services; + +public static class RateLimitService +{ + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + /// + /// Check whether a user is rate limited for a given command + /// + /// User you wish to check + /// Command the user is invoking + /// Message the user sent + /// + public static IsRateLimitedModel IsRateLimited(UserDbModel user, ICommand command, string message) + { + var result = new IsRateLimitedModel + { + IsRateLimited = false + }; + if (command.RateLimitOptions == null) return result; + if (command.RateLimitOptions.Flags.HasFlag(RateLimitFlags.ExemptPrivilegedUsers) && + user.UserRight > UserRight.Guest) return result; + var entries = GetBucketEntries(command.GetType().Name); + if (!command.RateLimitOptions.Flags.HasFlag(RateLimitFlags.Global)) + { + entries = entries.Where(x => x.UserId == user.Id).ToList(); + } + + if (command.RateLimitOptions.Flags.HasFlag(RateLimitFlags.UseEntireMessage)) + { + var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(message))); + entries = entries.Where(x => x.MessageHash == hash).ToList(); + } + + var now = DateTimeOffset.UtcNow; + entries = entries.Where(x => x.EntryExpires > now).ToList(); + if (entries.Count >= command.RateLimitOptions.MaxInvocations) + { + result.IsRateLimited = true; + result.OldestEntryExpires = entries.OrderBy(x => x.EntryCreated).Last().EntryExpires; + } + return result; + } + + /// + /// Get all the bucket entries for a given command + /// + /// String representation of the command. + /// Get it by running command.GetType().Name + /// A list of entries + /// Thrown if the cached entries were somehow null when converted to a string + public static List GetBucketEntries(string commandName) + { + var cache = MemoryCache.Default; + var entries = cache.Get($"RateLimitBucket:{commandName}"); + if (entries == null) return []; + List bucketEntries; + try + { + bucketEntries = JsonSerializer.Deserialize>((string)entries) ?? + throw new InvalidOperationException(); + } + catch (Exception e) + { + _logger.Error($"Caught an exception when trying to deserialize RateLimitBucket entries for {commandName}. JSON follows"); + _logger.Error(entries); + _logger.Error("Exception follows"); + _logger.Error(e); + return []; + } + + return bucketEntries; + } + + /// + /// Save the current state of bucket entries for a given command + /// + /// String representation of the command. + /// Get it by running command.GetType().Name + /// Entries you wish to save + public static void SaveBucketEntries(string commandName, List entries) + { + var cache = MemoryCache.Default; + cache.Set($"RateLimitBucket:{commandName}", JsonSerializer.Serialize(entries), + new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1) }); + } + + /// + /// Remove the most recent entry for a given user and command + /// Use this if you want to invalidate an entry as forgiveness for invalid user input + /// + /// User to remove the entry for + /// Command the user ran + public static void RemoveMostRecentEntry(UserDbModel user, ICommand command) + { + var entries = GetBucketEntries(command.GetType().Name); + var lastEntry = entries.Where(x => x.UserId == user.Id).OrderBy(x => x.EntryCreated).LastOrDefault(); + if (lastEntry == null) return; + entries.Remove(lastEntry); + SaveBucketEntries(command.GetType().Name, entries); + } + + /// + /// Add an entry to the rate limit bucket for the given command + /// + /// User the entry belongs to + /// Command the user ran + /// The user's message + public static void AddEntry(UserDbModel user, ICommand command, string message) + { + if (command.RateLimitOptions == null) return; + var commandName = command.GetType().Name; + var entries = GetBucketEntries(commandName); + entries.Add(new RateLimitBucketEntryModel + { + UserId = user.Id, + EntryCreated = DateTimeOffset.UtcNow, + EntryExpires = DateTimeOffset.UtcNow + command.RateLimitOptions.Window, + CommandInvoked = commandName, + MessageHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(message))) + }); + SaveBucketEntries(commandName, entries); + } + + /// + /// Removes entries which have expired for all commands in the rate limit bucket + /// + public static void CleanupExpiredEntries() + { + var cache = MemoryCache.Default; + var now = DateTimeOffset.UtcNow; + foreach (var entry in cache.Select(kvp => kvp.Key).Where(kvp => kvp.StartsWith("RateLimitBucket:")).ToList().OfType()) + { + _logger.Info($"Cleaning up expired entries for {entry}"); + var commandName = entry.Replace("RateLimitBucket:", string.Empty); + var entries = GetBucketEntries(commandName); + SaveBucketEntries(commandName, entries.Where(x => x.EntryExpires > now).ToList()); + } + } +} \ No newline at end of file diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs index 92392b3..3e2a7be 100644 --- a/KfChatDotNetBot/Settings/BuiltIn.cs +++ b/KfChatDotNetBot/Settings/BuiltIn.cs @@ -1038,6 +1038,22 @@ public static class BuiltIn Default = "7500", ValueType = SettingValueType.Text, Regex = WholeNumberRegex + }, + new BuiltInSettingsModel + { + Key = Keys.BotRateLimitCooldownAutoDeleteDelay, + Description = "Delay in milliseconds before removing a cooldown message set to auto delete", + Default = "15000", + ValueType = SettingValueType.Text, + Regex = WholeNumberRegex + }, + new BuiltInSettingsModel + { + Key = Keys.BotRateLimitExpiredEntryCleanupInterval, + Description = "How often to cleanup expired rate limit entries in seconds", + Default = "300", + ValueType = SettingValueType.Text, + Regex = WholeNumberRegex } ]; @@ -1157,5 +1173,7 @@ public static class BuiltIn public static string MoneyLossbackMinimumAmount = "Money.Lossback.MinimumAmount"; public static string BotImageChinkSelfDestruct = "Bot.Image.ChinkSelfDestruct"; public static string BotImageChinkSelfDestructDelay = "Bot.Image.ChinkSelfDestructDelay"; + public static string BotRateLimitCooldownAutoDeleteDelay = "Bot.RateLimit.CooldownAutoDeleteDelay"; + public static string BotRateLimitExpiredEntryCleanupInterval = "Bot.RateLimit.ExpiredEntryCleanupInterval"; } } \ No newline at end of file