Add callback method for when a module class has been added (#934)
commit 5b047bf02b4299f34172cac05dc7e4a84ecc108c Author: Joe4evr <jii.geugten@gmail.com> Date: Fri Feb 2 22:22:00 2018 +0100 [feature/OnModuleAdded] Quickstart fixes (#946) * Quickstart: fix minor derp * Other overdue fixes commit bd3e9eee943b9092cc45217b19ff95bae359f888 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 27 16:51:18 2018 -0500 Resort usings in ModuleBase commit 8042767579b337fdae7fe48e0a6ea2f007aef440 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 27 16:41:39 2018 -0500 Clean up removed owned IServiceProvider commit 30066cb102ffbd65906ead72a377811aa501abba Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 27 16:37:22 2018 -0500 Remove redundant try-catch around OnModuleBuilding invocation If this exception is going to be rethrown, there's no reason to include a try-catch. commit 60c7c31d4476c498a97ae0536ec5792f08efb89b Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 27 16:36:27 2018 -0500 Include the ModuleBuilder in OnModuleBuilding This allows modules hooking into OnModuleBuilding method to mutate theirselves at runtime. commit b6a9ff57860ff3bddbad7ca850fd331529cb8e6e Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 22 13:17:14 2018 +0100 #DERP commit f623d19c68c5642a44898a561f77ed82d53fd103 Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 22 13:15:31 2018 +0100 Resolution for #937 because it's literally 4 lines of code commit 8272c9675b0d63b4100aaf57f5067d635b68f5e6 Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 22 11:39:28 2018 +0100 Re-adjust quickstart commit e30b9071351b69baa30a93a4851516dca9ea43cf Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 22 11:35:08 2018 +0100 Undo experimental changes, request IServiceProvider instance everywhere instead commit ad7e0a46c8709e845dfacdc298a893e22dc11567 Author: Joe4evr <jii.geugten@gmail.com> Date: Fri Jan 19 03:40:27 2018 +0100 Fix quickstart leftover from previous draft commit e3349ef3d400bb3ad8cb28dd4234d5316a80bcc4 Author: Joe4evr <jii.geugten@gmail.com> Date: Fri Jan 19 03:33:46 2018 +0100 Doc comment on items commit 81bd9111faaf98a52679daae863ab04dce96e63e Author: Joe4evr <jii.geugten@gmail.com> Date: Fri Jan 19 03:16:44 2018 +0100 Add comment about the ServiceProviderFactory in the quickstart commit 72b5e6c8a149d8e989b46351965daa14f8ca318c Author: Joe4evr <jii.geugten@gmail.com> Date: Fri Jan 19 03:10:40 2018 +0100 Remove superfluous comments, provide simpler alternative for setting the ServiceProvider. commit 74b17b0e04e2c413397a2e1b66ff814615326205 Author: Joe4evr <jii.geugten@gmail.com> Date: Tue Jan 16 18:06:28 2018 +0100 Experimental change for feedback commit 7b100e99bb119be190006d1cd8e403776930e401 Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 15 23:34:06 2018 +0100 * Make the service provider parameters required * Adjust quickstart guide to reflect changes commit 7f1b792946ac6b950922b06178aa5cc37d9f4144 Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 15 20:04:37 2018 +0100 I..... missed one. commit 031b289d80604666dde62619e521af303203d48d Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 15 20:02:20 2018 +0100 Rename method to more intuitive 'OnModuleBuilding' commit 9a166ef1d0baecd21e4e5b965e2ac364feddbe2e Author: Joe4evr <jii.geugten@gmail.com> Date: Mon Jan 15 19:09:10 2018 +0100 Add callback method for when a module class has been added to the CommandService.
This commit is contained in:
@@ -19,10 +19,10 @@ class Program
|
|||||||
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
// Keep the CommandService and IServiceCollection around for use with commands.
|
// Keep the CommandService and DI container around for use with commands.
|
||||||
// These two types require you install the Discord.Net.Commands package.
|
// These two types require you install the Discord.Net.Commands package.
|
||||||
private readonly IServiceCollection _map = new ServiceCollection();
|
private readonly CommandService _commands;
|
||||||
private readonly CommandService _commands = new CommandService();
|
private readonly IServiceProvider _services;
|
||||||
|
|
||||||
private Program()
|
private Program()
|
||||||
{
|
{
|
||||||
@@ -41,14 +41,45 @@ class Program
|
|||||||
// add the `using` at the top, and uncomment this line:
|
// add the `using` at the top, and uncomment this line:
|
||||||
//WebSocketProvider = WS4NetProvider.Instance
|
//WebSocketProvider = WS4NetProvider.Instance
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_commands = new CommandService(new CommandServiceConfig
|
||||||
|
{
|
||||||
|
// Again, log level:
|
||||||
|
LogLevel = LogSeverity.Info,
|
||||||
|
|
||||||
|
// There's a few more properties you can set,
|
||||||
|
// for example, case-insensitive commands.
|
||||||
|
CaseSensitiveCommands = false,
|
||||||
|
});
|
||||||
|
|
||||||
// Subscribe the logging handler to both the client and the CommandService.
|
// Subscribe the logging handler to both the client and the CommandService.
|
||||||
_client.Log += Logger;
|
_client.Log += Log;
|
||||||
_commands.Log += Logger;
|
_commands.Log += Log;
|
||||||
|
|
||||||
|
// Setup your DI container.
|
||||||
|
_services = ConfigureServices(),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any services require the client, or the CommandService, or something else you keep on hand,
|
||||||
|
// pass them as parameters into this method as needed.
|
||||||
|
// If this method is getting pretty long, you can seperate it out into another file using partials.
|
||||||
|
private static IServiceProvider ConfigureServices()
|
||||||
|
{
|
||||||
|
var map = new ServiceCollection()
|
||||||
|
// Repeat this for all the service classes
|
||||||
|
// and other dependencies that your commands might need.
|
||||||
|
.AddSingleton(new SomeServiceClass());
|
||||||
|
|
||||||
|
// When all your required services are in the collection, build the container.
|
||||||
|
// Tip: There's an overload taking in a 'validateScopes' bool to make sure
|
||||||
|
// you haven't made any mistakes in your dependency graph.
|
||||||
|
return map.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example of a logging handler. This can be re-used by addons
|
// Example of a logging handler. This can be re-used by addons
|
||||||
// that ask for a Func<LogMessage, Task>.
|
// that ask for a Func<LogMessage, Task>.
|
||||||
private static Task Logger(LogMessage message)
|
private static Task Log(LogMessage message)
|
||||||
{
|
{
|
||||||
switch (message.Severity)
|
switch (message.Severity)
|
||||||
{
|
{
|
||||||
@@ -92,24 +123,15 @@ class Program
|
|||||||
await Task.Delay(Timeout.Infinite);
|
await Task.Delay(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceProvider _services;
|
|
||||||
|
|
||||||
private async Task InitCommands()
|
private async Task InitCommands()
|
||||||
{
|
{
|
||||||
// Repeat this for all the service classes
|
|
||||||
// and other dependencies that your commands might need.
|
|
||||||
_map.AddSingleton(new SomeServiceClass());
|
|
||||||
|
|
||||||
// When all your required services are in the collection, build the container.
|
|
||||||
// Tip: There's an overload taking in a 'validateScopes' bool to make sure
|
|
||||||
// you haven't made any mistakes in your dependency graph.
|
|
||||||
_services = _map.BuildServiceProvider();
|
|
||||||
|
|
||||||
// Either search the program and add all Module classes that can be found.
|
// Either search the program and add all Module classes that can be found.
|
||||||
// Module classes MUST be marked 'public' or they will be ignored.
|
// Module classes MUST be marked 'public' or they will be ignored.
|
||||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
|
// You also need to pass your 'IServiceProvider' instance now,
|
||||||
|
// so make sure that's done before you get here.
|
||||||
|
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||||
// Or add Modules manually if you prefer to be a little more explicit:
|
// Or add Modules manually if you prefer to be a little more explicit:
|
||||||
await _commands.AddModuleAsync<SomeModule>();
|
await _commands.AddModuleAsync<SomeModule>(_services);
|
||||||
// Note that the first one is 'Modules' (plural) and the second is 'Module' (singular).
|
// Note that the first one is 'Modules' (plural) and the second is 'Module' (singular).
|
||||||
|
|
||||||
// Subscribe a handler to see if a message invokes a command.
|
// Subscribe a handler to see if a message invokes a command.
|
||||||
@@ -123,8 +145,6 @@ class Program
|
|||||||
if (msg == null) return;
|
if (msg == null) return;
|
||||||
|
|
||||||
// We don't want the bot to respond to itself or other bots.
|
// We don't want the bot to respond to itself or other bots.
|
||||||
// NOTE: Selfbots should invert this first check and remove the second
|
|
||||||
// as they should ONLY be allowed to respond to messages from the same account.
|
|
||||||
if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return;
|
if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return;
|
||||||
|
|
||||||
// Create a number to track where the prefix ends and the command begins
|
// Create a number to track where the prefix ends and the command begins
|
||||||
@@ -140,10 +160,12 @@ class Program
|
|||||||
|
|
||||||
// Execute the command. (result does not indicate a return value,
|
// Execute the command. (result does not indicate a return value,
|
||||||
// rather an object stating if the command executed successfully).
|
// rather an object stating if the command executed successfully).
|
||||||
var result = await _commands.ExecuteAsync(context, pos, _services);
|
var result = await _commands.ExecuteAsync(context, pos);
|
||||||
|
|
||||||
// Uncomment the following lines if you want the bot
|
// Uncomment the following lines if you want the bot
|
||||||
// to send a message if it failed (not advised for most situations).
|
// to send a message if it failed.
|
||||||
|
// This does not catch errors from commands with 'RunMode.Async',
|
||||||
|
// subscribe a handler for '_commands.CommandExecuted' to see those.
|
||||||
//if (!result.IsSuccess && result.Error != CommandError.UnknownCommand)
|
//if (!result.IsSuccess && result.Error != CommandError.UnknownCommand)
|
||||||
// await msg.Channel.SendMessageAsync(result.ErrorReason);
|
// await msg.Channel.SendMessageAsync(result.ErrorReason);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ namespace Discord.Commands.Builders
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Summary { get; set; }
|
public string Summary { get; set; }
|
||||||
public string Remarks { get; set; }
|
public string Remarks { get; set; }
|
||||||
|
public string Group { get; set; }
|
||||||
|
|
||||||
public IReadOnlyList<CommandBuilder> Commands => _commands;
|
public IReadOnlyList<CommandBuilder> Commands => _commands;
|
||||||
public IReadOnlyList<ModuleBuilder> Modules => _submodules;
|
public IReadOnlyList<ModuleBuilder> Modules => _submodules;
|
||||||
@@ -25,6 +27,8 @@ namespace Discord.Commands.Builders
|
|||||||
public IReadOnlyList<Attribute> Attributes => _attributes;
|
public IReadOnlyList<Attribute> Attributes => _attributes;
|
||||||
public IReadOnlyList<string> Aliases => _aliases;
|
public IReadOnlyList<string> Aliases => _aliases;
|
||||||
|
|
||||||
|
internal TypeInfo TypeInfo { get; set; }
|
||||||
|
|
||||||
//Automatic
|
//Automatic
|
||||||
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
|
internal ModuleBuilder(CommandService service, ModuleBuilder parent)
|
||||||
{
|
{
|
||||||
@@ -111,17 +115,23 @@ namespace Discord.Commands.Builders
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModuleInfo BuildImpl(CommandService service, ModuleInfo parent = null)
|
private ModuleInfo BuildImpl(CommandService service, IServiceProvider services, ModuleInfo parent = null)
|
||||||
{
|
{
|
||||||
//Default name to first alias
|
//Default name to first alias
|
||||||
if (Name == null)
|
if (Name == null)
|
||||||
Name = _aliases[0];
|
Name = _aliases[0];
|
||||||
|
|
||||||
return new ModuleInfo(this, service, parent);
|
if (TypeInfo != null)
|
||||||
|
{
|
||||||
|
var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(TypeInfo, service, services);
|
||||||
|
moduleInstance.OnModuleBuilding(service, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ModuleInfo(this, service, services, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModuleInfo Build(CommandService service) => BuildImpl(service);
|
public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services);
|
||||||
|
|
||||||
internal ModuleInfo Build(CommandService service, ModuleInfo parent) => BuildImpl(service, parent);
|
internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service);
|
public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) => BuildAsync(validTypes, service, services);
|
||||||
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService service)
|
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService service, IServiceProvider services)
|
||||||
{
|
{
|
||||||
/*if (!validTypes.Any())
|
/*if (!validTypes.Any())
|
||||||
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
|
throw new InvalidOperationException("Could not find any valid modules from the given selection");*/
|
||||||
@@ -63,11 +63,11 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
var module = new ModuleBuilder(service, null);
|
var module = new ModuleBuilder(service, null);
|
||||||
|
|
||||||
BuildModule(module, typeInfo, service);
|
BuildModule(module, typeInfo, service, services);
|
||||||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
|
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services);
|
||||||
builtTypes.Add(typeInfo);
|
builtTypes.Add(typeInfo);
|
||||||
|
|
||||||
result[typeInfo.AsType()] = module.Build(service);
|
result[typeInfo.AsType()] = module.Build(service, services);
|
||||||
}
|
}
|
||||||
|
|
||||||
await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false);
|
await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false);
|
||||||
@@ -75,7 +75,7 @@ namespace Discord.Commands
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildSubTypes(ModuleBuilder builder, IEnumerable<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service)
|
private static void BuildSubTypes(ModuleBuilder builder, IEnumerable<TypeInfo> subTypes, List<TypeInfo> builtTypes, CommandService service, IServiceProvider services)
|
||||||
{
|
{
|
||||||
foreach (var typeInfo in subTypes)
|
foreach (var typeInfo in subTypes)
|
||||||
{
|
{
|
||||||
@@ -87,17 +87,18 @@ namespace Discord.Commands
|
|||||||
|
|
||||||
builder.AddModule((module) =>
|
builder.AddModule((module) =>
|
||||||
{
|
{
|
||||||
BuildModule(module, typeInfo, service);
|
BuildModule(module, typeInfo, service, services);
|
||||||
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
|
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services);
|
||||||
});
|
});
|
||||||
|
|
||||||
builtTypes.Add(typeInfo);
|
builtTypes.Add(typeInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service)
|
private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var attributes = typeInfo.GetCustomAttributes();
|
var attributes = typeInfo.GetCustomAttributes();
|
||||||
|
builder.TypeInfo = typeInfo;
|
||||||
|
|
||||||
foreach (var attribute in attributes)
|
foreach (var attribute in attributes)
|
||||||
{
|
{
|
||||||
@@ -117,6 +118,7 @@ namespace Discord.Commands
|
|||||||
break;
|
break;
|
||||||
case GroupAttribute group:
|
case GroupAttribute group:
|
||||||
builder.Name = builder.Name ?? group.Prefix;
|
builder.Name = builder.Name ?? group.Prefix;
|
||||||
|
builder.Group = group.Prefix;
|
||||||
builder.AddAliases(group.Prefix);
|
builder.AddAliases(group.Prefix);
|
||||||
break;
|
break;
|
||||||
case PreconditionAttribute precondition:
|
case PreconditionAttribute precondition:
|
||||||
@@ -140,12 +142,12 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
builder.AddCommand((command) =>
|
builder.AddCommand((command) =>
|
||||||
{
|
{
|
||||||
BuildCommand(command, typeInfo, method, service);
|
BuildCommand(command, typeInfo, method, service, services);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service)
|
private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service, IServiceProvider serviceprovider)
|
||||||
{
|
{
|
||||||
var attributes = method.GetCustomAttributes();
|
var attributes = method.GetCustomAttributes();
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
builder.AddParameter((parameter) =>
|
builder.AddParameter((parameter) =>
|
||||||
{
|
{
|
||||||
BuildParameter(parameter, paramInfo, pos++, count, service);
|
BuildParameter(parameter, paramInfo, pos++, count, service, serviceprovider);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +229,7 @@ namespace Discord.Commands
|
|||||||
builder.Callback = ExecuteCallback;
|
builder.Callback = ExecuteCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service)
|
private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var attributes = paramInfo.GetCustomAttributes();
|
var attributes = paramInfo.GetCustomAttributes();
|
||||||
var paramType = paramInfo.ParameterType;
|
var paramType = paramInfo.ParameterType;
|
||||||
@@ -245,7 +247,7 @@ namespace Discord.Commands
|
|||||||
builder.Summary = summary.Text;
|
builder.Summary = summary.Text;
|
||||||
break;
|
break;
|
||||||
case OverrideTypeReaderAttribute typeReader:
|
case OverrideTypeReaderAttribute typeReader:
|
||||||
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader);
|
builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader, services);
|
||||||
break;
|
break;
|
||||||
case ParamArrayAttribute _:
|
case ParamArrayAttribute _:
|
||||||
builder.IsMultiple = true;
|
builder.IsMultiple = true;
|
||||||
@@ -285,7 +287,7 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType)
|
private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var readers = service.GetTypeReaders(paramType);
|
var readers = service.GetTypeReaders(paramType);
|
||||||
TypeReader reader = null;
|
TypeReader reader = null;
|
||||||
@@ -296,7 +298,7 @@ namespace Discord.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
//We dont have a cached type reader, create one
|
//We dont have a cached type reader, create one
|
||||||
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance);
|
reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, services);
|
||||||
service.AddTypeReader(paramType, reader);
|
service.AddTypeReader(paramType, reader);
|
||||||
|
|
||||||
return reader;
|
return reader;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Discord.Commands.Builders;
|
|
||||||
using Discord.Logging;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -8,6 +6,9 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Discord.Commands.Builders;
|
||||||
|
using Discord.Logging;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
@@ -85,7 +86,8 @@ namespace Discord.Commands
|
|||||||
var builder = new ModuleBuilder(this, null, primaryAlias);
|
var builder = new ModuleBuilder(this, null, primaryAlias);
|
||||||
buildFunc(builder);
|
buildFunc(builder);
|
||||||
|
|
||||||
var module = builder.Build(this);
|
var module = builder.Build(this, null);
|
||||||
|
|
||||||
return LoadModuleInternal(module);
|
return LoadModuleInternal(module);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -93,8 +95,8 @@ namespace Discord.Commands
|
|||||||
_moduleLock.Release();
|
_moduleLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Task<ModuleInfo> AddModuleAsync<T>() => AddModuleAsync(typeof(T));
|
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services);
|
||||||
public async Task<ModuleInfo> AddModuleAsync(Type type)
|
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
|
||||||
{
|
{
|
||||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
@@ -104,7 +106,7 @@ namespace Discord.Commands
|
|||||||
if (_typedModuleDefs.ContainsKey(type))
|
if (_typedModuleDefs.ContainsKey(type))
|
||||||
throw new ArgumentException($"This module has already been added.");
|
throw new ArgumentException($"This module has already been added.");
|
||||||
|
|
||||||
var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault();
|
var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault();
|
||||||
|
|
||||||
if (module.Value == default(ModuleInfo))
|
if (module.Value == default(ModuleInfo))
|
||||||
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
|
throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?");
|
||||||
@@ -118,13 +120,13 @@ namespace Discord.Commands
|
|||||||
_moduleLock.Release();
|
_moduleLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly)
|
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services)
|
||||||
{
|
{
|
||||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false);
|
var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false);
|
||||||
var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false);
|
var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var info in moduleDefs)
|
foreach (var info in moduleDefs)
|
||||||
{
|
{
|
||||||
@@ -224,7 +226,7 @@ namespace Discord.Commands
|
|||||||
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
|
var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary<Type, TypeReader>());
|
||||||
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
|
var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader);
|
||||||
readers[nullableReader.GetType()] = nullableReader;
|
readers[nullableReader.GetType()] = nullableReader;
|
||||||
}
|
}
|
||||||
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
|
internal IDictionary<Type, TypeReader> GetTypeReaders(Type type)
|
||||||
{
|
{
|
||||||
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
|
if (_typeReaders.TryGetValue(type, out var definedTypeReaders))
|
||||||
@@ -277,92 +279,94 @@ namespace Discord.Commands
|
|||||||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
|
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
|
||||||
{
|
{
|
||||||
services = services ?? EmptyServiceProvider.Instance;
|
services = services ?? EmptyServiceProvider.Instance;
|
||||||
|
using (var scope = services.CreateScope())
|
||||||
var searchResult = Search(context, input);
|
|
||||||
if (!searchResult.IsSuccess)
|
|
||||||
return searchResult;
|
|
||||||
|
|
||||||
var commands = searchResult.Commands;
|
|
||||||
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
|
|
||||||
|
|
||||||
foreach (var match in commands)
|
|
||||||
{
|
{
|
||||||
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
|
var searchResult = Search(context, input);
|
||||||
}
|
if (!searchResult.IsSuccess)
|
||||||
|
return searchResult;
|
||||||
|
|
||||||
var successfulPreconditions = preconditionResults
|
var commands = searchResult.Commands;
|
||||||
.Where(x => x.Value.IsSuccess)
|
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (successfulPreconditions.Length == 0)
|
foreach (var match in commands)
|
||||||
{
|
|
||||||
//All preconditions failed, return the one from the highest priority command
|
|
||||||
var bestCandidate = preconditionResults
|
|
||||||
.OrderByDescending(x => x.Key.Command.Priority)
|
|
||||||
.FirstOrDefault(x => !x.Value.IsSuccess);
|
|
||||||
return bestCandidate.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If we get this far, at least one precondition was successful.
|
|
||||||
|
|
||||||
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
|
|
||||||
foreach (var pair in successfulPreconditions)
|
|
||||||
{
|
|
||||||
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (parseResult.Error == CommandError.MultipleMatches)
|
|
||||||
{
|
{
|
||||||
IReadOnlyList<TypeReaderValue> argList, paramList;
|
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false);
|
||||||
switch (multiMatchHandling)
|
}
|
||||||
|
|
||||||
|
var successfulPreconditions = preconditionResults
|
||||||
|
.Where(x => x.Value.IsSuccess)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (successfulPreconditions.Length == 0)
|
||||||
|
{
|
||||||
|
//All preconditions failed, return the one from the highest priority command
|
||||||
|
var bestCandidate = preconditionResults
|
||||||
|
.OrderByDescending(x => x.Key.Command.Priority)
|
||||||
|
.FirstOrDefault(x => !x.Value.IsSuccess);
|
||||||
|
return bestCandidate.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we get this far, at least one precondition was successful.
|
||||||
|
|
||||||
|
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
|
||||||
|
foreach (var pair in successfulPreconditions)
|
||||||
|
{
|
||||||
|
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (parseResult.Error == CommandError.MultipleMatches)
|
||||||
{
|
{
|
||||||
case MultiMatchHandling.Best:
|
IReadOnlyList<TypeReaderValue> argList, paramList;
|
||||||
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
switch (multiMatchHandling)
|
||||||
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
{
|
||||||
parseResult = ParseResult.FromSuccess(argList, paramList);
|
case MultiMatchHandling.Best:
|
||||||
break;
|
argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
||||||
|
paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray();
|
||||||
|
parseResult = ParseResult.FromSuccess(argList, paramList);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseResultsDict[pair.Key] = parseResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseResultsDict[pair.Key] = parseResult;
|
// Calculates the 'score' of a command given a parse result
|
||||||
}
|
float CalculateScore(CommandMatch match, ParseResult parseResult)
|
||||||
|
|
||||||
// Calculates the 'score' of a command given a parse result
|
|
||||||
float CalculateScore(CommandMatch match, ParseResult parseResult)
|
|
||||||
{
|
|
||||||
float argValuesScore = 0, paramValuesScore = 0;
|
|
||||||
|
|
||||||
if (match.Command.Parameters.Count > 0)
|
|
||||||
{
|
{
|
||||||
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
|
float argValuesScore = 0, paramValuesScore = 0;
|
||||||
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
|
|
||||||
|
|
||||||
argValuesScore = argValuesSum / match.Command.Parameters.Count;
|
if (match.Command.Parameters.Count > 0)
|
||||||
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
|
{
|
||||||
|
var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
|
||||||
|
var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0;
|
||||||
|
|
||||||
|
argValuesScore = argValuesSum / match.Command.Parameters.Count;
|
||||||
|
paramValuesScore = paramValuesSum / match.Command.Parameters.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
|
||||||
|
return match.Command.Priority + totalArgsScore * 0.99f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalArgsScore = (argValuesScore + paramValuesScore) / 2;
|
//Order the parse results by their score so that we choose the most likely result to execute
|
||||||
return match.Command.Priority + totalArgsScore * 0.99f;
|
var parseResults = parseResultsDict
|
||||||
|
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
|
||||||
|
|
||||||
|
var successfulParses = parseResults
|
||||||
|
.Where(x => x.Value.IsSuccess)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (successfulParses.Length == 0)
|
||||||
|
{
|
||||||
|
//All parses failed, return the one from the highest priority command, using score as a tie breaker
|
||||||
|
var bestMatch = parseResults
|
||||||
|
.FirstOrDefault(x => !x.Value.IsSuccess);
|
||||||
|
return bestMatch.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we get this far, at least one parse was successful. Execute the most likely overload.
|
||||||
|
var chosenOverload = successfulParses[0];
|
||||||
|
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, scope.ServiceProvider).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Order the parse results by their score so that we choose the most likely result to execute
|
|
||||||
var parseResults = parseResultsDict
|
|
||||||
.OrderByDescending(x => CalculateScore(x.Key, x.Value));
|
|
||||||
|
|
||||||
var successfulParses = parseResults
|
|
||||||
.Where(x => x.Value.IsSuccess)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (successfulParses.Length == 0)
|
|
||||||
{
|
|
||||||
//All parses failed, return the one from the highest priority command, using score as a tie breaker
|
|
||||||
var bestMatch = parseResults
|
|
||||||
.FirstOrDefault(x => !x.Value.IsSuccess);
|
|
||||||
return bestMatch.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If we get this far, at least one parse was successful. Execute the most likely overload.
|
|
||||||
var chosenOverload = successfulParses[0];
|
|
||||||
return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Discord.Commands
|
using System;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
public class CommandServiceConfig
|
public class CommandServiceConfig
|
||||||
{
|
{
|
||||||
@@ -18,5 +20,11 @@
|
|||||||
|
|
||||||
/// <summary> Determines whether extra parameters should be ignored. </summary>
|
/// <summary> Determines whether extra parameters should be ignored. </summary>
|
||||||
public bool IgnoreExtraArgs { get; set; } = false;
|
public bool IgnoreExtraArgs { get; set; } = false;
|
||||||
|
|
||||||
|
///// <summary> Gets or sets the <see cref="IServiceProvider"/> to use. </summary>
|
||||||
|
//public IServiceProvider ServiceProvider { get; set; } = null;
|
||||||
|
|
||||||
|
///// <summary> Gets or sets a factory function for the <see cref="IServiceProvider"/> to use. </summary>
|
||||||
|
//public Func<CommandService, IServiceProvider> ServiceProviderFactory { get; set; } = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Discord.Commands
|
using Discord.Commands.Builders;
|
||||||
|
|
||||||
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
internal interface IModuleBase
|
internal interface IModuleBase
|
||||||
{
|
{
|
||||||
@@ -7,5 +9,7 @@
|
|||||||
void BeforeExecute(CommandInfo command);
|
void BeforeExecute(CommandInfo command);
|
||||||
|
|
||||||
void AfterExecute(CommandInfo command);
|
void AfterExecute(CommandInfo command);
|
||||||
|
|
||||||
|
void OnModuleBuilding(CommandService commandService, ModuleBuilder builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Reflection;
|
||||||
using Discord.Commands.Builders;
|
using Discord.Commands.Builders;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
@@ -13,6 +13,7 @@ namespace Discord.Commands
|
|||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string Summary { get; }
|
public string Summary { get; }
|
||||||
public string Remarks { get; }
|
public string Remarks { get; }
|
||||||
|
public string Group { get; }
|
||||||
|
|
||||||
public IReadOnlyList<string> Aliases { get; }
|
public IReadOnlyList<string> Aliases { get; }
|
||||||
public IReadOnlyList<CommandInfo> Commands { get; }
|
public IReadOnlyList<CommandInfo> Commands { get; }
|
||||||
@@ -22,21 +23,26 @@ namespace Discord.Commands
|
|||||||
public ModuleInfo Parent { get; }
|
public ModuleInfo Parent { get; }
|
||||||
public bool IsSubmodule => Parent != null;
|
public bool IsSubmodule => Parent != null;
|
||||||
|
|
||||||
internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null)
|
//public TypeInfo TypeInfo { get; }
|
||||||
|
|
||||||
|
internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null)
|
||||||
{
|
{
|
||||||
Service = service;
|
Service = service;
|
||||||
|
|
||||||
Name = builder.Name;
|
Name = builder.Name;
|
||||||
Summary = builder.Summary;
|
Summary = builder.Summary;
|
||||||
Remarks = builder.Remarks;
|
Remarks = builder.Remarks;
|
||||||
|
Group = builder.Group;
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
|
|
||||||
|
//TypeInfo = builder.TypeInfo;
|
||||||
|
|
||||||
Aliases = BuildAliases(builder, service).ToImmutableArray();
|
Aliases = BuildAliases(builder, service).ToImmutableArray();
|
||||||
Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray();
|
Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray();
|
||||||
Preconditions = BuildPreconditions(builder).ToImmutableArray();
|
Preconditions = BuildPreconditions(builder).ToImmutableArray();
|
||||||
Attributes = BuildAttributes(builder).ToImmutableArray();
|
Attributes = BuildAttributes(builder).ToImmutableArray();
|
||||||
|
|
||||||
Submodules = BuildSubmodules(builder, service).ToImmutableArray();
|
Submodules = BuildSubmodules(builder, service, services).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> BuildAliases(ModuleBuilder builder, CommandService service)
|
private static IEnumerable<string> BuildAliases(ModuleBuilder builder, CommandService service)
|
||||||
@@ -66,12 +72,12 @@ namespace Discord.Commands
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ModuleInfo> BuildSubmodules(ModuleBuilder parent, CommandService service)
|
private List<ModuleInfo> BuildSubmodules(ModuleBuilder parent, CommandService service, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var result = new List<ModuleInfo>();
|
var result = new List<ModuleInfo>();
|
||||||
|
|
||||||
foreach (var submodule in parent.Modules)
|
foreach (var submodule in parent.Modules)
|
||||||
result.Add(submodule.Build(service, this));
|
result.Add(submodule.Build(service, services, this));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Discord.Commands.Builders;
|
||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
@@ -23,15 +24,18 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//IModuleBase
|
//IModuleBase
|
||||||
void IModuleBase.SetContext(ICommandContext context)
|
void IModuleBase.SetContext(ICommandContext context)
|
||||||
{
|
{
|
||||||
var newValue = context as T;
|
var newValue = context as T;
|
||||||
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
|
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
|
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
|
||||||
|
|
||||||
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
|
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
|
||||||
|
void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user