diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index 79e15f0c..24502f84 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -148,6 +148,19 @@ def _unwrap_slash_groups(data: ApplicationCommandInteractionData) -> Tuple[str, return command_name, command_options +def _quote_string_safe(string: str) -> str: + # we need to quote this string otherwise we may spill into + # other parameters and cause all kinds of trouble, as many + # quotes are supported and some may be in the option, we + # loop through all supported quotes and if neither open or + # close are in the string, we add them + for open, close in supported_quotes.items(): + if open not in string and close not in string: + return f"{open}{string}{close}" + + # all supported quotes are in the message and we cannot add any + # safely, very unlikely but still got to be covered + raise errors.UnexpectedQuoteError(string) class _DefaultRepr: def __repr__(self): @@ -1177,12 +1190,15 @@ class BotBase(GroupMixin): prefix = prefix[0] # Add arguments to fake message content, in the right order + ignore_params: List[inspect.Parameter] = [] message.content = f'{prefix}{command_name} ' for name, param in command.clean_params.items(): option = next((o for o in command_options if o['name'] == name), None) # type: ignore if option is None: if param.default is param.empty and not command._is_typing_optional(param.annotation): raise errors.MissingRequiredArgument(param) + else: + ignore_params.append(param) elif ( option["type"] == 3 and param.kind != param.KEYWORD_ONLY @@ -1190,28 +1206,13 @@ class BotBase(GroupMixin): ): # String with space in without "consume rest" option = cast(_ApplicationCommandInteractionDataOptionString, option) - - # we need to quote this string otherwise we may spill into - # other parameters and cause all kinds of trouble, as many - # quotes are supported and some may be in the option, we - # loop through all supported quotes and if neither open or - # close are in the string, we add them - quoted = False - string = option['value'] - for open, close in supported_quotes.items(): - if open not in string and close not in string: - message.content += f"{open}{string}{close} " - quoted = True - break - - # all supported quotes are in the message and we cannot add any - # safely, very unlikely but still got to be covered - if not quoted: - raise errors.UnexpectedQuoteError(string) + quoted_string = _quote_string_safe(option['value']) + message.content += f'{quoted_string} ' else: message.content += f'{option.get("value", "")} ' ctx = await self.get_context(message) + ctx._ignored_params = ignore_params ctx.interaction = interaction await self.invoke(ctx) diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 8cc2afb6..73d8d8fb 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -154,6 +154,7 @@ class Context(discord.abc.Messageable, Generic[BotT]): self.subcommand_passed: Optional[str] = subcommand_passed self.command_failed: bool = command_failed self.current_parameter: Optional[inspect.Parameter] = current_parameter + self._ignored_params: List[inspect.Parameter] = [] self._state: ConnectionState = self.message._state async def invoke(self, command: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T: diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index b23f8d16..30d11883 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -577,6 +577,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]): ctx.bot.dispatch('command_error', ctx, error) async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: + if param in ctx._ignored_params: + # in a slash command, we need a way to mark a param as default so ctx._ignored_params is used + return param.default if param.default is not param.empty else None + required = param.default is param.empty converter = get_converter(param) consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw