Remora / Remora.Discord

A data-oriented C# Discord library, focused on high-performance concurrency and robust design.
GNU Lesser General Public License v3.0
243 stars 46 forks source link

[Bug]: Exception on slash command execution with optional parameter #319

Closed Ruttie2006 closed 9 months ago

Ruttie2006 commented 11 months ago

Description

When an optional parameter is added to a slash command, and that slash command is executed without said paramater, an exception occurs. image

Steps to Reproduce

Code:

    internal static class Program
    {
        const ulong GUILD = 0uL;
        const string PREFIX = "?";
        const GatewayIntents INTENTS = GatewayIntents.MessageContents;

        static readonly CancellationTokenSource Cts = new();
        static async Task Main()
        {
            Console.CancelKeyPress += (s, e) =>
            {
                e.Cancel = true;
                Cts.Cancel();
            };

            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .CreateLogger();

            var prov = new ServiceCollection()
                .AddDiscordGateway(x => File.ReadAllText("token.txt"))
                .Configure<DiscordGatewayClientOptions>(static x =>
                {
                    x.Intents |= INTENTS;
                })
                .AddInteractivity()
                .AddDiscordCommands(true)
                .Configure<CommandResponderOptions>(x =>
                {
                    x.Prefix = PREFIX;
                })
                .AddInteractionGroup<Commands>()
                .AddCommandTree()
                .WithCommandGroup<Commands>()
                .Finish()
                .AddSerilog(dispose: true)
                .BuildServiceProvider();

            var slashService = prov.GetRequiredService<SlashService>();
            var appRes = await slashService.UpdateSlashCommandsAsync(DiscordSnowflake.New(GUILD));
            if (!appRes.IsSuccess)
                throw new InvalidOperationException(appRes.Error.Message);

            var gateway = prov.GetRequiredService<DiscordGatewayClient>();
            var runRes = await gateway.RunAsync(Cts.Token);

            if (!runRes.IsSuccess)
                throw new InvalidOperationException(runRes.Error.Message);
        }
    }
    internal class Commands(IFeedbackService feedback) : InteractionGroup
    {
        [Command("command")]
        [RequireContext(ChannelContext.Guild)]
        [RequireDiscordPermission(DiscordPermission.ManageGuild)]
        public async Task<Result> Command(IPartialRole? role = null)
        {
            if (role == null)
                return (Result)await feedback.SendContextualInfoAsync($"Hello without role.");
            else
                return (Result)await feedback.SendContextualInfoAsync($"Hello with role: {role.Name.OrThrow(static () => new InvalidCastException("Empty value."))}.");
        }
    }

Replace constants with applicable values.

Expected Behavior

Command should execute without the parameter being set.

Current Behavior

The following exception is printed in the console, and the command is not executed:

[16:51:29 WRN] Error in gateway event responder: Enumeration has not started. Call MoveNext.
System.InvalidOperationException: Enumeration has not started. Call MoveNext.
   at System.GenericEmptyEnumerator`1.get_Current()
   at Remora.Commands.Trees.Nodes.CommandNode.TryBind(IReadOnlyDictionary`2 namedParameters, BoundCommandNode& boundCommandShape, TreeSearchOptions searchOptions)
   at Remora.Commands.Trees.CommandTree.<>c__DisplayClass6_0.<Search>b__0(CommandNode c)
   at System.Linq.Enumerable.SelectListIterator`2.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.ToList()
   at Remora.Commands.Services.CommandService.TryPrepareCommandAsync(IReadOnlyList`1 commandPath, IReadOnlyDictionary`2 namedParameters, IServiceProvider services, TokenizerOptions tokenizerOptions, TreeSearchOptions searchOptions, String treeName, CancellationToken ct)
   at Remora.Discord.Commands.Responders.InteractionResponder.TryExecuteCommandAsync(InteractionContext operationContext, IReadOnlyList`1 commandPath, IReadOnlyDictionary`2 parameters, String treeName, CancellationToken ct)
   at Remora.Discord.Commands.Responders.InteractionResponder.RespondAsync(IInteractionCreate gatewayEvent, CancellationToken ct)
   at Remora.Discord.Gateway.Services.ResponderDispatchService.<>c__DisplayClass17_0`1.<<DispatchEventAsync>b__0>d.MoveNext()

Library / Runtime Information

Used packages:

    <PackageReference Include="Remora.Discord" Version="2023.4.0" />
    <PackageReference Include="Serilog" Version="3.0.1" />
    <PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />

.NET version: 8.0 LangVersion: preview (12)

VelvetToroyashi commented 11 months ago

👋🏽 Hi! Thanks for opening this issue. The stacktrace you've provided points to these lines being the culprit:

https://github.com/Remora/Remora.Commands/blob/aae457aecbbd42d679501195a398bb6bc7b183fb/Remora.Commands/Trees/Nodes/CommandNode.cs#L204-L211

I'll be looking into why this is the case momentarily.

VelvetToroyashi commented 11 months ago

With some investigative help from Vamplay on Discord, it turns out this is due to a breaking change in .NET 8. PR inbound.

Ruttie2006 commented 10 months ago

Any updates on a possible fix? I would like to use optional parameters, but because of this I can't really...

Nihlus commented 10 months ago

@VelvetToroyashi

VelvetToroyashi commented 10 months ago

Last I recall @DPlayer234 had implied they would fix it, but I can PR their changes if need be https://canary.discord.com/channels/789912892426027039/789913038920744960/1163523508924579880

DPlayer234 commented 10 months ago

@VelvetToroyashi I didn't mean to imply I'd make a PR. Feel free to go ahead.

Ruttie2006 commented 10 months ago

I don't mean to be pushy, but is there any update on this? ( @VelvetToroyashi )

VelvetToroyashi commented 10 months ago

Hi sorry, I've been caught up with various projects. I'd have to look through my branches to see if I've already fixed this, but otherwise 'no', I'm afraid.