From 6166b76f92ffd66499817e612c3a0f4fbd19ad14 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:31:33 +0800 Subject: [PATCH] Updated commands to remove the hide from help property, instead set the help text to null for whenever that eventually gets implemented. Also refactored the way tasks are handled so instead of adding to an array and checking in on them next time someone sends a message, it instead delegates it to a very basic async handler that'll await the command, report errors and kill the task if it takes too long. --- KfChatDotNetBot/Commands/ICommand.cs | 5 ++- KfChatDotNetBot/Commands/JuiceCommand.cs | 4 +- KfChatDotNetBot/Commands/MemeCommands.cs | 20 ++++------ KfChatDotNetBot/Commands/RainbetCommands.cs | 10 ++--- KfChatDotNetBot/Commands/TestCommands.cs | 38 +++++++++++++++++-- KfChatDotNetBot/Commands/TimeCommand.cs | 5 +-- KfChatDotNetBot/Commands/WhoisCommand.cs | 5 +-- KfChatDotNetBot/Services/BotCommands.cs | 42 +++++++++++---------- 8 files changed, 80 insertions(+), 49 deletions(-) diff --git a/KfChatDotNetBot/Commands/ICommand.cs b/KfChatDotNetBot/Commands/ICommand.cs index d162f9e..2cf5e0a 100644 --- a/KfChatDotNetBot/Commands/ICommand.cs +++ b/KfChatDotNetBot/Commands/ICommand.cs @@ -7,9 +7,10 @@ namespace KfChatDotNetBot.Commands; internal interface ICommand { List Patterns { get; } - string HelpText { get; } - bool HideFromHelp { get; } + // Set to null to disable help for a given command + string? HelpText { get; } UserRight RequiredRight { get; } + TimeSpan Timeout { get; } Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx); } \ No newline at end of file diff --git a/KfChatDotNetBot/Commands/JuiceCommand.cs b/KfChatDotNetBot/Commands/JuiceCommand.cs index 43013d0..0ca5dcb 100644 --- a/KfChatDotNetBot/Commands/JuiceCommand.cs +++ b/KfChatDotNetBot/Commands/JuiceCommand.cs @@ -14,6 +14,7 @@ public class JuiceCommand : ICommand public string HelpText => "Get juice!"; public bool HideFromHelp => false; public UserRight RequiredRight => UserRight.Guest; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); @@ -50,12 +51,13 @@ public class JuiceCommand : ICommand public class JuiceStatsCommand : ICommand { public List Patterns => [ - new Regex("^juice stats"), + new Regex("^juice stats$"), new Regex(@"^juice stats (?\d+)$") ]; public string HelpText => "Get juice stats!"; public bool HideFromHelp => false; public UserRight RequiredRight => UserRight.Guest; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { int top; diff --git a/KfChatDotNetBot/Commands/MemeCommands.cs b/KfChatDotNetBot/Commands/MemeCommands.cs index a6ca3af..c55642d 100644 --- a/KfChatDotNetBot/Commands/MemeCommands.cs +++ b/KfChatDotNetBot/Commands/MemeCommands.cs @@ -7,10 +7,9 @@ namespace KfChatDotNetBot.Commands; public class InsanityCommand : ICommand { public List Patterns => [new Regex("^insanity")]; - public string HelpText => "Insanity"; - public bool HideFromHelp => false; + public string? HelpText => "Insanity"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo @@ -21,10 +20,9 @@ public class InsanityCommand : ICommand public class TwistedCommand : ICommand { public List Patterns => [new Regex("^twisted")]; - public string HelpText => "Get it twisted"; - public bool HideFromHelp => false; + public string? HelpText => "Get it twisted"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo @@ -35,10 +33,9 @@ public class TwistedCommand : ICommand public class HelpMeCommand : ICommand { public List Patterns => [new Regex("^helpme")]; - public string HelpText => "Somebody please help me"; - public bool HideFromHelp => false; + public string? HelpText => "Somebody please help me"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo @@ -49,10 +46,9 @@ public class HelpMeCommand : ICommand public class SentCommand : ICommand { public List Patterns => [new Regex("^sent$")]; - public string HelpText => "Sent love"; - public bool HideFromHelp => false; + public string? HelpText => "Sent love"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { // ReSharper disable once StringLiteralTypo diff --git a/KfChatDotNetBot/Commands/RainbetCommands.cs b/KfChatDotNetBot/Commands/RainbetCommands.cs index a7b3626..acd611f 100644 --- a/KfChatDotNetBot/Commands/RainbetCommands.cs +++ b/KfChatDotNetBot/Commands/RainbetCommands.cs @@ -12,9 +12,9 @@ public class RainbetStatsCommand : ICommand public List Patterns => [ new Regex(@"^rainbet stats (?\d+)$") ]; - public string HelpText => "Get betting statistics in the given window"; - public bool HideFromHelp => false; + public string? HelpText => "Get betting statistics in the given window"; public UserRight RequiredRight => UserRight.Guest; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var window = Convert.ToInt32(arguments["window"].Value); @@ -28,7 +28,7 @@ public class RainbetStatsCommand : ICommand return; } var output = $"Rainbet stats for the last {window} hours (as seen on the bet feed):[br]" + - $"Bets: {bets.Count:N0}; Payout: ${bets.Sum(b => b.Payout):C}; Wagered: {bets.Sum(b => b.Value):C}"; + $"Bets: {bets.Count:N0}; Payout: {bets.Sum(b => b.Payout):C}; Wagered: {bets.Sum(b => b.Value):C}"; botInstance.SendChatMessage(output, true); } } @@ -38,9 +38,9 @@ public class RainbetRecentBetCommand : ICommand public List Patterns => [ new Regex(@"^rainbet recent$") ]; - public string HelpText => "Get the most recent 3 bets"; - public bool HideFromHelp => false; + public string? HelpText => "Get the most recent 3 bets"; public UserRight RequiredRight => UserRight.Guest; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await Helpers.GetMultipleValues([ diff --git a/KfChatDotNetBot/Commands/TestCommands.cs b/KfChatDotNetBot/Commands/TestCommands.cs index 9f829e8..3f561a5 100644 --- a/KfChatDotNetBot/Commands/TestCommands.cs +++ b/KfChatDotNetBot/Commands/TestCommands.cs @@ -13,10 +13,10 @@ public class EditTestCommand : ICommand new Regex("^test edit (?.+)") ]; - public string HelpText => "Test the editing functionality"; - public bool HideFromHelp => true; + 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(60); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var logger = LogManager.GetCurrentClassLogger(); @@ -49,4 +49,36 @@ public class EditTestCommand : ICommand await Task.Delay(delay, ctx); botInstance.KfClient.DeleteMessage(status.ChatMessageId!.Value); } +} + +public class TimeoutTestCommand : ICommand +{ + public List Patterns => [ + new Regex("^test timeout$") + ]; + + 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 async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) + { + await Task.Delay(TimeSpan.FromMinutes(1), ctx); + } +} + +public class ExceptionTestCommand : ICommand +{ + public List Patterns => [ + new Regex("^test exception$") + ]; + + 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 async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) + { + throw new Exception("Caused by the test exception command"); + } } \ No newline at end of file diff --git a/KfChatDotNetBot/Commands/TimeCommand.cs b/KfChatDotNetBot/Commands/TimeCommand.cs index 412baf4..c500753 100644 --- a/KfChatDotNetBot/Commands/TimeCommand.cs +++ b/KfChatDotNetBot/Commands/TimeCommand.cs @@ -7,10 +7,9 @@ namespace KfChatDotNetBot.Commands; public class TimeCommand : ICommand { public List Patterns => [new Regex("^time")]; - public string HelpText => "Get current time in BMT"; - public bool HideFromHelp => false; + public string? HelpText => "Get current time in BMT"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); 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/WhoisCommand.cs b/KfChatDotNetBot/Commands/WhoisCommand.cs index f7fb671..d1fbc6b 100644 --- a/KfChatDotNetBot/Commands/WhoisCommand.cs +++ b/KfChatDotNetBot/Commands/WhoisCommand.cs @@ -11,10 +11,9 @@ public class WhoisCommand : ICommand new Regex("^whois (?.+)") ]; - public string HelpText => "Lookup user IDs by username"; - public bool HideFromHelp => false; + public string? HelpText => "Lookup user IDs by username"; public UserRight RequiredRight => UserRight.Guest; - + public TimeSpan Timeout => TimeSpan.FromSeconds(10); public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { await using var db = new ApplicationDbContext(); diff --git a/KfChatDotNetBot/Services/BotCommands.cs b/KfChatDotNetBot/Services/BotCommands.cs index de859f1..065e3ea 100644 --- a/KfChatDotNetBot/Services/BotCommands.cs +++ b/KfChatDotNetBot/Services/BotCommands.cs @@ -1,5 +1,7 @@ -using Humanizer; +using System.Text.RegularExpressions; +using Humanizer; using KfChatDotNetBot.Commands; +using KfChatDotNetBot.Models.DbModels; using KfChatDotNetWsClient.Models.Events; using NLog; @@ -14,7 +16,6 @@ internal class BotCommands private char CommandPrefix = '!'; private IEnumerable Commands; private CancellationToken _cancellationToken; - private List _commandTasks = []; internal BotCommands(ChatBot bot, CancellationToken? ctx = null) { @@ -63,28 +64,29 @@ internal class BotCommands _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); break; } - var task = Task.Run(() => command.RunCommand(_bot, message, user, match.Groups, _cancellationToken), _cancellationToken); - _commandTasks.Add(task); + _ = ProcessMessageAsync(command, message, user, match.Groups); + break; } } - - // Check on the state of the tasks, there's no way to know what error they produce if they failed otherwise - List removals = []; - foreach (var task in _commandTasks) - { - if (!task.IsCompleted) continue; - if (task.IsFaulted) - { - _logger.Error("Command task failed at some point"); - _logger.Error(task.Exception); - } + } - removals.Add(task); - } - // .NET doesn't support modifying a collection you're iterating over - foreach (var removal in removals) + private async Task ProcessMessageAsync(ICommand command, MessageModel message, UserDbModel user, GroupCollection arguments) + { + var task = Task.Run(() => command.RunCommand(_bot, message, user, arguments, _cancellationToken), _cancellationToken); + try { - _commandTasks.Remove(removal); + await task.WaitAsync(command.Timeout, _cancellationToken); + } + catch (Exception e) + { + _logger.Error("Caught an exception while waiting for the command to complete"); + _logger.Error(e); + return; + } + if (task.IsFaulted) + { + _logger.Error("Command task failed"); + _logger.Error(task.Exception); } } } \ No newline at end of file