Browse Source

Merge b6a9ff5786 into 73ac9d7886

pull/934/merge
Joe4evr GitHub 7 years ago
parent
commit
9e0b5d75df
8 changed files with 200 additions and 123 deletions
  1. +42
    -18
      docs/guides/getting_started/samples/intro/structure.cs
  2. +23
    -5
      src/Discord.Net.Commands/Builders/ModuleBuilder.cs
  3. +18
    -16
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  4. +87
    -76
      src/Discord.Net.Commands/CommandService.cs
  5. +9
    -1
      src/Discord.Net.Commands/CommandServiceConfig.cs
  6. +3
    -1
      src/Discord.Net.Commands/IModuleBase.cs
  7. +11
    -5
      src/Discord.Net.Commands/Info/ModuleInfo.cs
  8. +7
    -1
      src/Discord.Net.Commands/ModuleBase.cs

+ 42
- 18
docs/guides/getting_started/samples/intro/structure.cs View File

@@ -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 = new CommandService();
private readonly CommandService _commands;
private readonly IServiceProvider _services;


private Program() private Program()
{ {
@@ -41,9 +41,40 @@ 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 += Logger;
_commands.Log += Logger; _commands.Log += Logger;
// 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()
{
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
@@ -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.
@@ -140,10 +162,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);
} }


+ 23
- 5
src/Discord.Net.Commands/Builders/ModuleBuilder.cs View File

@@ -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,31 @@ 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)
{
try
{
var moduleInstance = ReflectionUtils.CreateObject<IModuleBase>(TypeInfo, service, services);
moduleInstance.OnModuleBuilding(service);
}
catch (Exception)
{
//unsure of what to do here
throw;
}
}

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);
} }
} }

+ 18
- 16
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -42,8 +42,8 @@ namespace Discord.Commands
} }




public static Task<Dictionary<Type, ModuleInfo>> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service);
public static async Task<Dictionary<Type, ModuleInfo>> BuildAsync(IEnumerable<TypeInfo> validTypes, CommandService 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, 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);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
BuildModule(module, typeInfo, service, services);
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);
BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service);
BuildModule(module, typeInfo, service, services);
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;


+ 87
- 76
src/Discord.Net.Commands/CommandService.cs View File

@@ -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
{ {
@@ -27,6 +28,8 @@ namespace Discord.Commands
private readonly HashSet<ModuleInfo> _moduleDefs; private readonly HashSet<ModuleInfo> _moduleDefs;
private readonly CommandMap _map; private readonly CommandMap _map;


//internal readonly IServiceProvider _serviceProvider;

internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs;
internal readonly char _separatorChar; internal readonly char _separatorChar;
internal readonly RunMode _defaultRunMode; internal readonly RunMode _defaultRunMode;
@@ -74,6 +77,10 @@ namespace Discord.Commands
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IRole), typeof(RoleTypeReader<>))); entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IRole), typeof(RoleTypeReader<>)));
entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IUser), typeof(UserTypeReader<>))); entityTypeReaders.Add(new Tuple<Type, Type>(typeof(IUser), typeof(UserTypeReader<>)));
_entityTypeReaders = entityTypeReaders.ToImmutable(); _entityTypeReaders = entityTypeReaders.ToImmutable();

//_serviceProvider = config.ServiceProvider
// ?? config.ServiceProviderFactory?.Invoke(this)
// ?? EmptyServiceProvider.Instance;
} }


//Modules //Modules
@@ -85,7 +92,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 +101,8 @@ namespace Discord.Commands
_moduleLock.Release(); _moduleLock.Release();
} }
} }
public Task<ModuleInfo> AddModuleAsync<T>() => AddModuleAsync(typeof(T));
public async Task<ModuleInfo> AddModuleAsync(Type type)
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services);
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
{ {
await _moduleLock.WaitAsync().ConfigureAwait(false); await _moduleLock.WaitAsync().ConfigureAwait(false);
try try
@@ -104,7 +112,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 +126,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 +232,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 +285,95 @@ 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;

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)
//using (var scope = _serviceProvider.CreateScope())
using (var scope = services.CreateScope())
{ {
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false);
}
var searchResult = Search(context, input);
if (!searchResult.IsSuccess)
return searchResult;


var successfulPreconditions = preconditionResults
.Where(x => x.Value.IsSuccess)
.ToArray();
var commands = searchResult.Commands;
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>();


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;
}
foreach (var match in commands)
{
preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false);
}


//If we get this far, at least one precondition was successful.
var successfulPreconditions = preconditionResults
.Where(x => x.Value.IsSuccess)
.ToArray();


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 (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.


if (parseResult.Error == CommandError.MultipleMatches)
var parseResultsDict = new Dictionary<CommandMatch, ParseResult>();
foreach (var pair in successfulPreconditions)
{ {
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false);

if (parseResult.Error == CommandError.MultipleMatches)
{ {
case MultiMatchHandling.Best:
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;
IReadOnlyList<TypeReaderValue> argList, paramList;
switch (multiMatchHandling)
{
case MultiMatchHandling.Best:
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)
{
float argValuesScore = 0, paramValuesScore = 0;
if (match.Command.Parameters.Count > 0)
// Calculates the 'score' of a command given a parse result
float CalculateScore(CommandMatch match, ParseResult parseResult)
{ {
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;
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;
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;
}


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;
return match.Command.Priority + totalArgsScore * 0.99f;
}
//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));


//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();


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 (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);
} }

//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);
} }
} }
} }

+ 9
- 1
src/Discord.Net.Commands/CommandServiceConfig.cs View File

@@ -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;
} }
} }

+ 3
- 1
src/Discord.Net.Commands/IModuleBase.cs View File

@@ -1,4 +1,4 @@
namespace Discord.Commands
namespace Discord.Commands
{ {
internal interface IModuleBase internal interface IModuleBase
{ {
@@ -7,5 +7,7 @@
void BeforeExecute(CommandInfo command); void BeforeExecute(CommandInfo command);
void AfterExecute(CommandInfo command); void AfterExecute(CommandInfo command);

void OnModuleBuilding(CommandService commandService);
} }
} }

+ 11
- 5
src/Discord.Net.Commands/Info/ModuleInfo.cs View File

@@ -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;
} }


+ 7
- 1
src/Discord.Net.Commands/ModuleBase.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
@@ -23,6 +23,10 @@ namespace Discord.Commands
{ {
} }


protected virtual void OnModuleBuilding(CommandService commandService)
{
}

//IModuleBase //IModuleBase
void IModuleBase.SetContext(ICommandContext context) void IModuleBase.SetContext(ICommandContext context)
{ {
@@ -33,5 +37,7 @@ namespace Discord.Commands
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) => OnModuleBuilding(commandService);
} }
} }

Loading…
Cancel
Save