#nullable disable using Microsoft.Extensions.DependencyInjection; using NadekoBot.Common.ModuleBehaviors; namespace NadekoBot.Services; // should be renamed to handler as it's not only executing public sealed class BehaviorHandler : IBehaviorHandler, INService { private readonly IServiceProvider _services; private IReadOnlyCollection noCommandExecs; private IReadOnlyCollection preCommandExecs; private IReadOnlyCollection onMessageExecs; private IReadOnlyCollection inputTransformers; private readonly SemaphoreSlim _customLock = new(1, 1); private readonly List _customExecs = new(); public BehaviorHandler(IServiceProvider services) { _services = services; } public void Initialize() { noCommandExecs = _services.GetServices().ToArray(); preCommandExecs = _services.GetServices().ToArray(); onMessageExecs = _services.GetServices().OrderByDescending(x => x.Priority).ToArray(); inputTransformers = _services.GetServices().ToArray(); } #region Add/Remove public async Task AddRangeAsync(IEnumerable execs) { await _customLock.WaitAsync(); try { foreach (var exe in execs) { if (_customExecs.Contains(exe)) continue; _customExecs.Add(exe); } } finally { _customLock.Release(); } } public async Task AddAsync(ICustomBehavior behavior) { await _customLock.WaitAsync(); try { if (_customExecs.Contains(behavior)) return false; _customExecs.Add(behavior); return true; } finally { _customLock.Release(); } } public async Task RemoveAsync(ICustomBehavior behavior) { await _customLock.WaitAsync(); try { return _customExecs.Remove(behavior); } finally { _customLock.Release(); } } public async Task RemoveRangeAsync(IEnumerable behs) { await _customLock.WaitAsync(); try { foreach(var beh in behs) _customExecs.Remove(beh); } finally { _customLock.Release(); } } #endregion #region Running public async Task RunExecOnMessageAsync(SocketGuild guild, IUserMessage usrMsg) { async Task Exec(IReadOnlyCollection execs) where T : IExecOnMessage { foreach (var exec in execs) { try { if (await exec.ExecOnMessageAsync(guild, usrMsg)) { Log.Information("{TypeName} blocked message g:{GuildId} u:{UserId} c:{ChannelId} msg:{Message}", GetExecName(exec), guild?.Id, usrMsg.Author.Id, usrMsg.Channel.Id, usrMsg.Content?.TrimTo(10)); return true; } } catch (Exception ex) { Log.Error(ex, "An error occurred in {TypeName} late blocker: {ErrorMessage}", GetExecName(exec), ex.Message); } } return false; } if (await Exec(onMessageExecs)) { return true; } await _customLock.WaitAsync(); try { if (await Exec(_customExecs)) return true; } finally { _customLock.Release(); } return false; } private string GetExecName(object exec) => exec is BehaviorAdapter ba ? ba.ToString() : exec.GetType().Name; public async Task RunPreCommandAsync(ICommandContext ctx, CommandInfo cmd) { async Task Exec(IReadOnlyCollection execs) where T: IExecPreCommand { foreach (var exec in execs) { try { if (await exec.ExecPreCommandAsync(ctx, cmd.Module.GetTopLevelModule().Name, cmd)) { Log.Information("{TypeName} Pre-Command blocked [{User}] Command: [{Command}]", GetExecName(exec), ctx.User, cmd.Aliases[0]); return true; } } catch (Exception ex) { Log.Error(ex, "An error occurred in {TypeName} PreCommand: {ErrorMessage}", GetExecName(exec), ex.Message); } } return false; } if (await Exec(preCommandExecs)) return true; await _customLock.WaitAsync(); try { if (await Exec(_customExecs)) return true; } finally { _customLock.Release(); } return false; } public async Task RunOnNoCommandAsync(SocketGuild guild, IUserMessage usrMsg) { async Task Exec(IReadOnlyCollection execs) where T : IExecNoCommand { foreach (var exec in execs) { try { await exec.ExecOnNoCommandAsync(guild, usrMsg); } catch (Exception ex) { Log.Error(ex, "An error occurred in {TypeName} OnNoCommand: {ErrorMessage}", GetExecName(exec), ex.Message); } } } await Exec(noCommandExecs); await _customLock.WaitAsync(); try { await Exec(_customExecs); } finally { _customLock.Release(); } } public async Task RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg) { async Task Exec(IReadOnlyCollection execs, string content) where T : IInputTransformer { foreach (var exec in execs) { try { var newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, content); if (newContent is not null) { Log.Information("{ExecName} transformed content {OldContent} -> {NewContent}", GetExecName(exec), content, newContent); return newContent; } } catch (Exception ex) { Log.Warning(ex, "An error occured during InputTransform handling: {ErrorMessage}", ex.Message); } } return null; } var newContent = await Exec(inputTransformers, usrMsg.Content); if (newContent is not null) return newContent; await _customLock.WaitAsync(); try { newContent = await Exec(_customExecs, usrMsg.Content); if (newContent is not null) return newContent; } finally { _customLock.Release(); } return usrMsg.Content; } public async ValueTask RunPostCommandAsync(ICommandContext ctx, string moduleName, CommandInfo cmd) { foreach (var exec in _customExecs) { try { await exec.ExecPostCommandAsync(ctx, moduleName, cmd.Name); } catch (Exception ex) { Log.Warning(ex, "An error occured during PostCommand handling in {ExecName}: {ErrorMessage}", GetExecName(exec), ex.Message); } } } #endregion }