using Humanizer; using KfChatDotNetBot.Commands; using KfChatDotNetWsClient.Models.Events; using NLog; namespace KfChatDotNetBot.Services; // Took one look at GambaSesh's code, and it made my head explode // This implementation is inspired by similar bot I wrote years ago internal class BotCommands { private ChatBot _bot; private Logger _logger = LogManager.GetCurrentClassLogger(); private char CommandPrefix = '!'; private IEnumerable Commands; private CancellationToken _cancellationToken; private List _commandTasks = []; internal BotCommands(ChatBot bot, CancellationToken? ctx = null) { _cancellationToken = ctx ?? CancellationToken.None; _bot = bot; var interfaceType = typeof(ICommand); Commands = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) .Where(x => interfaceType.IsAssignableFrom(x) && x is { IsInterface: false, IsAbstract: false }) .Select(Activator.CreateInstance).Cast(); foreach (var command in Commands) { _logger.Debug($"Found command {command.GetType().Name}"); } } internal void ProcessMessage(MessageModel message) { if (string.IsNullOrEmpty(message.MessageRaw)) { return; } if (!message.MessageRaw.StartsWith(CommandPrefix)) { return; } var messageTrimmed = message.MessageRaw.TrimStart(CommandPrefix); foreach (var command in Commands) { foreach (var regex in command.Patterns) { var match = regex.Match(messageTrimmed); if (!match.Success) continue; _logger.Debug($"Message matches {regex}"); using var db = new ApplicationDbContext(); var user = db.Users.FirstOrDefault(u => u.KfId == message.Author.Id); // This should never happen as brand-new users are created upon join if (user == null) 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); break; } var task = Task.Run(() => command.RunCommand(_bot, message, user, match.Groups, _cancellationToken), _cancellationToken); _commandTasks.Add(task); } } // 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) { _commandTasks.Remove(removal); } } }