コード例 #1
0
    /// <summary>
    /// Determines whether a node matches the current state of the tokenizer.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <param name="tokenizer">The tokenizer.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>true if the node matches; otherwise, false.</returns>
    private bool IsNodeMatch(IChildNode node, TokenizingEnumerator tokenizer, TreeSearchOptions searchOptions)
    {
        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        if (tokenizer.Current.Type != TokenType.Value)
        {
            return(false);
        }

        if (tokenizer.Current.Value.Equals(node.Key, searchOptions.KeyComparison))
        {
            return(true);
        }

        foreach (var alias in node.Aliases)
        {
            if (tokenizer.Current.Value.Equals(alias, searchOptions.KeyComparison))
            {
                return(true);
            }
        }

        return(false);
    }
コード例 #2
0
    /// <summary>
    /// Performs a depth-first search of the given node.
    /// </summary>
    /// <param name="parentNode">The node.</param>
    /// <param name="tokenizer">The tokenizer.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>The matching nodes.</returns>
    private IEnumerable <BoundCommandNode> Search
    (
        IParentNode parentNode,
        TokenizingEnumerator tokenizer,
        TreeSearchOptions searchOptions
    )
    {
        var boundCommandNodes = new List <BoundCommandNode>();

        foreach (var child in parentNode.Children)
        {
            if (!IsNodeMatch(child, tokenizer, searchOptions))
            {
                continue;
            }

            switch (child)
            {
            case CommandNode commandNode:
            {
                if (!commandNode.TryBind(tokenizer, out var boundCommandShape))
                {
                    continue;
                }

                boundCommandNodes.Add(boundCommandShape);
                break;
            }

            case IParentNode groupNode:
            {
                // Consume the token
                if (!tokenizer.MoveNext())
                {
                    // No more tokens, so we can't continue searching
                    return(boundCommandNodes);
                }

                var nestedResults = Search(groupNode, tokenizer, searchOptions);
                boundCommandNodes.AddRange(nestedResults);

                continue;
            }

            default:
            {
                throw new InvalidOperationException
                      (
                          "Unknown node type encountered; tree is invalid and the search cannot continue."
                      );
            }
            }
        }

        return(boundCommandNodes);
    }
コード例 #3
0
    /// <summary>
    /// Performs a depth-first search of the given node.
    /// </summary>
    /// <param name="parentNode">The node.</param>
    /// <param name="commandPath">The command path.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>The matching nodes.</returns>
    private IEnumerable <CommandNode> Search
    (
        IParentNode parentNode,
        IReadOnlyList <string> commandPath,
        TreeSearchOptions searchOptions
    )
    {
        if (commandPath.Count < 1)
        {
            return(Array.Empty <CommandNode>());
        }

        var commandNodes = new List <CommandNode>();

        foreach (var child in parentNode.Children)
        {
            if (!IsNodeMatch(child, commandPath[0], searchOptions))
            {
                continue;
            }

            switch (child)
            {
            case CommandNode commandNode:
            {
                commandNodes.Add(commandNode);
                break;
            }

            case IParentNode groupNode:
            {
                var nestedResults = Search(groupNode, commandPath.Skip(1).ToList(), searchOptions);
                commandNodes.AddRange(nestedResults);

                continue;
            }

            default:
            {
                throw new InvalidOperationException
                      (
                          "Unknown node type encountered; tree is invalid and the search cannot continue."
                      );
            }
            }
        }

        return(commandNodes);
    }
コード例 #4
0
    /// <summary>
    /// Determines whether a node matches the given path component.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <param name="pathComponent">The pathComponent.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>true if the node matches; otherwise, false.</returns>
    private bool IsNodeMatch(IChildNode node, ReadOnlySpan <char> pathComponent, TreeSearchOptions searchOptions)
    {
        if (pathComponent.Equals(node.Key, searchOptions.KeyComparison))
        {
            return(true);
        }

        foreach (var alias in node.Aliases)
        {
            if (pathComponent.Equals(alias, searchOptions.KeyComparison))
            {
                return(true);
            }
        }

        return(false);
    }
コード例 #5
0
                public void CanFindCommandWithDifferentCasing()
                {
                    var builder = new CommandTreeBuilder();

                    builder.RegisterModule <GroupWithCasingDifferences>();

                    var tree = builder.Build();

                    var options = new TreeSearchOptions(StringComparison.OrdinalIgnoreCase);

                    var result = tree.Search
                                 (
                        "test somecommand",
                        searchOptions: options
                                 );

                    Assert.NotEmpty(result);
                }
コード例 #6
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ConsentCheckingPreExecutionEvent"/> class.
 /// </summary>
 /// <param name="privacy">The privacy service.</param>
 /// <param name="commandService">The command service.</param>
 /// <param name="options">The responder options.</param>
 /// <param name="interactionAPI">The interaction API.</param>
 /// <param name="tokenizerOptions">The tokenizer options.</param>
 /// <param name="treeSearchOptions">The tree search options.</param>
 /// <param name="feedback">The feedback service.</param>
 public ConsentCheckingPreExecutionEvent
 (
     PrivacyService privacy,
     CommandService commandService,
     IOptions <CommandResponderOptions> options,
     IDiscordRestInteractionAPI interactionAPI,
     IOptions <TokenizerOptions> tokenizerOptions,
     IOptions <TreeSearchOptions> treeSearchOptions,
     FeedbackService feedback
 )
 {
     _privacy           = privacy;
     _commandService    = commandService;
     _interactionAPI    = interactionAPI;
     _feedback          = feedback;
     _options           = options.Value;
     _tokenizerOptions  = tokenizerOptions.Value;
     _treeSearchOptions = treeSearchOptions.Value;
 }
