Medusa System Added

Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)
This commit is contained in:
Kwoth
2022-04-16 12:44:46 +00:00
parent 3a516ab32a
commit 7cb15f5278
103 changed files with 3363 additions and 203 deletions

View File

@@ -16,7 +16,6 @@ public class CommandHandler : INService, IReadyExecutor
public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; };
public event Func<CommandInfo, ITextChannel, string, Task> CommandErrored = delegate { return Task.CompletedTask; };
public event Func<IUserMessage, Task> OnMessageNoTrigger = delegate { return Task.CompletedTask; };
//userid/msg count
public ConcurrentDictionary<ulong, uint> UserMessagesSent { get; } = new();
@@ -27,7 +26,7 @@ public class CommandHandler : INService, IReadyExecutor
private readonly CommandService _commandService;
private readonly BotConfigService _bss;
private readonly Bot _bot;
private readonly IBehaviourExecutor _behaviourExecutor;
private readonly IBehaviorHandler _behaviorHandler;
private readonly IServiceProvider _services;
private readonly ConcurrentDictionary<ulong, string> _prefixes;
@@ -41,7 +40,7 @@ public class CommandHandler : INService, IReadyExecutor
CommandService commandService,
BotConfigService bss,
Bot bot,
IBehaviourExecutor behaviourExecutor,
IBehaviorHandler behaviorHandler,
// InteractionService interactions,
IServiceProvider services)
{
@@ -49,7 +48,7 @@ public class CommandHandler : INService, IReadyExecutor
_commandService = commandService;
_bss = bss;
_bot = bot;
_behaviourExecutor = behaviourExecutor;
_behaviorHandler = behaviorHandler;
_db = db;
_services = services;
// _interactions = interactions;
@@ -243,44 +242,53 @@ public class CommandHandler : INService, IReadyExecutor
{
var startTime = Environment.TickCount;
var blocked = await _behaviourExecutor.RunEarlyBehavioursAsync(guild, usrMsg);
var blocked = await _behaviorHandler.RunExecOnMessageAsync(guild, usrMsg);
if (blocked)
return;
var blockTime = Environment.TickCount - startTime;
var messageContent = await _behaviourExecutor.RunInputTransformersAsync(guild, usrMsg);
var messageContent = await _behaviorHandler.RunInputTransformersAsync(guild, usrMsg);
var prefix = GetPrefix(guild?.Id);
var isPrefixCommand = messageContent.StartsWith(".prefix", StringComparison.InvariantCultureIgnoreCase);
// execute the command and measure the time it took
if (messageContent.StartsWith(prefix, StringComparison.InvariantCulture) || isPrefixCommand)
{
var (success, error, info) = await ExecuteCommandAsync(new(_client, usrMsg),
var context = new CommandContext(_client, usrMsg);
var (success, error, info) = await ExecuteCommandAsync(context,
messageContent,
isPrefixCommand ? 1 : prefix.Length,
_services,
MultiMatchHandling.Best);
startTime = Environment.TickCount - startTime;
if (success)
// if a command is found
if (info is not null)
{
await LogSuccessfulExecution(usrMsg, channel as ITextChannel, blockTime, startTime);
await CommandExecuted(usrMsg, info);
return;
}
// if it successfully executed
if (success)
{
await LogSuccessfulExecution(usrMsg, channel as ITextChannel, blockTime, startTime);
await CommandExecuted(usrMsg, info);
await _behaviorHandler.RunPostCommandAsync(context, info.Module.GetTopLevelModule().Name, info);
return;
}
if (error is not null)
{
LogErroredExecution(error, usrMsg, channel as ITextChannel, blockTime, startTime);
if (guild is not null)
await CommandErrored(info, channel as ITextChannel, error);
// if it errored
if (error is not null)
{
LogErroredExecution(error, usrMsg, channel as ITextChannel, blockTime, startTime);
if (guild is not null)
await CommandErrored(info, channel as ITextChannel, error);
return;
}
}
}
else
await OnMessageNoTrigger(usrMsg);
await _behaviourExecutor.RunLateExecutorsAsync(guild, usrMsg);
await _behaviorHandler.RunOnNoCommandAsync(guild, usrMsg);
}
public Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommandAsync(
@@ -384,7 +392,7 @@ public class CommandHandler : INService, IReadyExecutor
return (false, null, cmd);
//return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.");
var blocked = await _behaviourExecutor.RunLateBlockersAsync(context, cmd);
var blocked = await _behaviorHandler.RunPreCommandAsync(context, cmd);
if (blocked)
return (false, null, cmd);

View File

@@ -1,12 +1,17 @@
#nullable disable
namespace NadekoBot.Services;
public interface IBehaviourExecutor
public interface IBehaviorHandler
{
public Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg);
public Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg);
Task<bool> RunLateBlockersAsync(ICommandContext context, CommandInfo cmd);
Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg);
public void Initialize();
Task<bool> AddAsync(ICustomBehavior behavior);
Task AddRangeAsync(IEnumerable<ICustomBehavior> behavior);
Task<bool> RemoveAsync(ICustomBehavior behavior);
Task RemoveRangeAsync(IEnumerable<ICustomBehavior> behs);
Task<bool> RunExecOnMessageAsync(SocketGuild guild, IUserMessage usrMsg);
Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg);
Task<bool> RunPreCommandAsync(ICommandContext context, CommandInfo cmd);
ValueTask RunPostCommandAsync(ICommandContext ctx, string moduleName, CommandInfo cmd);
Task RunOnNoCommandAsync(SocketGuild guild, IUserMessage usrMsg);
void Initialize();
}

