mirror of https://github.com/oliverbooth/VpSharp
Add Commands extension
This commit is contained in:
parent
e0666d54e4
commit
535f9fb9e8
|
@ -32,6 +32,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
|
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
|
||||||
|
dotnet pack VpSharp.Commands -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Push NuGet Package to GitHub
|
- name: Push NuGet Package to GitHub
|
||||||
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
||||||
|
|
|
@ -32,6 +32,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
|
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
|
||||||
|
dotnet pack VpSharp.Commands -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
|
||||||
|
|
||||||
- name: Push NuGet Package to GitHub
|
- name: Push NuGet Package to GitHub
|
||||||
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
||||||
|
|
|
@ -32,6 +32,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
|
dotnet pack VpSharp -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
|
||||||
|
dotnet pack VpSharp.Commands -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
|
||||||
|
|
||||||
- name: Push NuGet Package to GitHub
|
- name: Push NuGet Package to GitHub
|
||||||
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
namespace VpSharp.Commands.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the aliases of a command.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class AliasesAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AliasesAttribute" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="alias">The first alias.</param>
|
||||||
|
/// <param name="aliases">Additional aliases.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <para><paramref name="alias" /> is <see langword="null" />.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para><paramref name="aliases" /> is <see langword="null" />.</para>
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <para><paramref name="alias" /> is empty, or consists of only whitespace.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>An element in <paramref name="aliases" /> is null, empty, or consists of only whitespace.</para>
|
||||||
|
/// </exception>
|
||||||
|
public AliasesAttribute(string alias, params string[] aliases)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(alias);
|
||||||
|
ArgumentNullException.ThrowIfNull(aliases);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(alias))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Alias cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string a in aliases)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(a))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot have a null alias");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Aliases = new string[aliases.Length + 1];
|
||||||
|
Aliases[0] = alias;
|
||||||
|
Array.Copy(aliases, 0, Aliases, 1, aliases.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command aliases.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The command aliases.</value>
|
||||||
|
public string[] Aliases { get; }
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
namespace VpSharp.Commands.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the name of a command.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class CommandAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommandAttribute" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The command name.</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="name" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="ArgumentException"><paramref name="name" /> is empty, or consists of only whitespace.</exception>
|
||||||
|
public CommandAttribute(string name)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(name);
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Name cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The command name.</value>
|
||||||
|
public string Name { get; }
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace VpSharp.Commands.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a string parameter should consume the remainder of the arguments as one string.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
public sealed class RemainderAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a registered command.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Command
|
||||||
|
{
|
||||||
|
internal Command(string name, string[] aliases, MethodInfo method, CommandModule module)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Aliases = aliases[..];
|
||||||
|
Method = method;
|
||||||
|
Module = module;
|
||||||
|
Parameters = method.GetParameters()[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the aliases for this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The aliases.</value>
|
||||||
|
public string[] Aliases { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
internal MethodInfo Method { get; }
|
||||||
|
|
||||||
|
internal CommandModule Module { get; }
|
||||||
|
|
||||||
|
internal ParameterInfo[] Parameters { get; }
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System.Drawing;
|
||||||
|
using VpSharp.Entities;
|
||||||
|
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides metadata about a command invocation.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CommandContext
|
||||||
|
{
|
||||||
|
private readonly VirtualParadiseClient _client;
|
||||||
|
|
||||||
|
internal CommandContext(VirtualParadiseClient client, VirtualParadiseAvatar avatar, string commandName, string alias,
|
||||||
|
string rawArguments)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
Avatar = avatar;
|
||||||
|
CommandName = commandName;
|
||||||
|
Alias = alias;
|
||||||
|
RawArguments = rawArguments;
|
||||||
|
Arguments = rawArguments.Split();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the alias that was used to invoke the command.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The alias used.</value>
|
||||||
|
public string Alias { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the arguments of the command.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The arguments passed by the avatar.</value>
|
||||||
|
public string[] Arguments { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the avatar who executed the command.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The executing avatar.</value>
|
||||||
|
public VirtualParadiseAvatar Avatar { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name of the command being executed.</value>
|
||||||
|
public string CommandName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw argument string as sent by the avatar.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The raw argument string.</value>
|
||||||
|
public string RawArguments { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a response message to the command.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to send.</param>
|
||||||
|
/// <param name="ephemeral">
|
||||||
|
/// <see langword="true" /> to respond only to the avatar which sent the command; <see langword="false" /> to send a
|
||||||
|
/// regular chat message.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The message which was sent.</returns>
|
||||||
|
public Task<VirtualParadiseMessage> RespondAsync(string message, bool ephemeral = false)
|
||||||
|
{
|
||||||
|
return ephemeral
|
||||||
|
? Avatar.SendMessageAsync(_client.CurrentAvatar?.Name, message, FontStyle.Regular, Color.Black)
|
||||||
|
: _client.SendMessageAsync(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the base class for command modules.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class CommandModule
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using VpSharp.ClientExtensions;
|
||||||
|
using VpSharp.Commands.Attributes;
|
||||||
|
using VpSharp.EventData;
|
||||||
|
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the Commands extension for <see cref="VirtualParadiseClient" />.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CommandsExtension : VirtualParadiseClientExtension
|
||||||
|
{
|
||||||
|
private const BindingFlags BindingFlags = System.Reflection.BindingFlags.Public |
|
||||||
|
System.Reflection.BindingFlags.NonPublic |
|
||||||
|
System.Reflection.BindingFlags.Instance;
|
||||||
|
|
||||||
|
private readonly CommandsExtensionConfiguration _configuration;
|
||||||
|
private readonly Dictionary<string, Command> _commandMap = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommandsExtension" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The owning client.</param>
|
||||||
|
/// <param name="configuration">The configuration to use.</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="configuration" /> is <see langword="null" />.</exception>
|
||||||
|
public CommandsExtension(VirtualParadiseClient client, CommandsExtensionConfiguration configuration)
|
||||||
|
: base(client)
|
||||||
|
{
|
||||||
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers all commands from all modules in the specified assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The assembly whose command modules to register.</param>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
///
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="TypeInitializationException">A command module could not be instantiated.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// <para>A command in the specified assembly does not have a <see cref="CommandContext" /> as its first parameter.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>A command in the specified assembly contains a duplicate name or alias for a command.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>
|
||||||
|
/// A command in the specified assembly has <see cref="RemainderAttribute" /> on a parameter that is not the last in
|
||||||
|
/// the parameter list.
|
||||||
|
/// </para>
|
||||||
|
/// </exception>
|
||||||
|
public void RegisterCommands(Assembly assembly)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(assembly);
|
||||||
|
|
||||||
|
foreach (Type type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (!type.IsAbstract && type.IsSubclassOf(typeof(CommandModule)))
|
||||||
|
{
|
||||||
|
RegisterCommands(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the command
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <typeparamref name="T" /> refers to a type that does not inherit <see cref="CommandModule" />.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="TypeInitializationException"><typeparamref name="T" /> could not be instantiated.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// <para>A command in the specified module does not have a <see cref="CommandContext" /> as its first parameter.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>A command in the specified module contains a duplicate name or alias for a command.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>
|
||||||
|
/// A command in the specified module has <see cref="RemainderAttribute" /> on a parameter that is not the last in the
|
||||||
|
/// parameter list.
|
||||||
|
/// </para>
|
||||||
|
/// </exception>
|
||||||
|
public void RegisterCommands<T>() where T : CommandModule
|
||||||
|
{
|
||||||
|
RegisterCommands(typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moduleType"></param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="moduleType" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// <para><paramref name="moduleType" /> refers to an <c>abstract</c> type.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para><paramref name="moduleType" /> refers to a type that does not inherit <see cref="CommandModule" />.</para>
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="TypeInitializationException"><paramref name="moduleType" /> could not be instantiated.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// <para>A command in the specified module does not have a <see cref="CommandContext" /> as its first parameter.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>A command in the specified module contains a duplicate name or alias for a command.</para>
|
||||||
|
/// -or-
|
||||||
|
/// <para>
|
||||||
|
/// A command in the specified module has <see cref="RemainderAttribute" /> on a parameter that is not the last in the
|
||||||
|
/// parameter list.
|
||||||
|
/// </para>
|
||||||
|
/// </exception>
|
||||||
|
public void RegisterCommands(Type moduleType)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(moduleType);
|
||||||
|
|
||||||
|
if (moduleType.IsAbstract)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Module type cannot be abstract");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moduleType.IsSubclassOf(typeof(CommandModule)))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Module type is not a subclass of {typeof(CommandModule)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Activator.CreateInstance(moduleType) is not CommandModule module)
|
||||||
|
{
|
||||||
|
var innerException = new Exception($"Could not instantiate {moduleType.FullName}");
|
||||||
|
throw new TypeInitializationException(moduleType.FullName, innerException);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MethodInfo method in moduleType.GetMethods(BindingFlags))
|
||||||
|
{
|
||||||
|
RegisterCommandMethod(module, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Task OnMessageReceived(MessageReceivedEventArgs args)
|
||||||
|
{
|
||||||
|
var message = args.Message;
|
||||||
|
|
||||||
|
if (message.Type != MessageType.ChatMessage)
|
||||||
|
{
|
||||||
|
return base.OnMessageReceived(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ReadOnlySpan<char> prefix in _configuration.Prefixes)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> content = message.Content;
|
||||||
|
if (!content.StartsWith(prefix))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<char> commandName, rawArguments;
|
||||||
|
int spaceIndex = content.IndexOf(' ');
|
||||||
|
|
||||||
|
if (spaceIndex == -1)
|
||||||
|
{
|
||||||
|
commandName = content[prefix.Length..];
|
||||||
|
rawArguments = ReadOnlySpan<char>.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandName = content[prefix.Length..spaceIndex];
|
||||||
|
rawArguments = content[(spaceIndex + 1)..];
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandNameString = commandName.ToString();
|
||||||
|
if (!_commandMap.TryGetValue(commandNameString, out Command? command))
|
||||||
|
{
|
||||||
|
return base.OnMessageReceived(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = new CommandContext(Client, message.Author, command.Name, commandNameString, rawArguments.ToString());
|
||||||
|
object?[] arguments = {context};
|
||||||
|
|
||||||
|
if (rawArguments.Length > 0)
|
||||||
|
{
|
||||||
|
spaceIndex = rawArguments.IndexOf(' ');
|
||||||
|
if (spaceIndex == -1)
|
||||||
|
{
|
||||||
|
Array.Resize(ref arguments, 2);
|
||||||
|
arguments[1] = rawArguments.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var appendLast = true;
|
||||||
|
|
||||||
|
for (var argumentIndex = 1; spaceIndex > -1; argumentIndex++)
|
||||||
|
{
|
||||||
|
Array.Resize(ref arguments, argumentIndex + 1);
|
||||||
|
|
||||||
|
if (argumentIndex == command.Parameters.Length)
|
||||||
|
{
|
||||||
|
if (command.Parameters[argumentIndex - 1].GetCustomAttribute<RemainderAttribute>() is not null)
|
||||||
|
{
|
||||||
|
appendLast = false;
|
||||||
|
arguments[argumentIndex] = rawArguments.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments[argumentIndex] = rawArguments[..spaceIndex].ToString();
|
||||||
|
rawArguments = rawArguments[(spaceIndex + 1)..];
|
||||||
|
spaceIndex = rawArguments.IndexOf(' ');
|
||||||
|
argumentIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appendLast)
|
||||||
|
{
|
||||||
|
Array.Resize(ref arguments, arguments.Length + 1);
|
||||||
|
arguments[^1] = rawArguments.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(string.Join(';', arguments));
|
||||||
|
object? returnValue = command.Method.Invoke(command.Module, arguments);
|
||||||
|
if (returnValue is Task task)
|
||||||
|
{
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMessageReceived(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMessageReceived(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterCommandMethod(CommandModule module, MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
var commandAttribute = methodInfo.GetCustomAttribute<CommandAttribute>();
|
||||||
|
if (commandAttribute is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliasesAttribute = methodInfo.GetCustomAttribute<AliasesAttribute>();
|
||||||
|
ParameterInfo[] parameters = methodInfo.GetParameters();
|
||||||
|
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(CommandContext))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Command method must have a {typeof(CommandContext)} parameter at index 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < parameters.Length - 1; index++)
|
||||||
|
{
|
||||||
|
if (parameters[index].GetCustomAttribute<RemainderAttribute>() is not null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{typeof(RemainderAttribute)} can only be placed on the last parameter.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var types = new Type[parameters.Length - 1];
|
||||||
|
for (var index = 1; index < parameters.Length; index++)
|
||||||
|
{
|
||||||
|
types[index - 1] = parameters[index].ParameterType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var command = new Command(
|
||||||
|
commandAttribute.Name,
|
||||||
|
aliasesAttribute?.Aliases ?? Array.Empty<string>(),
|
||||||
|
methodInfo,
|
||||||
|
module
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_commandMap.ContainsKey(command.Name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Duplicate command name registered ({command.Name})");
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandMap[command.Name] = command;
|
||||||
|
|
||||||
|
foreach (string alias in command.Aliases)
|
||||||
|
{
|
||||||
|
if (_commandMap.ContainsKey(alias))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Duplicate command name registered ({alias})");
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandMap[alias] = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines configuration for <see cref="CommandsExtensionConfiguration" />.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CommandsExtensionConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the prefixes to be use for commands.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The command prefixes, as an array of <see cref="string" /> values.</value>
|
||||||
|
public string[] Prefixes { get; set; } = Array.Empty<string>();
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace VpSharp.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="VirtualParadiseClient" />.
|
||||||
|
/// </summary>
|
||||||
|
public static class VirtualParadiseClientExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers <see cref="CommandsExtension" /> to be used with the current client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The <see cref="VirtualParadiseClient" />.</param>
|
||||||
|
/// <param name="configuration">The configuration required for the extensions.</param>
|
||||||
|
/// <returns>The commands extension instance.</returns>
|
||||||
|
public static CommandsExtension UseCommands(this VirtualParadiseClient client, CommandsExtensionConfiguration configuration)
|
||||||
|
{
|
||||||
|
return client.AddExtension<CommandsExtension>(configuration);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<VersionPrefix>0.1.0</VersionPrefix>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' == ''">
|
||||||
|
<Version>$(VersionPrefix)-$(VersionSuffix)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||||
|
<PackageVersion>$(VersionPrefix)-$(VersionSuffix)</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">
|
||||||
|
<Version>$(VersionPrefix)-$(VersionSuffix).$(BuildNumber)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).$(BuildNumber)</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).$(BuildNumber)</FileVersion>
|
||||||
|
<PackageVersion>$(VersionPrefix)-$(VersionSuffix).$(BuildNumber)</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(VersionSuffix)' == ''">
|
||||||
|
<Version>$(VersionPrefix)</Version>
|
||||||
|
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
|
||||||
|
<FileVersion>$(VersionPrefix).0</FileVersion>
|
||||||
|
<PackageVersion>$(VersionPrefix)</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\VpSharp\VpSharp.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -8,7 +8,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\VpSharp\VpSharp.csproj" />
|
<ProjectReference Include="..\VpSharp.Commands\VpSharp.Commands.csproj"/>
|
||||||
|
<ProjectReference Include="..\VpSharp\VpSharp.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
using VpSharp.Commands;
|
||||||
|
using VpSharp.Commands.Attributes;
|
||||||
|
|
||||||
|
namespace VpSharp.IntegrationTests.CommandModules;
|
||||||
|
|
||||||
|
internal sealed class TestCommands : CommandModule
|
||||||
|
{
|
||||||
|
[Command("echo")]
|
||||||
|
[Aliases("say")]
|
||||||
|
public async Task EchoCommand(CommandContext context, [Remainder] string message)
|
||||||
|
{
|
||||||
|
await context.RespondAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("ping")]
|
||||||
|
[Aliases("pong", "pingpong")]
|
||||||
|
public async Task PingAsync(CommandContext context)
|
||||||
|
{
|
||||||
|
await context.RespondAsync("Pong!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
using VpSharp;
|
using VpSharp;
|
||||||
|
using VpSharp.Commands;
|
||||||
using VpSharp.Entities;
|
using VpSharp.Entities;
|
||||||
|
using VpSharp.IntegrationTests.CommandModules;
|
||||||
|
|
||||||
string? username = Environment.GetEnvironmentVariable("username");
|
string? username = Environment.GetEnvironmentVariable("username");
|
||||||
string? password = Environment.GetEnvironmentVariable("password");
|
string? password = Environment.GetEnvironmentVariable("password");
|
||||||
|
@ -16,7 +18,7 @@ if (string.IsNullOrWhiteSpace(password))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var configuration = new VirtualParadiseConfiguration()
|
var configuration = new VirtualParadiseConfiguration
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = password,
|
Password = password,
|
||||||
|
@ -26,6 +28,8 @@ var configuration = new VirtualParadiseConfiguration()
|
||||||
};
|
};
|
||||||
|
|
||||||
using var client = new VirtualParadiseClient(configuration);
|
using var client = new VirtualParadiseClient(configuration);
|
||||||
|
CommandsExtension commands = client.UseCommands(new CommandsExtensionConfiguration {Prefixes = new[] {"!"}});
|
||||||
|
commands.RegisterCommands<TestCommands>();
|
||||||
|
|
||||||
Console.WriteLine(@"Connecting to universe");
|
Console.WriteLine(@"Connecting to universe");
|
||||||
await client.ConnectAsync().ConfigureAwait(false);
|
await client.ConnectAsync().ConfigureAwait(false);
|
||||||
|
|
|
@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VpSharp.Commands", "VpSharp.Commands\VpSharp.Commands.csproj", "{8EE96C20-57AA-48E1-95A2-04580C4F7E05}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -32,5 +34,9 @@ Global
|
||||||
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{87C0D19A-27C9-4041-9DD5-191B8D0FDEF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8EE96C20-57AA-48E1-95A2-04580C4F7E05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8EE96C20-57AA-48E1-95A2-04580C4F7E05}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8EE96C20-57AA-48E1-95A2-04580C4F7E05}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8EE96C20-57AA-48E1-95A2-04580C4F7E05}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in New Issue