From 925b312d7e7dba35dfc1baf2110cf095612fefff Mon Sep 17 00:00:00 2001 From: EdVraz Date: Wed, 16 Feb 2022 11:03:55 +0100 Subject: [PATCH 01/15] a --- interactions/client.py | 240 ++++++++++++++++++++++++++++------------ interactions/client.pyi | 6 + 2 files changed, 177 insertions(+), 69 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 7d4e82031..df76278ed 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -1,3 +1,4 @@ +import re import sys from asyncio import get_event_loop, iscoroutinefunction from functools import wraps @@ -319,6 +320,174 @@ def event(self, coro: Coroutine, name: Optional[str] = MISSING) -> Callable[..., self._websocket.dispatch.register(coro, name if name is not MISSING else coro.__name__) return coro + def __check_command( + self, + command: ApplicationCommand, + coro: Coroutine, + regex: str = r"^[\w-]{1,32}$", + ) -> None: + """ + Checks if a command follows API-limits + """ + + # TODO: split into smaller functions + + + + if command.name is MISSING: + raise InteractionException(11, message="Your command must have a name.") + + else: + log.debug(f"checking command: {command.name}") + + if not re.fullmatch(regex, command.name): + raise InteractionException( + 11, message=f"Your command does not match the regex for valid command names ('{regex}')" + ) + elif command.type == ApplicationCommandType.CHAT_INPUT and command.description is MISSING: + raise InteractionException( + 11, message="Chat-input commands must have a description." + ) + elif command.type != ApplicationCommandType.CHAT_INPUT and command.description is not MISSING: + raise InteractionException( + 11, message="Only chat-input commands can have a description." + ) + + elif command.description is not MISSING and len(command.description) > 100: + raise InteractionException( + 11, message="Command descriptions must be less than 100 characters." + ) + + if not len(coro.__code__.co_varnames): + raise InteractionException( + 11, message="Your command needs at least one argument to return context." + ) + if command.options is not MISSING: + if len(coro.__code__.co_varnames) + 1 < len(command.options): + raise InteractionException( + 11, + message="You must have the same amount of arguments as the options of the command.", + ) + if isinstance(command.options, List) and len(command.options) > 25: + raise InteractionException( + 11, message="Your command must have less than 25 options." + ) + _option: Option + for _option in command.options: + if _option.type not in ( + OptionType.SUB_COMMAND, + OptionType.SUB_COMMAND_GROUP, + ): + if getattr(_option, "autocomplete", False) and getattr( + _option, "choices", False + ): + log.warning( + "Autocomplete may not be set to true if choices are present." + ) + if not getattr(_option, "description", False): + raise InteractionException( + 11, + message="A description is required for Options that are not sub-commands.", + ) + if _option.type == OptionType.SUB_COMMAND_GROUP: + for _sub_group in _option.options: + if _sub_group.name is MISSING: + raise InteractionException( + 11, + message="Sub command groups must have a name." + ) + else: + log.debug(f"checking sub command group '{_sub_group.name}' of command '{command.name}'") + if not re.fullmatch(regex, _sub_group.name): + raise InteractionException( + 11, + message=f"The sub command group name does not match the regex for valid names ('{regex}')" + ) + elif _sub_group.description is MISSING: + raise InteractionException( + 11, + message="sub command groups must have a description." + ) + elif len(_sub_group.description) > 100: + raise InteractionException( + 11, + message="sub command group descriptions must not be longer than 100 characters." + ) + if not _sub_group.options: + raise InteractionException( + 11, + message="sub command groups must have subcommands!" + ) + else: + for _sub_command in _sub_group.options: + if _sub_command.name is MISSING: + raise InteractionException( + 11, + message="sub commands must have a name!" + ) + else: + log.debug( + f"checking sub command '{_sub_command.name}' of group '{_sub_group.name}'") + if not re.fullmatch(regex, _sub_command.name): + raise InteractionException( + 11, + message=f"The sub command name does not match the regex for valid names ('{regex}')" + ) + elif _sub_command.description is MISSING: + raise InteractionException( + 11, + message="sub commands must have a description." + ) + elif len(_sub_command.description) > 100: + raise InteractionException( + 11, + message="sub command descriptions must not be longer than 100 characters." + ) + if _sub_command.options is not MISSING: + for _opt in _sub_command.options: + if _opt.name is MISSING: + raise InteractionException(11, message="Options must have a name.") + else: + log.debug(f"checking option '{_option.name}' of subcommand '{_sub_command.name}'") + if not re.fullmatch(regex, _opt.name): + raise InteractionException( + 11, + message=f"The option name does not match the regex for valid names ('{regex}')" + ) + elif _opt.description is MISSING: + raise InteractionException( + 11, message="Command options must have a description" + ) + elif len(_opt.description) > 100: + raise InteractionException( + 11, + message="Command option descriptions must be less than 100 characters.", + ) + + + elif _option.type == OptionType.SUB_COMMAND: + ... + + else: + if _option.name is MISSING: + raise InteractionException(11, message="Options must have a name.") + else: + log.debug(f"checking option '{_option.name}' of command '{command.name}'") + if not re.fullmatch(regex, _option.name): + raise InteractionException( + 11, message=f"The option name does not match the regex for valid options names ('{regex}')" + ) + elif _option.description is MISSING: + raise InteractionException( + 11, message="Command options must have a description" + ) + elif len(_option.description) > 100: + raise InteractionException( + 11, + message="Command option descriptions must be less than 100 characters.", + ) + + def command( self, *, @@ -373,75 +542,6 @@ async def message_command(ctx): """ def decorator(coro: Coroutine) -> Callable[..., Any]: - if name is MISSING: - raise InteractionException(11, message="Your command must have a name.") - - elif len(name) > 32: - raise InteractionException( - 11, message="Command names must be less than 32 characters." - ) - elif type == ApplicationCommandType.CHAT_INPUT and description is MISSING: - raise InteractionException( - 11, message="Chat-input commands must have a description." - ) - elif type != ApplicationCommandType.CHAT_INPUT and description is not MISSING: - raise InteractionException( - 11, message="Only chat-input commands can have a description." - ) - - elif description is not MISSING and len(description) > 100: - raise InteractionException( - 11, message="Command descriptions must be less than 100 characters." - ) - - for _ in name: - if _.isupper() and type == ApplicationCommandType.CHAT_INPUT: - raise InteractionException( - 11, - message="Your chat-input command name must not contain uppercase characters (Discord limitation)", - ) - - if not len(coro.__code__.co_varnames): - raise InteractionException( - 11, message="Your command needs at least one argument to return context." - ) - if options is not MISSING: - if len(coro.__code__.co_varnames) + 1 < len(options): - raise InteractionException( - 11, - message="You must have the same amount of arguments as the options of the command.", - ) - if isinstance(options, List) and len(options) > 25: - raise InteractionException( - 11, message="Your command must have less than 25 options." - ) - _option: Option - for _option in options: - if _option.type not in ( - OptionType.SUB_COMMAND, - OptionType.SUB_COMMAND_GROUP, - ): - if getattr(_option, "autocomplete", False) and getattr( - _option, "choices", False - ): - log.warning( - "Autocomplete may not be set to true if choices are present." - ) - if not getattr(_option, "description", False): - raise InteractionException( - 11, - message="A description is required for Options that are not sub-commands.", - ) - if len(_option.description) > 100: - raise InteractionException( - 11, - message="Command option descriptions must be less than 100 characters.", - ) - - if len(_option.name) > 32: - raise InteractionException( - 11, message="Command option names must be less than 32 characters." - ) commands: List[ApplicationCommand] = command( type=type, @@ -452,6 +552,8 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: default_permission=default_permission, ) + self.__check_command(command=commands[0], coro=coro) + if self._automate_sync: if self._loop.is_running(): [self._loop.create_task(self._synchronize(command)) for command in commands] diff --git a/interactions/client.pyi b/interactions/client.pyi index d62e2ba22..deca1de08 100644 --- a/interactions/client.pyi +++ b/interactions/client.pyi @@ -46,6 +46,12 @@ class Client: async def _ready(self) -> None: ... async def _login(self) -> None: ... def event(self, coro: Coroutine, name: Optional[str] = None) -> Callable[..., Any]: ... + def __check_command( + self, + command: ApplicationCommand, + coro: Coroutine, + regex: str = "^[\w-]{1,32}$", + )-> None: ... def command( self, *, From 0b06ae69538b48930c4c0e53c63be4f33ac29dcc Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Wed, 16 Feb 2022 17:46:45 +0100 Subject: [PATCH 02/15] refactor: extract command checking into seperate function --- interactions/client.py | 264 +++++++++++++++++++---------------------- 1 file changed, 125 insertions(+), 139 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index df76278ed..a8f9b661e 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -327,12 +327,117 @@ def __check_command( regex: str = r"^[\w-]{1,32}$", ) -> None: """ - Checks if a command follows API-limits + Checks if a command is valid. """ + _options_names: List[str] = [] + _sub_groups_present: bool = False + _sub_cmds_present: bool = False - # TODO: split into smaller functions + def __check_sub_group(_sub_group: Option): + if command.type != ApplicationCommandType.CHAT_INPUT: + raise InteractionException( + 11, message="Only CHAT_INPUT commands can have subcommands!" + ) + if _sub_group.name is MISSING: + raise InteractionException(11, message="Sub command groups must have a name.") + else: + log.debug( + f"checking sub command group '{_sub_group.name}' of command '{command.name}'" + ) + if not re.fullmatch(regex, _sub_group.name): + raise InteractionException( + 11, + message=f"The sub command group name does not match the regex for valid names ('{regex}')", + ) + elif _sub_group.description is MISSING: + raise InteractionException(11, message="A description is required.") + elif len(_sub_group.description) > 100: + raise InteractionException( + 11, message="Descriptions must be less than 100 characters." + ) + if not _sub_group.options: + raise InteractionException(11, message="sub command groups must have subcommands!") + if len(_sub_group.options) > 25: + raise InteractionException( + 11, message="A sub command group cannot contain more than 25 sub commands!" + ) + for _sub_command in _sub_group.options: + __check_sub_command(_sub_command, _sub_group) + + def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): + if _sub_command.name is MISSING: + raise InteractionException(11, message="sub commands must have a name!") + if _sub_group is not MISSING: + log.debug( + f"checking sub command '{_sub_command.name}' of group '{_sub_group.name}'" + ) + else: + log.debug(f"checking sub command '{_sub_command.name}' of command '{command.name}'") + if not re.fullmatch(regex, _sub_command.name): + raise InteractionException( + 11, + message=f"The sub command name does not match the regex for valid names ('{regex}')", + ) + elif _sub_command.description is MISSING: + raise InteractionException(11, message="A description is required.") + elif len(_sub_command.description) > 100: + raise InteractionException( + 11, message="Descriptions must be less than 100 characters." + ) + if _sub_command.options is not MISSING: + if len(_sub_command.options) > 25: + raise InteractionException( + 11, message="Your sub command must have less than 25 options." + ) + for _opt in _sub_command.options: + __check_options(_opt, _sub_command) + + def __check_options(_option: Option, _sub_command: Option = MISSING): + if getattr(_option, "autocomplete", False) and getattr(_option, "choices", False): + log.warning("Autocomplete may not be set to true if choices are present.") + if _option.name is MISSING: + raise InteractionException(11, message="Options must have a name.") + if _sub_command is not MISSING: + log.debug(f"checking option '{_option.name}' of sub command '{_sub_command.name}'") + else: + _options_names.append(_option.name) + log.debug(f"checking option '{_option.name}' of command '{command.name}'") + if not re.fullmatch(regex, _option.name): + raise InteractionException( + 11, + message=f"The option name does not match the regex for valid names ('{regex}')", + ) + if _option.description is MISSING: + raise InteractionException( + 11, + message="A description is required.", + ) + elif len(_option.description) > 100: + raise InteractionException( + 11, + message="Descriptions must be less than 100 characters.", + ) + def __check_coro(): + if not len(coro.__code__.co_varnames): + raise InteractionException( + 11, message="Your command needs at least one argument to return context." + ) + elif _sub_cmds_present and len(coro.__code__.co_varnames) < 2: + raise InteractionException( + 11, message="Your command needs one argument for the sub_command." + ) + elif _sub_groups_present and len(coro.__code__.co_varnames) < 3: + raise InteractionException( + 11, + message="Your command needs one argument for the sub_command and one for the sub_command_group.", + ) + add: int = 1 + abs(_sub_cmds_present) + abs(_sub_groups_present) + if len(coro.__code__.co_varnames) + add < len(set(_options_names)): + raise InteractionException( + 11, message="You need one argument for every option name in your command!" + ) if command.name is MISSING: raise InteractionException(11, message="Your command must have a name.") @@ -340,153 +445,41 @@ def __check_command( else: log.debug(f"checking command: {command.name}") - if not re.fullmatch(regex, command.name): + if ( + not re.fullmatch(regex, command.name) + and command.type == ApplicationCommandType.CHAT_INPUT + ): raise InteractionException( - 11, message=f"Your command does not match the regex for valid command names ('{regex}')" + 11, message=f"Your command does not match the regex for valid names ('{regex}')" ) elif command.type == ApplicationCommandType.CHAT_INPUT and command.description is MISSING: - raise InteractionException( - 11, message="Chat-input commands must have a description." - ) - elif command.type != ApplicationCommandType.CHAT_INPUT and command.description is not MISSING: + raise InteractionException(11, message="A description is required.") + elif ( + command.type != ApplicationCommandType.CHAT_INPUT and command.description is not MISSING + ): raise InteractionException( 11, message="Only chat-input commands can have a description." ) elif command.description is not MISSING and len(command.description) > 100: - raise InteractionException( - 11, message="Command descriptions must be less than 100 characters." - ) + raise InteractionException(11, message="Descriptions must be less than 100 characters.") - if not len(coro.__code__.co_varnames): - raise InteractionException( - 11, message="Your command needs at least one argument to return context." - ) if command.options is not MISSING: - if len(coro.__code__.co_varnames) + 1 < len(command.options): - raise InteractionException( - 11, - message="You must have the same amount of arguments as the options of the command.", - ) - if isinstance(command.options, List) and len(command.options) > 25: + if len(command.options) > 25: raise InteractionException( 11, message="Your command must have less than 25 options." ) - _option: Option for _option in command.options: - if _option.type not in ( - OptionType.SUB_COMMAND, - OptionType.SUB_COMMAND_GROUP, - ): - if getattr(_option, "autocomplete", False) and getattr( - _option, "choices", False - ): - log.warning( - "Autocomplete may not be set to true if choices are present." - ) - if not getattr(_option, "description", False): - raise InteractionException( - 11, - message="A description is required for Options that are not sub-commands.", - ) if _option.type == OptionType.SUB_COMMAND_GROUP: - for _sub_group in _option.options: - if _sub_group.name is MISSING: - raise InteractionException( - 11, - message="Sub command groups must have a name." - ) - else: - log.debug(f"checking sub command group '{_sub_group.name}' of command '{command.name}'") - if not re.fullmatch(regex, _sub_group.name): - raise InteractionException( - 11, - message=f"The sub command group name does not match the regex for valid names ('{regex}')" - ) - elif _sub_group.description is MISSING: - raise InteractionException( - 11, - message="sub command groups must have a description." - ) - elif len(_sub_group.description) > 100: - raise InteractionException( - 11, - message="sub command group descriptions must not be longer than 100 characters." - ) - if not _sub_group.options: - raise InteractionException( - 11, - message="sub command groups must have subcommands!" - ) - else: - for _sub_command in _sub_group.options: - if _sub_command.name is MISSING: - raise InteractionException( - 11, - message="sub commands must have a name!" - ) - else: - log.debug( - f"checking sub command '{_sub_command.name}' of group '{_sub_group.name}'") - if not re.fullmatch(regex, _sub_command.name): - raise InteractionException( - 11, - message=f"The sub command name does not match the regex for valid names ('{regex}')" - ) - elif _sub_command.description is MISSING: - raise InteractionException( - 11, - message="sub commands must have a description." - ) - elif len(_sub_command.description) > 100: - raise InteractionException( - 11, - message="sub command descriptions must not be longer than 100 characters." - ) - if _sub_command.options is not MISSING: - for _opt in _sub_command.options: - if _opt.name is MISSING: - raise InteractionException(11, message="Options must have a name.") - else: - log.debug(f"checking option '{_option.name}' of subcommand '{_sub_command.name}'") - if not re.fullmatch(regex, _opt.name): - raise InteractionException( - 11, - message=f"The option name does not match the regex for valid names ('{regex}')" - ) - elif _opt.description is MISSING: - raise InteractionException( - 11, message="Command options must have a description" - ) - elif len(_opt.description) > 100: - raise InteractionException( - 11, - message="Command option descriptions must be less than 100 characters.", - ) - + __check_sub_group(_option) elif _option.type == OptionType.SUB_COMMAND: - ... + __check_sub_command(_option) else: - if _option.name is MISSING: - raise InteractionException(11, message="Options must have a name.") - else: - log.debug(f"checking option '{_option.name}' of command '{command.name}'") - if not re.fullmatch(regex, _option.name): - raise InteractionException( - 11, message=f"The option name does not match the regex for valid options names ('{regex}')" - ) - elif _option.description is MISSING: - raise InteractionException( - 11, message="Command options must have a description" - ) - elif len(_option.description) > 100: - raise InteractionException( - 11, - message="Command option descriptions must be less than 100 characters.", - ) + __check_options(_option) + __check_coro() def command( self, @@ -607,11 +600,6 @@ async def context_menu_name(ctx): """ def decorator(coro: Coroutine) -> Callable[..., Any]: - if not len(coro.__code__.co_varnames): - raise InteractionException( - 11, - message="Your command needs at least one argument to return context.", - ) commands: List[ApplicationCommand] = command( type=ApplicationCommandType.MESSAGE, @@ -619,6 +607,7 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: scope=scope, default_permission=default_permission, ) + self.__check_command(commands[0], coro) if self._automate_sync: if self._loop.is_running(): @@ -667,11 +656,6 @@ async def context_menu_name(ctx): """ def decorator(coro: Coroutine) -> Callable[..., Any]: - if not len(coro.__code__.co_varnames): - raise InteractionException( - 11, - message="Your command needs at least one argument to return context.", - ) commands: List[ApplicationCommand] = command( type=ApplicationCommandType.USER, @@ -680,6 +664,8 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: default_permission=default_permission, ) + self.__check_command(commands[0], coro) + if self._automate_sync: if self._loop.is_running(): [self._loop.create_task(self._synchronize(command)) for command in commands] From 4c78135d99c6dfa40fb4b3523ae3eadb3484b7a0 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:12:30 +0100 Subject: [PATCH 03/15] fix!: change convertion and prevent NoneType error --- interactions/client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index a8f9b661e..c319d45c7 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -464,7 +464,7 @@ def __check_coro(): elif command.description is not MISSING and len(command.description) > 100: raise InteractionException(11, message="Descriptions must be less than 100 characters.") - if command.options is not MISSING: + if command.options and command.options is not MISSING: if len(command.options) > 25: raise InteractionException( 11, message="Your command must have less than 25 options." @@ -544,8 +544,7 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: options=options, default_permission=default_permission, ) - - self.__check_command(command=commands[0], coro=coro) + self.__check_command(command=ApplicationCommand(**commands[0]), coro=coro) if self._automate_sync: if self._loop.is_running(): @@ -607,7 +606,7 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: scope=scope, default_permission=default_permission, ) - self.__check_command(commands[0], coro) + self.__check_command(ApplicationCommand(**commands[0]), coro) if self._automate_sync: if self._loop.is_running(): @@ -664,7 +663,7 @@ def decorator(coro: Coroutine) -> Callable[..., Any]: default_permission=default_permission, ) - self.__check_command(commands[0], coro) + self.__check_command(ApplicationCommand(**commands[0]), coro) if self._automate_sync: if self._loop.is_running(): From b41cefe50ca1131c17ce02fea086a6da8a6417ad Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Wed, 16 Feb 2022 22:04:06 +0100 Subject: [PATCH 04/15] refactor: update regex to not allow uppercase letters --- interactions/client.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index c319d45c7..1a46e8139 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -324,11 +324,12 @@ def __check_command( self, command: ApplicationCommand, coro: Coroutine, - regex: str = r"^[\w-]{1,32}$", + regex: str = r"^[a-z]|[0-9]|(?:[._]|[a-z0-9]|[.-]+){1,32}$", ) -> None: """ Checks if a command is valid. """ + reg = re.compile(regex) _options_names: List[str] = [] _sub_groups_present: bool = False _sub_cmds_present: bool = False @@ -344,10 +345,10 @@ def __check_sub_group(_sub_group: Option): log.debug( f"checking sub command group '{_sub_group.name}' of command '{command.name}'" ) - if not re.fullmatch(regex, _sub_group.name): + if re.fullmatch(reg, _sub_group.name): raise InteractionException( 11, - message=f"The sub command group name does not match the regex for valid names ('{regex}')", + message=f"The sub command group name does not match the reg for valid names ('{regex}')", ) elif _sub_group.description is MISSING: raise InteractionException(11, message="A description is required.") @@ -373,10 +374,10 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): ) else: log.debug(f"checking sub command '{_sub_command.name}' of command '{command.name}'") - if not re.fullmatch(regex, _sub_command.name): + if re.fullmatch(reg, _sub_command.name): raise InteractionException( 11, - message=f"The sub command name does not match the regex for valid names ('{regex}')", + message=f"The sub command name does not match the reg for valid names ('{reg}')", ) elif _sub_command.description is MISSING: raise InteractionException(11, message="A description is required.") @@ -402,10 +403,10 @@ def __check_options(_option: Option, _sub_command: Option = MISSING): else: _options_names.append(_option.name) log.debug(f"checking option '{_option.name}' of command '{command.name}'") - if not re.fullmatch(regex, _option.name): + if re.fullmatch(reg, _option.name): raise InteractionException( 11, - message=f"The option name does not match the regex for valid names ('{regex}')", + message=f"The option name does not match the reg for valid names ('{regex}')", ) if _option.description is MISSING: raise InteractionException( @@ -446,11 +447,11 @@ def __check_coro(): log.debug(f"checking command: {command.name}") if ( - not re.fullmatch(regex, command.name) + not re.fullmatch(reg, command.name) and command.type == ApplicationCommandType.CHAT_INPUT ): raise InteractionException( - 11, message=f"Your command does not match the regex for valid names ('{regex}')" + 11, message=f"Your command does not match the reg for valid names ('{regex}')" ) elif command.type == ApplicationCommandType.CHAT_INPUT and command.description is MISSING: raise InteractionException(11, message="A description is required.") From c11d66b4b2003fd95891761915c4963ce2442656 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 08:41:34 +0100 Subject: [PATCH 05/15] refactor: update regex --- interactions/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/client.py b/interactions/client.py index 1a46e8139..66f4a6964 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -324,7 +324,7 @@ def __check_command( self, command: ApplicationCommand, coro: Coroutine, - regex: str = r"^[a-z]|[0-9]|(?:[._]|[a-z0-9]|[.-]+){1,32}$", + regex: str = r"^[a-z0-9_-]{1,32}$", ) -> None: """ Checks if a command is valid. From 0a9442d3e906cbcefde744a152cea9848ec31370 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:07:01 +0100 Subject: [PATCH 06/15] refactor: add check for context menus and fix typo --- interactions/client.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 66f4a6964..36ec731cc 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -335,10 +335,6 @@ def __check_command( _sub_cmds_present: bool = False def __check_sub_group(_sub_group: Option): - if command.type != ApplicationCommandType.CHAT_INPUT: - raise InteractionException( - 11, message="Only CHAT_INPUT commands can have subcommands!" - ) if _sub_group.name is MISSING: raise InteractionException(11, message="Sub command groups must have a name.") else: @@ -348,7 +344,7 @@ def __check_sub_group(_sub_group: Option): if re.fullmatch(reg, _sub_group.name): raise InteractionException( 11, - message=f"The sub command group name does not match the reg for valid names ('{regex}')", + message=f"The sub command group name does not match the regex for valid names ('{regex}')", ) elif _sub_group.description is MISSING: raise InteractionException(11, message="A description is required.") @@ -377,7 +373,7 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): if re.fullmatch(reg, _sub_command.name): raise InteractionException( 11, - message=f"The sub command name does not match the reg for valid names ('{reg}')", + message=f"The sub command name does not match the regex for valid names ('{reg}')", ) elif _sub_command.description is MISSING: raise InteractionException(11, message="A description is required.") @@ -406,7 +402,7 @@ def __check_options(_option: Option, _sub_command: Option = MISSING): if re.fullmatch(reg, _option.name): raise InteractionException( 11, - message=f"The option name does not match the reg for valid names ('{regex}')", + message=f"The option name does not match the regex for valid names ('{regex}')", ) if _option.description is MISSING: raise InteractionException( @@ -451,7 +447,7 @@ def __check_coro(): and command.type == ApplicationCommandType.CHAT_INPUT ): raise InteractionException( - 11, message=f"Your command does not match the reg for valid names ('{regex}')" + 11, message=f"Your command does not match the regex for valid names ('{regex}')" ) elif command.type == ApplicationCommandType.CHAT_INPUT and command.description is MISSING: raise InteractionException(11, message="A description is required.") @@ -470,6 +466,12 @@ def __check_coro(): raise InteractionException( 11, message="Your command must have less than 25 options." ) + + if command.type != ApplicationCommandType.CHAT_INPUT: + raise InteractionException( + 11, message="Only CHAT_INPUT commands can have options/sub-commands!" + ) + for _option in command.options: if _option.type == OptionType.SUB_COMMAND_GROUP: __check_sub_group(_option) From 516516a12ada2e4ebaa9be3a22ed8fe2ac3c58d8 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:12:12 +0100 Subject: [PATCH 07/15] fix!: add missing ``not`` --- interactions/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 36ec731cc..b5c9f4a42 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -341,7 +341,7 @@ def __check_sub_group(_sub_group: Option): log.debug( f"checking sub command group '{_sub_group.name}' of command '{command.name}'" ) - if re.fullmatch(reg, _sub_group.name): + if not re.fullmatch(reg, _sub_group.name): raise InteractionException( 11, message=f"The sub command group name does not match the regex for valid names ('{regex}')", @@ -370,7 +370,7 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): ) else: log.debug(f"checking sub command '{_sub_command.name}' of command '{command.name}'") - if re.fullmatch(reg, _sub_command.name): + if not re.fullmatch(reg, _sub_command.name): raise InteractionException( 11, message=f"The sub command name does not match the regex for valid names ('{reg}')", @@ -399,7 +399,7 @@ def __check_options(_option: Option, _sub_command: Option = MISSING): else: _options_names.append(_option.name) log.debug(f"checking option '{_option.name}' of command '{command.name}'") - if re.fullmatch(reg, _option.name): + if not re.fullmatch(reg, _option.name): raise InteractionException( 11, message=f"The option name does not match the regex for valid names ('{regex}')", From 25e6ad4865a8c3a0a8b751d2fe52849644132f7b Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:21:08 +0100 Subject: [PATCH 08/15] Add check for duplicated option names on same command --- interactions/client.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index b5c9f4a42..48a2b8967 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -386,10 +386,12 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): raise InteractionException( 11, message="Your sub command must have less than 25 options." ) + _sub_opt_names = [] for _opt in _sub_command.options: - __check_options(_opt, _sub_command) + __check_options(_opt, _sub_opt_names, _sub_command) + del _sub_opt_names - def __check_options(_option: Option, _sub_command: Option = MISSING): + def __check_options(_option: Option, _names: list, _sub_command: Option = MISSING): if getattr(_option, "autocomplete", False) and getattr(_option, "choices", False): log.warning("Autocomplete may not be set to true if choices are present.") if _option.name is MISSING: @@ -414,6 +416,11 @@ def __check_options(_option: Option, _sub_command: Option = MISSING): 11, message="Descriptions must be less than 100 characters.", ) + if _option.name in _names: + raise InteractionException( + 11, message="You must not have two options with the same name in a command!" + ) + _names.append(_option.name) def __check_coro(): if not len(coro.__code__.co_varnames): @@ -472,6 +479,7 @@ def __check_coro(): 11, message="Only CHAT_INPUT commands can have options/sub-commands!" ) + _opt_names = [] for _option in command.options: if _option.type == OptionType.SUB_COMMAND_GROUP: __check_sub_group(_option) @@ -480,7 +488,8 @@ def __check_coro(): __check_sub_command(_option) else: - __check_options(_option) + __check_options(_option, _opt_names) + del _opt_names __check_coro() From 473f4dae445c44f1117447dd138c712c1696234d Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:23:02 +0100 Subject: [PATCH 09/15] refactor: update regex in pyi --- interactions/client.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/client.pyi b/interactions/client.pyi index deca1de08..e9e1f0d7d 100644 --- a/interactions/client.pyi +++ b/interactions/client.pyi @@ -50,7 +50,7 @@ class Client: self, command: ApplicationCommand, coro: Coroutine, - regex: str = "^[\w-]{1,32}$", + regex: str = r"^[a-z0-9_-]{1,32}$", )-> None: ... def command( self, From 3619227d1990a1eb1721c95e636b34b7bbdc4844 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:30:59 +0100 Subject: [PATCH 10/15] refactor: correct description check --- interactions/client.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 48a2b8967..324953231 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -346,7 +346,7 @@ def __check_sub_group(_sub_group: Option): 11, message=f"The sub command group name does not match the regex for valid names ('{regex}')", ) - elif _sub_group.description is MISSING: + elif _sub_group.description is MISSING and not _sub_group.description: raise InteractionException(11, message="A description is required.") elif len(_sub_group.description) > 100: raise InteractionException( @@ -375,7 +375,7 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): 11, message=f"The sub command name does not match the regex for valid names ('{reg}')", ) - elif _sub_command.description is MISSING: + elif _sub_command.description is MISSING or not _sub_command.description: raise InteractionException(11, message="A description is required.") elif len(_sub_command.description) > 100: raise InteractionException( @@ -406,7 +406,7 @@ def __check_options(_option: Option, _names: list, _sub_command: Option = MISSIN 11, message=f"The option name does not match the regex for valid names ('{regex}')", ) - if _option.description is MISSING: + if _option.description is MISSING or not _option.description: raise InteractionException( 11, message="A description is required.", @@ -456,10 +456,16 @@ def __check_coro(): raise InteractionException( 11, message=f"Your command does not match the regex for valid names ('{regex}')" ) - elif command.type == ApplicationCommandType.CHAT_INPUT and command.description is MISSING: + elif ( + command.type == ApplicationCommandType.CHAT_INPUT + and command.description is MISSING + or not command.description + ): raise InteractionException(11, message="A description is required.") elif ( - command.type != ApplicationCommandType.CHAT_INPUT and command.description is not MISSING + command.type != ApplicationCommandType.CHAT_INPUT + and command.description is not MISSING + and command.description ): raise InteractionException( 11, message="Only chat-input commands can have a description." From f6ddf0c83cbf7bfa2c9557bb546c455924d51ffc Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 18:55:31 +0100 Subject: [PATCH 11/15] refactor: check for `**kwargs` in coro check --- interactions/client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interactions/client.py b/interactions/client.py index 324953231..567040b18 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -423,10 +423,13 @@ def __check_options(_option: Option, _names: list, _sub_command: Option = MISSIN _names.append(_option.name) def __check_coro(): + log.debug(f"Checking coroutine: '{coro.__name__}'") if not len(coro.__code__.co_varnames): raise InteractionException( 11, message="Your command needs at least one argument to return context." ) + elif "kwargs" in coro.__code__.co_varnames: + return elif _sub_cmds_present and len(coro.__code__.co_varnames) < 2: raise InteractionException( 11, message="Your command needs one argument for the sub_command." @@ -439,6 +442,10 @@ def __check_coro(): add: int = 1 + abs(_sub_cmds_present) + abs(_sub_groups_present) if len(coro.__code__.co_varnames) + add < len(set(_options_names)): + log.debug( + "Coroutine is missing arguments for options:" + f" {[_arg for _arg in _options_names if _arg not in coro.__code__.co_varnames]}" + ) raise InteractionException( 11, message="You need one argument for every option name in your command!" ) From 408c47047576ca6ddd09b27cb48401091f774b75 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 19:21:52 +0100 Subject: [PATCH 12/15] refactor: indent debug strings to create a visual separation --- interactions/client.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 567040b18..59497150e 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -335,12 +335,14 @@ def __check_command( _sub_cmds_present: bool = False def __check_sub_group(_sub_group: Option): + nonlocal _sub_groups_present + _sub_groups_present = True if _sub_group.name is MISSING: raise InteractionException(11, message="Sub command groups must have a name.") - else: - log.debug( - f"checking sub command group '{_sub_group.name}' of command '{command.name}'" - ) + __indent = 4 + log.debug( + f"{' ' * __indent}checking sub command group '{_sub_group.name}' of command '{command.name}'" + ) if not re.fullmatch(reg, _sub_group.name): raise InteractionException( 11, @@ -362,14 +364,20 @@ def __check_sub_group(_sub_group: Option): __check_sub_command(_sub_command, _sub_group) def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): + nonlocal _sub_cmds_present # + _sub_cmds_present = True if _sub_command.name is MISSING: raise InteractionException(11, message="sub commands must have a name!") if _sub_group is not MISSING: + __indent = 8 log.debug( - f"checking sub command '{_sub_command.name}' of group '{_sub_group.name}'" + f"{' ' * __indent}checking sub command '{_sub_command.name}' of group '{_sub_group.name}'" ) else: - log.debug(f"checking sub command '{_sub_command.name}' of command '{command.name}'") + __indent = 4 + log.debug( + f"{' ' * __indent}checking sub command '{_sub_command.name}' of command '{command.name}'" + ) if not re.fullmatch(reg, _sub_command.name): raise InteractionException( 11, @@ -392,15 +400,22 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): del _sub_opt_names def __check_options(_option: Option, _names: list, _sub_command: Option = MISSING): + nonlocal _options_names if getattr(_option, "autocomplete", False) and getattr(_option, "choices", False): log.warning("Autocomplete may not be set to true if choices are present.") if _option.name is MISSING: raise InteractionException(11, message="Options must have a name.") if _sub_command is not MISSING: - log.debug(f"checking option '{_option.name}' of sub command '{_sub_command.name}'") + __indent = 8 if not _sub_groups_present else 12 + log.debug( + f"{' ' * __indent}checking option '{_option.name}' of sub command '{_sub_command.name}'" + ) else: - _options_names.append(_option.name) - log.debug(f"checking option '{_option.name}' of command '{command.name}'") + __indent = 4 + log.debug( + f"{' ' * __indent}checking option '{_option.name}' of command '{command.name}'" + ) + _options_names.append(_option.name) if not re.fullmatch(reg, _option.name): raise InteractionException( 11, @@ -423,7 +438,8 @@ def __check_options(_option: Option, _names: list, _sub_command: Option = MISSIN _names.append(_option.name) def __check_coro(): - log.debug(f"Checking coroutine: '{coro.__name__}'") + __indent = 4 + log.debug(f"{' ' * __indent}Checking coroutine: '{coro.__name__}'") if not len(coro.__code__.co_varnames): raise InteractionException( 11, message="Your command needs at least one argument to return context." From b85e5b59c6e3b3df553e1da0ba25eb6fa75dda6b Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:08:24 +0100 Subject: [PATCH 13/15] remove leftover # --- interactions/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/client.py b/interactions/client.py index 59497150e..16aa5b831 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -364,7 +364,7 @@ def __check_sub_group(_sub_group: Option): __check_sub_command(_sub_command, _sub_group) def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): - nonlocal _sub_cmds_present # + nonlocal _sub_cmds_present _sub_cmds_present = True if _sub_command.name is MISSING: raise InteractionException(11, message="sub commands must have a name!") From f4ef5be998ba8c4c4d9c74c26403bf998be54edf Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:12:56 +0100 Subject: [PATCH 14/15] refactor: correct logging statement --- interactions/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions/client.py b/interactions/client.py index 16aa5b831..9c2287ab3 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -470,7 +470,7 @@ def __check_coro(): raise InteractionException(11, message="Your command must have a name.") else: - log.debug(f"checking command: {command.name}") + log.debug(f"checking command '{command.name}':") if ( not re.fullmatch(reg, command.name) From fe1f7c2ecbcf9fe1041c388912d8e737b8b2b2d3 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Thu, 17 Feb 2022 21:41:05 +0100 Subject: [PATCH 15/15] fix!: option parsing --- interactions/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interactions/client.py b/interactions/client.py index 9c2287ab3..82be7f346 100644 --- a/interactions/client.py +++ b/interactions/client.py @@ -361,7 +361,7 @@ def __check_sub_group(_sub_group: Option): 11, message="A sub command group cannot contain more than 25 sub commands!" ) for _sub_command in _sub_group.options: - __check_sub_command(_sub_command, _sub_group) + __check_sub_command(Option(**_sub_command), _sub_group) def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): nonlocal _sub_cmds_present @@ -396,7 +396,7 @@ def __check_sub_command(_sub_command: Option, _sub_group: Option = MISSING): ) _sub_opt_names = [] for _opt in _sub_command.options: - __check_options(_opt, _sub_opt_names, _sub_command) + __check_options(Option(**_opt), _sub_opt_names, _sub_command) del _sub_opt_names def __check_options(_option: Option, _names: list, _sub_command: Option = MISSING): @@ -457,7 +457,7 @@ def __check_coro(): ) add: int = 1 + abs(_sub_cmds_present) + abs(_sub_groups_present) - if len(coro.__code__.co_varnames) + add < len(set(_options_names)): + if len(coro.__code__.co_varnames) - add < len(set(_options_names)): log.debug( "Coroutine is missing arguments for options:" f" {[_arg for _arg in _options_names if _arg not in coro.__code__.co_varnames]}"