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