View File

@@ -0,0 +1,13 @@
using NadekoBot.Common.ModuleBehaviors;
namespace NadekoBot.Services;
public interface ICustomBehavior
: IExecOnMessage,
IInputTransformer,
IExecPreCommand,
IExecNoCommand,
IExecPostCommand
{
}

View File

@@ -4,82 +4,299 @@ using NadekoBot.Common.ModuleBehaviors;
namespace NadekoBot.Services;
public sealed class BehaviorExecutor : IBehaviourExecutor, INService
// should be renamed to handler as it's not only executing
public sealed class BehaviorHandler : IBehaviorHandler, INService
{
private readonly IServiceProvider _services;
private IEnumerable<ILateExecutor> lateExecutors;
private IEnumerable<ILateBlocker> lateBlockers;
private IEnumerable<IEarlyBehavior> earlyBehaviors;
private IEnumerable<IInputTransformer> transformers;
private IReadOnlyCollection<IExecNoCommand> noCommandExecs;
private IReadOnlyCollection<IExecPreCommand> preCommandExecs;
private IReadOnlyCollection<IExecOnMessage> onMessageExecs;
private IReadOnlyCollection<IInputTransformer> inputTransformers;
public BehaviorExecutor(IServiceProvider services)
=> _services = services;
private readonly SemaphoreSlim _customLock = new(1, 1);
private readonly List<ICustomBehavior> _customExecs = new();
public BehaviorHandler(IServiceProvider services)
{
_services = services;
}
public void Initialize()
{
lateExecutors = _services.GetServices<ILateExecutor>();
lateBlockers = _services.GetServices<ILateBlocker>();
earlyBehaviors = _services.GetServices<IEarlyBehavior>().OrderByDescending(x => x.Priority);
transformers = _services.GetServices<IInputTransformer>();
noCommandExecs = _services.GetServices<IExecNoCommand>().ToArray();
preCommandExecs = _services.GetServices<IExecPreCommand>().ToArray();
onMessageExecs = _services.GetServices<IExecOnMessage>().OrderByDescending(x => x.Priority).ToArray();
inputTransformers = _services.GetServices<IInputTransformer>().ToArray();
}
public async Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg)
#region Add/Remove
public async Task AddRangeAsync(IEnumerable<ICustomBehavior> execs)
{
foreach (var beh in earlyBehaviors)
await _customLock.WaitAsync();
try
{
if (await beh.RunBehavior(guild, usrMsg))
foreach (var exe in execs)
{
if (_customExecs.Contains(exe))
continue;
_customExecs.Add(exe);
}
}
finally
{
_customLock.Release();
}
}
public async Task<bool> AddAsync(ICustomBehavior behavior)
{
await _customLock.WaitAsync();
try
{
if (_customExecs.Contains(behavior))
return false;
_customExecs.Add(behavior);
return true;
}
finally
{
_customLock.Release();
}
}
public async Task<bool> RemoveAsync(ICustomBehavior behavior)
{
await _customLock.WaitAsync();
try
{
return _customExecs.Remove(behavior);
}
finally
{
_customLock.Release();
}
}
public async Task RemoveRangeAsync(IEnumerable<ICustomBehavior> behs)
{
await _customLock.WaitAsync();
try
{
foreach(var beh in behs)
_customExecs.Remove(beh);
}
finally
{
_customLock.Release();
}
}
#endregion
#region Running
public async Task<bool> RunExecOnMessageAsync(SocketGuild guild, IUserMessage usrMsg)
{
async Task<bool> Exec<T>(IReadOnlyCollection<T> 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));
}
}
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<bool> RunPreCommandAsync(ICommandContext ctx, CommandInfo cmd)
{
async Task<bool> Exec<T>(IReadOnlyCollection<T> 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<T>(IReadOnlyCollection<T> 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<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg)
{
var messageContent = usrMsg.Content;
foreach (var exec in transformers)
async Task<string> Exec<T>(IReadOnlyCollection<T> execs, string content)
where T : IInputTransformer
{
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
!= messageContent.ToLowerInvariant())
foreach (var exec in execs)
{
messageContent = newContent;
break;
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;
}
return messageContent;
}
public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
{
foreach (var exec in lateBlockers)
var newContent = await Exec(inputTransformers, usrMsg.Content);
if (newContent is not null)
return newContent;
await _customLock.WaitAsync();
try
{
if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
{
Log.Information("Late blocking User [{User}] Command: [{Command}] in [{Module}]",
ctx.User,
cmd.Aliases[0],
exec.GetType().Name);
return true;
}
newContent = await Exec(_customExecs, usrMsg.Content);
if (newContent is not null)
return newContent;
}
finally
{
_customLock.Release();
}
return false;
return usrMsg.Content;
}
public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
public async ValueTask RunPostCommandAsync(ICommandContext ctx, string moduleName, CommandInfo cmd)
{
foreach (var exec in lateExecutors)
foreach (var exec in _customExecs)
{
try
{
await exec.LateExecute(guild, usrMsg);
await exec.ExecPostCommandAsync(ctx, moduleName, cmd.Name);
}
catch (Exception ex)
{
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}", exec.GetType().Name, ex.Message);
Log.Warning(ex,
"An error occured during PostCommand handling in {ExecName}: {ErrorMessage}",
GetExecName(exec),
ex.Message);
}
}
}
#endregion
}

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
public sealed class StatsService : IStatsService, IReadyExecutor, INService
{
public const string BOT_VERSION = "4.0.6";
public const string BOT_VERSION = "4.1.0";
public string Author
=> "Kwoth#2452";