コード例 #7
0
    /// <summary>
    /// Determines whether a node matches the current state of the enumerator.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <param name="enumerator">The tokenizer.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>true if the node matches; otherwise, false.</returns>
    private bool IsNodeMatch(IChildNode node, SpanSplitEnumerator enumerator, TreeSearchOptions searchOptions)
    {
        if (!enumerator.MoveNext())
        {
            return(false);
        }

        if (enumerator.Current.Equals(node.Key, searchOptions.KeyComparison))
        {
            return(true);
        }

        foreach (var alias in node.Aliases)
        {
            if (enumerator.Current.Equals(alias, searchOptions.KeyComparison))
            {
                return(true);
            }
        }

        return(false);
    }
コード例 #8
0
        /// <inheritdoc />
        public async Task <Result> RespondAsync
        (
            IInteractionCreate?gatewayEvent,
            CancellationToken ct = default
        )
        {
            if (gatewayEvent is null)
            {
                return(Result.FromSuccess());
            }

            if (gatewayEvent.Type != InteractionType.ApplicationCommand)
            {
                return(Result.FromSuccess());
            }

            if (!gatewayEvent.Data.HasValue)
            {
                return(Result.FromSuccess());
            }

            if (!gatewayEvent.ChannelID.HasValue)
            {
                return(Result.FromSuccess());
            }

            var user = gatewayEvent.User.HasValue
                ? gatewayEvent.User.Value
                : gatewayEvent.Member.HasValue
                    ? gatewayEvent.Member.Value.User.HasValue
                        ? gatewayEvent.Member.Value.User.Value
                        : null
                    : null;

            if (user is null)
            {
                return(Result.FromSuccess());
            }

            // Signal Discord that we'll be handling this one asynchronously
            var response            = new InteractionResponse(InteractionCallbackType.DeferredChannelMessageWithSource);
            var interactionResponse = await _interactionAPI.CreateInteractionResponseAsync
                                      (
                gatewayEvent.ID,
                gatewayEvent.Token,
                response,
                ct
                                      );

            if (!interactionResponse.IsSuccess)
            {
                return(interactionResponse);
            }

            var interactionData = gatewayEvent.Data.Value !;

            interactionData.UnpackInteraction(out var command, out var parameters);

            var context = new InteractionContext
                          (
                gatewayEvent.GuildID,
                gatewayEvent.ChannelID.Value,
                user,
                gatewayEvent.Member,
                gatewayEvent.Token,
                gatewayEvent.ID,
                gatewayEvent.ApplicationID,
                interactionData.Resolved
                          );

            // Provide the created context to any services inside this scope
            _contextInjection.Context = context;

            // Run any user-provided pre execution events
            var preExecution = await _eventCollector.RunPreExecutionEvents(context, ct);

            if (!preExecution.IsSuccess)
            {
                return(preExecution);
            }

            // Run the actual command
            var searchOptions = new TreeSearchOptions(StringComparison.OrdinalIgnoreCase);
            var executeResult = await _commandService.TryExecuteAsync
                                (
                command,
                parameters,
                _services,
                searchOptions : searchOptions,
                ct : ct
                                );

            if (!executeResult.IsSuccess)
            {
                return(Result.FromError(executeResult));
            }

            // Run any user-provided post execution events
            return(await _eventCollector.RunPostExecutionEvents
                   (
                       context,
                       executeResult.Entity,
                       ct
                   ));
        }
コード例 #9
0
        /// <inheritdoc />
        public async Task <EventResponseResult> RespondAsync
        (
            IInteractionCreate?gatewayEvent,
            CancellationToken ct = default
        )
        {
            if (gatewayEvent is null)
            {
                return(EventResponseResult.FromSuccess());
            }

            if (!gatewayEvent.Data.HasValue)
            {
                return(EventResponseResult.FromSuccess());
            }

            if (!gatewayEvent.Member.User.HasValue)
            {
                return(EventResponseResult.FromSuccess());
            }

            // Signal Discord that we'll be handling this one asynchronously
            var response            = new InteractionResponse(InteractionResponseType.Acknowledge, default);
            var interactionResponse = await _interactionAPI.CreateInteractionResponseAsync
                                      (
                gatewayEvent.ID,
                gatewayEvent.Token,
                response,
                ct
                                      );

            if (!interactionResponse.IsSuccess)
            {
                return(EventResponseResult.FromError(interactionResponse));
            }

            var interactionData = gatewayEvent.Data.Value !;

            interactionData.UnpackInteraction(out var command, out var parameters);

            var context = new InteractionContext
                          (
                gatewayEvent.ChannelID,
                gatewayEvent.Member.User.Value !,
                gatewayEvent.Member,
                gatewayEvent.Token,
                gatewayEvent.ID,
                gatewayEvent.GuildID
                          );

            // Run any user-provided pre execution events
            var preExecution = await _eventCollector.RunPreExecutionEvents(context, ct);

            if (!preExecution.IsSuccess)
            {
                return(EventResponseResult.FromError(preExecution));
            }

            // Run the actual command
            var searchOptions = new TreeSearchOptions(StringComparison.OrdinalIgnoreCase);
            var executeResult = await _commandService.TryExecuteAsync
                                (
                command,
                parameters,
                _services,
                new object[] { context },
                searchOptions,
                ct
                                );

            if (!executeResult.IsSuccess)
            {
                return(EventResponseResult.FromError(executeResult));
            }

            // Run any user-provided post execution events
            var postExecution = await _eventCollector.RunPostExecutionEvents
                                (
                context,
                executeResult.InnerResult !,
                ct
                                );

            if (!postExecution.IsSuccess)
            {
                return(EventResponseResult.FromError(postExecution));
            }

            return(EventResponseResult.FromSuccess());
        }