diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..e61de53c0 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +ignore = E203 E501 W503 W504 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a882c3d7..bb0fcae48 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,6 +8,7 @@ What changes were made? ## Checklist +- [ ] I've run the `pre_push.py` script to format and lint code. - [ ] I've checked this pull request runs on `Python 3.6.X`. - [ ] This fixes something in [Issues](https://github.com/eunwoo1104/discord-py-slash-command/issues). - Issue: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..a5c70a06d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +jobs: + lint-multi-os: + name: Lint ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.x + - uses: actions/cache@v1 + with: + key: v0-${{ runner.os }}-pip-lint-${{ hashFiles('setup.py') }} + path: ~/.cache/pip + restore-keys: | + v0-${{ runner.os }}-pip-lint- + v0-${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[lint] + - name: Run black + run: black --check --verbose . + - name: Run flake8 + run: flake8 --exclude docs --statistics + - name: Run isort + run: isort -cv . + - name: Run sphinx + run: sphinx-build -W --keep-going docs/ /tmp/foo + strategy: + matrix: + os: [macOS-latest, ubuntu-latest, windows-latest] +name: CI +on: [pull_request, push] diff --git a/.gitignore b/.gitignore index 839b32575..9fa8b2b65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,8 @@ -.idea -__pycache__ -test.py -test2.py -test3.py -docs/_build -slash.log -test -__*.py -soontm.png - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg *.egg -MANIFEST +*.egg-info/ +*.eggs/ +*.pyc +.cache/ +_build/ +build/ +dist/ \ No newline at end of file diff --git a/README.md b/README.md index 2640437ed..73a0cf552 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Documentation ⦿ Discord Server
- + ## About Discord Slash Commands are a new implementation for the Bot API that utilize the forward-slash "/" symbol. Released on 15 December 2020, many bot developers are still learning to learn how to implement this into @@ -79,8 +79,8 @@ def setup(bot): ``` -------- -This library is based on gateway event. If you are looking for webserver based, have a look at this: -[dispike](https://github.com/ms7m/dispike) -[discord-interactions-python](https://github.com/discord/discord-interactions-python) -Or for other languages: -[discord-api-docs Community Resources: Interactions](https://discord.com/developers/docs/topics/community-resources#interactions) \ No newline at end of file +- This library is based on gateway event. If you are looking for webserver based, have a look at this: + - [dispike](https://github.com/ms7m/dispike) + - [discord-interactions-python](https://github.com/discord/discord-interactions-python) +- Or for other languages: + - [discord-api-docs Community Resources: Interactions](https://discord.com/developers/docs/topics/community-resources#interactions) \ No newline at end of file diff --git a/discord_slash/__init__.py b/discord_slash/__init__.py index 662495e71..7fa481236 100644 --- a/discord_slash/__init__.py +++ b/discord_slash/__init__.py @@ -8,12 +8,11 @@ :license: MIT """ -from .client import SlashCommand -from .model import SlashCommandOptionType -from .context import SlashContext -from .context import ComponentContext -from .dpy_overrides import ComponentMessage -from .utils import manage_commands -from .utils import manage_components - -__version__ = "1.2.2" +from .client import SlashCommand # noqa: F401 +from .const import __version__ # noqa: F401 +from .context import ComponentContext # noqa: F401 +from .context import SlashContext # noqa: F401 +from .dpy_overrides import ComponentMessage # noqa: F401 +from .model import SlashCommandOptionType # noqa: F401 +from .utils import manage_commands # noqa: F401 +from .utils import manage_components # noqa: F401 diff --git a/discord_slash/client.py b/discord_slash/client.py index 82bc521f8..840565647 100644 --- a/discord_slash/client.py +++ b/discord_slash/client.py @@ -1,15 +1,13 @@ import copy import logging import typing -import discord -from inspect import iscoroutinefunction, getdoc from contextlib import suppress +from inspect import getdoc, iscoroutinefunction + +import discord from discord.ext import commands -from . import http -from . import model -from . import error -from . import context -from . import dpy_overrides + +from . import context, error, http, model from .utils import manage_commands @@ -43,13 +41,15 @@ class SlashCommand: :ivar has_listener: Whether discord client has listener add function. """ - def __init__(self, - client: typing.Union[discord.Client, commands.Bot], - sync_commands: bool = False, - delete_from_unused_guilds: bool = False, - sync_on_cog_reload: bool = False, - override_type: bool = False, - application_id: typing.Optional[int] = None): + def __init__( + self, + client: typing.Union[discord.Client, commands.Bot], + sync_commands: bool = False, + delete_from_unused_guilds: bool = False, + sync_on_cog_reload: bool = False, + override_type: bool = False, + application_id: typing.Optional[int] = None, + ): self._discord = client self.commands = {} self.subcommands = {} @@ -61,12 +61,18 @@ def __init__(self, if self.sync_commands: self._discord.loop.create_task(self.sync_all_commands(delete_from_unused_guilds)) - if not isinstance(client, commands.Bot) and not isinstance(client, commands.AutoShardedBot) and not override_type: - self.logger.warning("Detected discord.Client! It is highly recommended to use `commands.Bot`. Do not add any `on_socket_response` event.") + if ( + not isinstance(client, commands.Bot) + and not isinstance(client, commands.AutoShardedBot) + and not override_type + ): + self.logger.warning( + "Detected discord.Client! It is highly recommended to use `commands.Bot`. Do not add any `on_socket_response` event." + ) self._discord.on_socket_response = self.on_socket_response self.has_listener = False else: - if not hasattr(self._discord, 'slash'): + if not hasattr(self._discord, "slash"): self._discord.slash = self else: raise error.DuplicateSlashClient("You can't have duplicate SlashCommand instances!") @@ -96,7 +102,9 @@ def override_remove_cog(name: str): def override_reload_extension(*args): orig_reload(*args) - self._discord.loop.create_task(self.sync_all_commands(delete_from_unused_guilds)) + self._discord.loop.create_task( + self.sync_all_commands(delete_from_unused_guilds) + ) self._discord.reload_extension = override_reload_extension @@ -110,12 +118,18 @@ def get_cog_commands(self, cog: commands.Cog): :param cog: Cog that has slash commands. :type cog: discord.ext.commands.Cog """ - if hasattr(cog, '_slash_registered'): # Temporary warning - return self.logger.warning("Calling get_cog_commands is no longer required " - "to add cog slash commands. Make sure to remove all calls to this function.") + if hasattr(cog, "_slash_registered"): # Temporary warning + return self.logger.warning( + "Calling get_cog_commands is no longer required " + "to add cog slash commands. Make sure to remove all calls to this function." + ) cog._slash_registered = True # Assuming all went well func_list = [getattr(cog, x) for x in dir(cog)] - res = [x for x in func_list if isinstance(x, (model.CogBaseCommandObject, model.CogSubcommandObject))] + res = [ + x + for x in func_list + if isinstance(x, (model.CogBaseCommandObject, model.CogSubcommandObject)) + ] for x in res: x.cog = cog if isinstance(x, model.CogBaseCommandObject): @@ -134,7 +148,9 @@ def get_cog_commands(self, cog: commands.Cog): for applicable_guild in base_permissions: if applicable_guild not in base_command.permissions: base_command.permissions[applicable_guild] = [] - base_command.permissions[applicable_guild].extend(base_permissions[applicable_guild]) + base_command.permissions[applicable_guild].extend( + base_permissions[applicable_guild] + ) self.commands[x.base].has_subcommands = True @@ -163,11 +179,14 @@ def remove_cog_commands(self, cog): :param cog: Cog that has slash commands. :type cog: discord.ext.commands.Cog """ - if hasattr(cog, '_slash_registered'): + if hasattr(cog, "_slash_registered"): del cog._slash_registered func_list = [getattr(cog, x) for x in dir(cog)] - res = [x for x in func_list if - isinstance(x, (model.CogBaseCommandObject, model.CogSubcommandObject))] + res = [ + x + for x in func_list + if isinstance(x, (model.CogBaseCommandObject, model.CogSubcommandObject)) + ] for x in res: if isinstance(x, model.CogBaseCommandObject): if x.name not in self.commands: @@ -215,10 +234,7 @@ async def to_dict(self): for i in self.commands[x].allowed_guild_ids: if i not in all_guild_ids: all_guild_ids.append(i) - cmds = { - "global": [], - "guild": {x: [] for x in all_guild_ids} - } + cmds = {"global": [], "guild": {x: [] for x in all_guild_ids}} wait = {} # Before merging to return dict, let's first put commands to temporary dict. for x in self.commands: selected = self.commands[x] @@ -231,7 +247,7 @@ async def to_dict(self): "description": selected.description or "No Description.", "options": selected.options or [], "default_permission": selected.default_permission, - "permissions": {} + "permissions": {}, } if y in selected.permissions: command_dict["permissions"][y] = selected.permissions[y] @@ -244,7 +260,7 @@ async def to_dict(self): "description": selected.description or "No Description.", "options": selected.options or [], "default_permission": selected.default_permission, - "permissions": selected.permissions or {} + "permissions": selected.permissions or {}, } wait["global"][x] = copy.deepcopy(command_dict) @@ -263,7 +279,7 @@ async def to_dict(self): "name": sub.name, "description": sub.description or "No Description.", "type": model.SlashCommandOptionType.SUB_COMMAND, - "options": sub.options or [] + "options": sub.options or [], } if sub.allowed_guild_ids: for z in sub.allowed_guild_ids: @@ -276,7 +292,7 @@ async def to_dict(self): "name": y, "description": "No Description.", "type": model.SlashCommandOptionType.SUB_COMMAND_GROUP, - "options": [] + "options": [], } for z in sub: sub_sub = sub[z] @@ -284,7 +300,7 @@ async def to_dict(self): "name": sub_sub.name, "description": sub_sub.description or "No Description.", "type": model.SlashCommandOptionType.SUB_COMMAND, - "options": sub_sub.options or [] + "options": sub_sub.options or [], } if sub_sub.allowed_guild_ids: for i in sub_sub.allowed_guild_ids: @@ -322,21 +338,21 @@ async def sync_all_commands( permissions_map = {} cmds = await self.to_dict() self.logger.info("Syncing commands...") - cmds_formatted = {None: cmds['global']} - for guild in cmds['guild']: - cmds_formatted[guild] = cmds['guild'][guild] + cmds_formatted = {None: cmds["global"]} + for guild in cmds["guild"]: + cmds_formatted[guild] = cmds["guild"][guild] for scope in cmds_formatted: permissions = {} new_cmds = cmds_formatted[scope] - existing_cmds = await self.req.get_all_commands(guild_id = scope) + existing_cmds = await self.req.get_all_commands(guild_id=scope) existing_by_name = {} - to_send=[] + to_send = [] changed = False for cmd in existing_cmds: existing_by_name[cmd["name"]] = model.CommandData(**cmd) - if len(new_cmds) != len(existing_cmds): + if len(new_cmds) != len(existing_cmds): changed = True for command in new_cmds: @@ -346,22 +362,27 @@ async def sync_all_commands( cmd_data = model.CommandData(**command) existing_cmd = existing_by_name[cmd_name] if cmd_data != existing_cmd: - changed=True + changed = True to_send.append(command) else: command_with_id = command command_with_id["id"] = existing_cmd.id to_send.append(command_with_id) else: - changed=True + changed = True to_send.append(command) - if changed: - self.logger.debug(f"Detected changes on {scope if scope is not None else 'global'}, updating them") - existing_cmds = await self.req.put_slash_commands(slash_commands=to_send, guild_id=scope) + self.logger.debug( + f"Detected changes on {scope if scope is not None else 'global'}, updating them" + ) + existing_cmds = await self.req.put_slash_commands( + slash_commands=to_send, guild_id=scope + ) else: - self.logger.debug(f"Detected no changes on {scope if scope is not None else 'global'}, skipping") + self.logger.debug( + f"Detected no changes on {scope if scope is not None else 'global'}, skipping" + ) id_name_map = {} for cmd in existing_cmds: @@ -376,11 +397,10 @@ async def sync_all_commands( permission = { "id": cmd_id, "guild_id": applicable_guild, - "permissions": cmd_permissions[applicable_guild] + "permissions": cmd_permissions[applicable_guild], } permissions_map[applicable_guild].append(permission) - self.logger.info("Syncing permissions...") self.logger.debug(f"Commands permission data are {permissions_map}") for scope in permissions_map: @@ -393,39 +413,45 @@ async def sync_all_commands( else: existing_perms_model = {} for existing_perm in existing_perms: - existing_perms_model[existing_perm["id"]] = model.GuildPermissionsData(**existing_perm) + existing_perms_model[existing_perm["id"]] = model.GuildPermissionsData( + **existing_perm + ) for new_perm in new_perms: if new_perm["id"] not in existing_perms_model: changed = True break - if existing_perms_model[new_perm["id"]] != model.GuildPermissionsData(**new_perm): + if existing_perms_model[new_perm["id"]] != model.GuildPermissionsData( + **new_perm + ): changed = True break - + if changed: self.logger.debug(f"Detected permissions changes on {scope}, updating them") await self.req.update_guild_commands_permissions(scope, new_perms) else: self.logger.debug(f"Detected no permissions changes on {scope}, skipping") - if delete_from_unused_guilds: self.logger.info("Deleting unused guild commands...") - other_guilds = [guild.id for guild in self._discord.guilds if guild.id not in cmds["guild"]] + other_guilds = [ + guild.id for guild in self._discord.guilds if guild.id not in cmds["guild"] + ] # This is an extremly bad way to do this, because slash cmds can be in guilds the bot isn't in # But it's the only way until discord makes an endpoint to request all the guild with cmds registered. - + for guild in other_guilds: with suppress(discord.Forbidden): - existing = await self.req.get_all_commands(guild_id = guild) + existing = await self.req.get_all_commands(guild_id=guild) if len(existing) != 0: self.logger.debug(f"Deleting commands from {guild}") await self.req.put_slash_commands(slash_commands=[], guild_id=guild) - if delete_perms_from_unused_guilds: self.logger.info("Deleting unused guild permissions...") - other_guilds = [guild.id for guild in self._discord.guilds if guild.id not in permissions_map.keys()] + other_guilds = [ + guild.id for guild in self._discord.guilds if guild.id not in permissions_map.keys() + ] for guild in other_guilds: with suppress(discord.Forbidden): self.logger.debug(f"Deleting permissions from {guild}") @@ -435,16 +461,18 @@ async def sync_all_commands( self.logger.info("Completed syncing all commands!") - def add_slash_command(self, - cmd, - name: str = None, - description: str = None, - guild_ids: typing.List[int] = None, - options: list = None, - default_permission: bool = True, - permissions: typing.Dict[int, list] = None, - connector: dict = None, - has_subcommands: bool = False): + def add_slash_command( + self, + cmd, + name: str = None, + description: str = None, + guild_ids: typing.List[int] = None, + options: list = None, + default_permission: bool = True, + permissions: typing.Dict[int, list] = None, + connector: dict = None, + has_subcommands: bool = False, + ): """ Registers slash command to SlashCommand. @@ -496,26 +524,28 @@ def add_slash_command(self, "default_permission": default_permission, "api_permissions": permissions, "connector": connector or {}, - "has_subcommands": has_subcommands + "has_subcommands": has_subcommands, } obj = model.BaseCommandObject(name, _cmd) self.commands[name] = obj self.logger.debug(f"Added command `{name}`") return obj - def add_subcommand(self, - cmd, - base, - subcommand_group=None, - name=None, - description: str = None, - base_description: str = None, - base_default_permission: bool = True, - base_permissions: typing.Dict[int, list] = None, - subcommand_group_description: str = None, - guild_ids: typing.List[int] = None, - options: list = None, - connector: dict = None): + def add_subcommand( + self, + cmd, + base, + subcommand_group=None, + name=None, + description: str = None, + base_description: str = None, + base_default_permission: bool = True, + base_permissions: typing.Dict[int, list] = None, + subcommand_group_description: str = None, + guild_ids: typing.List[int] = None, + options: list = None, + connector: dict = None, + ): """ Registers subcommand to SlashCommand. @@ -567,7 +597,7 @@ def add_subcommand(self, "default_permission": base_default_permission, "api_permissions": base_permissions, "connector": {}, - "has_subcommands": True + "has_subcommands": True, } _sub = { "func": cmd, @@ -577,7 +607,7 @@ def add_subcommand(self, "sub_group_desc": subcommand_group_description, "guild_ids": guild_ids, "api_options": options, - "connector": connector or {} + "connector": connector or {}, } if base not in self.commands: self.commands[base] = model.BaseCommandObject(base, _cmd) @@ -588,7 +618,9 @@ def add_subcommand(self, for applicable_guild in base_permissions: if applicable_guild not in base_command.permissions: base_command.permissions[applicable_guild] = [] - base_command.permissions[applicable_guild].extend(base_permissions[applicable_guild]) + base_command.permissions[applicable_guild].extend( + base_permissions[applicable_guild] + ) if base_command.description: _cmd["description"] = base_command.description if base not in self.subcommands: @@ -605,18 +637,22 @@ def add_subcommand(self, raise error.DuplicateCommand(f"{base} {name}") obj = model.SubcommandObject(_sub, base, name) self.subcommands[base][name] = obj - self.logger.debug(f"Added subcommand `{base} {subcommand_group or ''} {name or cmd.__name__}`") + self.logger.debug( + f"Added subcommand `{base} {subcommand_group or ''} {name or cmd.__name__}`" + ) return obj - def slash(self, - *, - name: str = None, - description: str = None, - guild_ids: typing.List[int] = None, - options: typing.List[dict] = None, - default_permission: bool = True, - permissions: dict = None, - connector: dict = None): + def slash( + self, + *, + name: str = None, + description: str = None, + guild_ids: typing.List[int] = None, + options: typing.List[dict] = None, + default_permission: bool = True, + permissions: dict = None, + connector: dict = None, + ): """ Decorator that registers coroutine as a slash command.\n All decorator args must be passed as keyword-only args.\n @@ -681,26 +717,37 @@ def wrapper(cmd): if decorator_permissions: permissions.update(decorator_permissions) - obj = self.add_slash_command(cmd, name, description, guild_ids, options, default_permission, permissions, connector) + obj = self.add_slash_command( + cmd, + name, + description, + guild_ids, + options, + default_permission, + permissions, + connector, + ) return obj return wrapper - def subcommand(self, - *, - base, - subcommand_group=None, - name=None, - description: str = None, - base_description: str = None, - base_desc: str = None, - base_default_permission: bool = True, - base_permissions: dict = None, - subcommand_group_description: str = None, - sub_group_desc: str = None, - guild_ids: typing.List[int] = None, - options: typing.List[dict] = None, - connector: dict = None): + def subcommand( + self, + *, + base, + subcommand_group=None, + name=None, + description: str = None, + base_description: str = None, + base_desc: str = None, + base_default_permission: bool = True, + base_permissions: dict = None, + subcommand_group_description: str = None, + sub_group_desc: str = None, + guild_ids: typing.List[int] = None, + options: typing.List[dict] = None, + connector: dict = None, + ): """ Decorator that registers subcommand.\n Unlike discord.py, you don't need base command.\n @@ -764,7 +811,20 @@ def wrapper(cmd): if decorator_permissions: base_permissions.update(decorator_permissions) - obj = self.add_subcommand(cmd, base, subcommand_group, name, description, base_description, base_default_permission, base_permissions, subcommand_group_description, guild_ids, options, connector) + obj = self.add_subcommand( + cmd, + base, + subcommand_group, + name, + description, + base_description, + base_default_permission, + base_permissions, + subcommand_group_description, + guild_ids, + options, + connector, + ) return obj return wrapper @@ -772,12 +832,13 @@ def wrapper(cmd): def permission(self, guild_id: int, permissions: list): """ Decorator that add permissions. This will set the permissions for a single guild, you can use it more than once for each command. - :param guild_id: ID of the guild for the permissions. + :param guild_id: ID of the guild for the permissions. :type guild_id: int :param permissions: Permission requirements of the slash command. Default ``None``. :type permissions: dict - + """ + def wrapper(cmd): if not getattr(cmd, "__permissions__", None): cmd.__permissions__ = {} @@ -786,8 +847,13 @@ def wrapper(cmd): return wrapper - async def process_options(self, guild: discord.Guild, options: list, connector: dict, - temporary_auto_convert: dict = None) -> dict: + async def process_options( + self, + guild: discord.Guild, + options: list, + connector: dict, + temporary_auto_convert: dict = None, + ) -> dict: """ Processes Role, User, and Channel option types to discord.py's models. @@ -809,7 +875,7 @@ async def process_options(self, guild: discord.Guild, options: list, connector: # and 2nd as a actual fetching method. [guild.get_member, guild.fetch_member], guild.get_channel, - guild.get_role + guild.get_role, ] types = { @@ -827,7 +893,7 @@ async def process_options(self, guild: discord.Guild, options: list, connector: "ROLE": 2, model.SlashCommandOptionType.ROLE: 2, 8: 2, - "8": 2 + "8": 2, } to_return = {} @@ -852,10 +918,16 @@ async def process_options(self, guild: discord.Guild, options: list, connector: loaded_converter = loaded_converter[1] if not processed: try: - processed = await loaded_converter(int(x["value"])) \ - if iscoroutinefunction(loaded_converter) else \ - loaded_converter(int(x["value"])) - except (discord.Forbidden, discord.HTTPException, discord.NotFound): # Just in case. + processed = ( + await loaded_converter(int(x["value"])) + if iscoroutinefunction(loaded_converter) + else loaded_converter(int(x["value"])) + ) + except ( + discord.Forbidden, + discord.HTTPException, + discord.NotFound, + ): # Just in case. self.logger.warning("Failed fetching discord object! Passing ID instead.") processed = int(x["value"]) to_return[connector.get(x["name"]) or x["name"]] = processed @@ -923,7 +995,10 @@ async def _on_slash(self, to_use): selected_cmd = self.commands[to_use["data"]["name"]] - if selected_cmd.allowed_guild_ids and ctx.guild_id not in selected_cmd.allowed_guild_ids: + if ( + selected_cmd.allowed_guild_ids + and ctx.guild_id not in selected_cmd.allowed_guild_ids + ): return if selected_cmd.has_subcommands and not selected_cmd.func: @@ -940,8 +1015,16 @@ async def _on_slash(self, to_use): for x in selected_cmd.options: temporary_auto_convert[x["name"].lower()] = x["type"] - args = await self.process_options(ctx.guild, to_use["data"]["options"], selected_cmd.connector, temporary_auto_convert) \ - if "options" in to_use["data"] else {} + args = ( + await self.process_options( + ctx.guild, + to_use["data"]["options"], + selected_cmd.connector, + temporary_auto_convert, + ) + if "options" in to_use["data"] + else {} + ) self._discord.dispatch("slash_command", ctx) @@ -980,8 +1063,13 @@ async def handle_subcommand(self, ctx: context.SlashContext, data: dict): for n in selected.options: temporary_auto_convert[n["name"].lower()] = n["type"] - args = await self.process_options(ctx.guild, x["options"], selected.connector, temporary_auto_convert) \ - if "options" in x else {} + args = ( + await self.process_options( + ctx.guild, x["options"], selected.connector, temporary_auto_convert + ) + if "options" in x + else {} + ) self._discord.dispatch("slash_command", ctx) await self.invoke_command(selected, ctx, args) return @@ -993,8 +1081,13 @@ async def handle_subcommand(self, ctx: context.SlashContext, data: dict): for n in selected.options: temporary_auto_convert[n["name"].lower()] = n["type"] - args = await self.process_options(ctx.guild, sub_opts, selected.connector, temporary_auto_convert) \ - if "options" in sub else {} + args = ( + await self.process_options( + ctx.guild, sub_opts, selected.connector, temporary_auto_convert + ) + if "options" in sub + else {} + ) self._discord.dispatch("slash_command", ctx) await self.invoke_command(selected, ctx, args) @@ -1025,7 +1118,7 @@ async def on_slash_command_error(ctx, ex): :return: """ if self.has_listener: - if self._discord.extra_events.get('on_slash_command_error'): + if self._discord.extra_events.get("on_slash_command_error"): self._discord.dispatch("slash_command_error", ctx, ex) return if hasattr(self._discord, "on_slash_command_error"): diff --git a/discord_slash/cog_ext.py b/discord_slash/cog_ext.py index 1d5589d5c..54e699f1f 100644 --- a/discord_slash/cog_ext.py +++ b/discord_slash/cog_ext.py @@ -1,17 +1,20 @@ -import typing import inspect +import typing + from .model import CogBaseCommandObject, CogSubcommandObject from .utils import manage_commands -def cog_slash(*, - name: str = None, - description: str = None, - guild_ids: typing.List[int] = None, - options: typing.List[dict] = None, - default_permission: bool = True, - permissions: typing.Dict[int, list] = None, - connector: dict = None): +def cog_slash( + *, + name: str = None, + description: str = None, + guild_ids: typing.List[int] = None, + options: typing.List[dict] = None, + default_permission: bool = True, + permissions: typing.Dict[int, list] = None, + connector: dict = None +): """ Decorator for Cog to add slash command.\n Almost same as :func:`.client.SlashCommand.slash`. @@ -43,6 +46,7 @@ async def ping(self, ctx: SlashContext): :param connector: Kwargs connector for the command. Default ``None``. :type connector: dict """ + def wrapper(cmd): desc = description or inspect.getdoc(cmd) if options is None: @@ -58,26 +62,29 @@ def wrapper(cmd): "default_permission": default_permission, "api_permissions": permissions, "connector": connector, - "has_subcommands": False + "has_subcommands": False, } return CogBaseCommandObject(name or cmd.__name__, _cmd) + return wrapper -def cog_subcommand(*, - base, - subcommand_group=None, - name=None, - description: str = None, - base_description: str = None, - base_desc: str = None, - base_default_permission: bool = True, - base_permissions: typing.Dict[int, list] = None, - subcommand_group_description: str = None, - sub_group_desc: str = None, - guild_ids: typing.List[int] = None, - options: typing.List[dict] = None, - connector: dict = None): +def cog_subcommand( + *, + base, + subcommand_group=None, + name=None, + description: str = None, + base_description: str = None, + base_desc: str = None, + base_default_permission: bool = True, + base_permissions: typing.Dict[int, list] = None, + subcommand_group_description: str = None, + sub_group_desc: str = None, + guild_ids: typing.List[int] = None, + options: typing.List[dict] = None, + connector: dict = None +): """ Decorator for Cog to add subcommand.\n Almost same as :func:`.client.SlashCommand.subcommand`. @@ -138,7 +145,7 @@ def wrapper(cmd): "default_permission": base_default_permission, "api_permissions": base_permissions, "connector": {}, - "has_subcommands": True + "has_subcommands": True, } _sub = { @@ -149,7 +156,8 @@ def wrapper(cmd): "sub_group_desc": subcommand_group_description, "guild_ids": guild_ids, "api_options": opts, - "connector": connector + "connector": connector, } return CogSubcommandObject(base, _cmd, subcommand_group, name or cmd.__name__, _sub) + return wrapper diff --git a/discord_slash/const.py b/discord_slash/const.py new file mode 100644 index 000000000..5b64473ba --- /dev/null +++ b/discord_slash/const.py @@ -0,0 +1,5 @@ +"""Discord Slash Constants""" + +__version__ = "1.2.2" + +BASE_API = "https://discord.com/api/v8" diff --git a/discord_slash/context.py b/discord_slash/context.py index fa56a10cb..5972ccad5 100644 --- a/discord_slash/context.py +++ b/discord_slash/context.py @@ -1,17 +1,13 @@ import datetime import typing -import asyncio from warnings import warn import discord -from contextlib import suppress from discord.ext import commands from discord.utils import snowflake_time -from . import http -from . import error -from . import model -from . dpy_overrides import ComponentMessage +from . import error, http, model +from .dpy_overrides import ComponentMessage class InteractionContext: @@ -36,11 +32,13 @@ class InteractionContext: :ivar author: User or Member instance of the command invoke. """ - def __init__(self, - _http: http.SlashCommandRequest, - _json: dict, - _discord: typing.Union[discord.Client, commands.Bot], - logger): + def __init__( + self, + _http: http.SlashCommandRequest, + _json: dict, + _discord: typing.Union[discord.Client, commands.Bot], + logger, + ): self._token = _json["token"] self.message = None # Should be set later. self.interaction_id = _json["id"] @@ -51,10 +49,14 @@ def __init__(self, self.responded = False self._deferred_hidden = False # To check if the patch to the deferred response matches self.guild_id = int(_json["guild_id"]) if "guild_id" in _json.keys() else None - self.author_id = int(_json["member"]["user"]["id"] if "member" in _json.keys() else _json["user"]["id"]) + self.author_id = int( + _json["member"]["user"]["id"] if "member" in _json.keys() else _json["user"]["id"] + ) self.channel_id = int(_json["channel_id"]) if self.guild: - self.author = discord.Member(data=_json["member"], state=self.bot._connection, guild=self.guild) + self.author = discord.Member( + data=_json["member"], state=self.bot._connection, guild=self.guild + ) elif self.guild_id: self.author = discord.User(data=_json["member"]["user"], state=self.bot._connection) else: @@ -63,12 +65,20 @@ def __init__(self, @property def _deffered_hidden(self): - warn("`_deffered_hidden` as been renamed to `_deferred_hidden`.", DeprecationWarning, stacklevel=2) + warn( + "`_deffered_hidden` as been renamed to `_deferred_hidden`.", + DeprecationWarning, + stacklevel=2, + ) return self._deferred_hidden @_deffered_hidden.setter def _deffered_hidden(self, value): - warn("`_deffered_hidden` as been renamed to `_deferred_hidden`.", DeprecationWarning, stacklevel=2) + warn( + "`_deffered_hidden` as been renamed to `_deferred_hidden`.", + DeprecationWarning, + stacklevel=2, + ) self._deferred_hidden = value @property @@ -114,18 +124,20 @@ async def defer(self, hidden: bool = False): await self._http.post_initial_response(base, self.interaction_id, self._token) self.deferred = True - async def send(self, - content: str = "", *, - embed: discord.Embed = None, - embeds: typing.List[discord.Embed] = None, - tts: bool = False, - file: discord.File = None, - files: typing.List[discord.File] = None, - allowed_mentions: discord.AllowedMentions = None, - hidden: bool = False, - delete_after: float = None, - components: typing.List[dict] = None, - ) -> model.SlashMessage: + async def send( + self, + content: str = "", + *, + embed: discord.Embed = None, + embeds: typing.List[discord.Embed] = None, + tts: bool = False, + file: discord.File = None, + files: typing.List[discord.File] = None, + allowed_mentions: discord.AllowedMentions = None, + hidden: bool = False, + delete_after: float = None, + components: typing.List[dict] = None, + ) -> model.SlashMessage: """ Sends response of the slash command. @@ -172,14 +184,19 @@ async def send(self, if delete_after and hidden: raise error.IncorrectFormat("You can't delete a hidden message!") if components and not all(comp.get("type") == 1 for comp in components): - raise error.IncorrectFormat("The top level of the components list must be made of ActionRows!") + raise error.IncorrectFormat( + "The top level of the components list must be made of ActionRows!" + ) base = { "content": content, "tts": tts, "embeds": [x.to_dict() for x in embeds] if embeds else [], - "allowed_mentions": allowed_mentions.to_dict() if allowed_mentions - else self.bot.allowed_mentions.to_dict() if self.bot.allowed_mentions else {}, + "allowed_mentions": allowed_mentions.to_dict() + if allowed_mentions + else self.bot.allowed_mentions.to_dict() + if self.bot.allowed_mentions + else {}, "components": components or [], } if hidden: @@ -199,10 +216,7 @@ async def send(self, resp = await self._http.edit(base, self._token, files=files) self.deferred = False else: - json_data = { - "type": 4, - "data": base - } + json_data = {"type": 4, "data": base} await self._http.post_initial_response(json_data, self.interaction_id, self._token) if not hidden: resp = await self._http.edit({}, self._token) @@ -215,11 +229,13 @@ async def send(self, for file in files: file.close() if not hidden: - smsg = model.SlashMessage(state=self.bot._connection, - data=resp, - channel=self.channel or discord.Object(id=self.channel_id), - _http=self._http, - interaction_token=self._token) + smsg = model.SlashMessage( + state=self.bot._connection, + data=resp, + channel=self.channel or discord.Object(id=self.channel_id), + _http=self._http, + interaction_token=self._token, + ) if delete_after: self.bot.loop.create_task(smsg.delete(delay=delete_after)) if initial_message: @@ -240,11 +256,14 @@ class SlashContext(InteractionContext): :ivar subcommand_group: Subcommand group of the command. :ivar command_id: ID of the command. """ - def __init__(self, - _http: http.SlashCommandRequest, - _json: dict, - _discord: typing.Union[discord.Client, commands.Bot], - logger): + + def __init__( + self, + _http: http.SlashCommandRequest, + _json: dict, + _discord: typing.Union[discord.Client, commands.Bot], + logger, + ): self.name = self.command = self.invoked_with = _json["data"]["name"] self.args = [] self.kwargs = {} @@ -264,11 +283,14 @@ class ComponentContext(InteractionContext): :ivar origin_message: The origin message of the component. Not available if the origin message was ephemeral. :ivar origin_message_id: The ID of the origin message. """ - def __init__(self, - _http: http.SlashCommandRequest, - _json: dict, - _discord: typing.Union[discord.Client, commands.Bot], - logger): + + def __init__( + self, + _http: http.SlashCommandRequest, + _json: dict, + _discord: typing.Union[discord.Client, commands.Bot], + logger, + ): self.custom_id = self.component_id = _json["data"]["custom_id"] self.component_type = _json["data"]["component_type"] super().__init__(_http=_http, _json=_json, _discord=_discord, logger=logger) @@ -276,8 +298,9 @@ def __init__(self, self.origin_message_id = int(_json["message"]["id"]) if "message" in _json.keys() else None if self.origin_message_id and (_json["message"]["flags"] & 64) != 64: - self.origin_message = ComponentMessage(state=self.bot._connection, channel=self.channel, - data=_json["message"]) + self.origin_message = ComponentMessage( + state=self.bot._connection, channel=self.channel, data=_json["message"] + ) async def defer(self, hidden: bool = False, edit_origin: bool = False): """ @@ -331,8 +354,13 @@ async def edit_origin(self, **fields): _resp["embeds"] = [x.to_dict() for x in embeds] allowed_mentions = fields.get("allowed_mentions") - _resp["allowed_mentions"] = allowed_mentions.to_dict() if allowed_mentions else \ - self.bot.allowed_mentions.to_dict() if self.bot.allowed_mentions else {} + _resp["allowed_mentions"] = ( + allowed_mentions.to_dict() + if allowed_mentions + else self.bot.allowed_mentions.to_dict() + if self.bot.allowed_mentions + else {} + ) if not self.responded: if files and not self.deferred: @@ -340,12 +368,11 @@ async def edit_origin(self, **fields): if self.deferred: _json = await self._http.edit(_resp, self._token, files=files) self.deferred = False - else: - json_data = { - "type": 7, - "data": _resp - } - _json = await self._http.post_initial_response(json_data, self.interaction_id, self._token) + else: # noqa: F841 + json_data = {"type": 7, "data": _resp} + _json = await self._http.post_initial_response( # noqa: F841 + json_data, self.interaction_id, self._token + ) self.responded = True else: raise error.IncorrectFormat("Already responded") diff --git a/discord_slash/dpy_overrides.py b/discord_slash/dpy_overrides.py index 58d63c732..f943f29b4 100644 --- a/discord_slash/dpy_overrides.py +++ b/discord_slash/dpy_overrides.py @@ -1,10 +1,7 @@ import discord +from discord import AllowedMentions, File, InvalidArgument, abc, http, utils from discord.ext import commands -from discord import AllowedMentions, InvalidArgument, File from discord.http import Route -from discord import http -from discord import abc -from discord import utils class ComponentMessage(discord.Message): @@ -12,7 +9,7 @@ class ComponentMessage(discord.Message): def __init__(self, *, state, channel, data): super().__init__(state=state, channel=channel, data=data) - self.components = data['components'] + self.components = data["components"] def new_override(cls, *args, **kwargs): @@ -25,71 +22,96 @@ def new_override(cls, *args, **kwargs): discord.message.Message.__new__ = new_override -def send_files(self, channel_id, *, files, content=None, tts=False, embed=None, components=None, - nonce=None, allowed_mentions=None, message_reference=None): - r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id) +def send_files( + self, + channel_id, + *, + files, + content=None, + tts=False, + embed=None, + components=None, + nonce=None, + allowed_mentions=None, + message_reference=None +): + r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) form = [] - payload = {'tts': tts} + payload = {"tts": tts} if content: - payload['content'] = content + payload["content"] = content if embed: - payload['embed'] = embed + payload["embed"] = embed if components: - payload['components'] = components + payload["components"] = components if nonce: - payload['nonce'] = nonce + payload["nonce"] = nonce if allowed_mentions: - payload['allowed_mentions'] = allowed_mentions + payload["allowed_mentions"] = allowed_mentions if message_reference: - payload['message_reference'] = message_reference + payload["message_reference"] = message_reference - form.append({'name': 'payload_json', 'value': utils.to_json(payload)}) + form.append({"name": "payload_json", "value": utils.to_json(payload)}) if len(files) == 1: file = files[0] - form.append({ - 'name': 'file', - 'value': file.fp, - 'filename': file.filename, - 'content_type': 'application/octet-stream' - }) + form.append( + { + "name": "file", + "value": file.fp, + "filename": file.filename, + "content_type": "application/octet-stream", + } + ) else: for index, file in enumerate(files): - form.append({ - 'name': 'file%s' % index, - 'value': file.fp, - 'filename': file.filename, - 'content_type': 'application/octet-stream' - }) + form.append( + { + "name": "file%s" % index, + "value": file.fp, + "filename": file.filename, + "content_type": "application/octet-stream", + } + ) return self.request(r, form=form, files=files) -def send_message(self, channel_id, content, *, tts=False, embed=None, components=None, - nonce=None, allowed_mentions=None, message_reference=None): - r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id) +def send_message( + self, + channel_id, + content, + *, + tts=False, + embed=None, + components=None, + nonce=None, + allowed_mentions=None, + message_reference=None +): + r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id) payload = {} if content: - payload['content'] = content + payload["content"] = content if tts: - payload['tts'] = True + payload["tts"] = True if embed: - payload['embed'] = embed + payload["embed"] = embed if components: - payload['components'] = components + payload["components"] = components if nonce: - payload['nonce'] = nonce + payload["nonce"] = nonce if allowed_mentions: - payload['allowed_mentions'] = allowed_mentions + payload["allowed_mentions"] = allowed_mentions if message_reference: - payload['message_reference'] = message_reference + payload["message_reference"] = message_reference return self.request(r, json=payload) @@ -98,10 +120,21 @@ def send_message(self, channel_id, content, *, tts=False, embed=None, components http.HTTPClient.send_message = send_message -async def send(self, content=None, *, tts=False, embed=None, file=None, components=None, - files=None, delete_after=None, nonce=None, - allowed_mentions=None, reference=None, - mention_author=None): +async def send( + self, + content=None, + *, + tts=False, + embed=None, + file=None, + components=None, + files=None, + delete_after=None, + nonce=None, + allowed_mentions=None, + reference=None, + mention_author=None +): """|coro| Sends a message to the destination with the content given. @@ -195,47 +228,70 @@ async def send(self, content=None, *, tts=False, embed=None, file=None, componen if mention_author is not None: allowed_mentions = allowed_mentions or AllowedMentions().to_dict() - allowed_mentions['replied_user'] = bool(mention_author) + allowed_mentions["replied_user"] = bool(mention_author) if reference is not None: try: reference = reference.to_message_reference_dict() except AttributeError: - raise InvalidArgument('reference parameter must be Message or MessageReference') from None + raise InvalidArgument( + "reference parameter must be Message or MessageReference" + ) from None if file is not None and files is not None: - raise InvalidArgument('cannot pass both file and files parameter to send()') + raise InvalidArgument("cannot pass both file and files parameter to send()") if file is not None: if not isinstance(file, File): - raise InvalidArgument('file parameter must be File') + raise InvalidArgument("file parameter must be File") try: - data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions, - content=content, tts=tts, embed=embed, nonce=nonce, - components=components, - message_reference=reference) + data = await state.http.send_files( + channel.id, + files=[file], + allowed_mentions=allowed_mentions, + content=content, + tts=tts, + embed=embed, + nonce=nonce, + components=components, + message_reference=reference, + ) finally: file.close() elif files is not None: if len(files) > 10: - raise InvalidArgument('files parameter must be a list of up to 10 elements') + raise InvalidArgument("files parameter must be a list of up to 10 elements") elif not all(isinstance(file, File) for file in files): - raise InvalidArgument('files parameter must be a list of File') + raise InvalidArgument("files parameter must be a list of File") try: - data = await state.http.send_files(channel.id, files=files, content=content, tts=tts, - embed=embed, nonce=nonce, allowed_mentions=allowed_mentions, - components=components, - message_reference=reference) + data = await state.http.send_files( + channel.id, + files=files, + content=content, + tts=tts, + embed=embed, + nonce=nonce, + allowed_mentions=allowed_mentions, + components=components, + message_reference=reference, + ) finally: for f in files: f.close() else: - data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, components=components, - nonce=nonce, allowed_mentions=allowed_mentions, - message_reference=reference) + data = await state.http.send_message( + channel.id, + content, + tts=tts, + embed=embed, + components=components, + nonce=nonce, + allowed_mentions=allowed_mentions, + message_reference=reference, + ) ret = state.create_message(channel=channel, data=data) if delete_after is not None: @@ -251,4 +307,5 @@ async def send_override(context_or_channel, *args, **kwargs): return await send(channel, *args, **kwargs) + abc.Messageable.send = send_override diff --git a/discord_slash/error.py b/discord_slash/error.py index 8df1b92de..897576281 100644 --- a/discord_slash/error.py +++ b/discord_slash/error.py @@ -18,6 +18,7 @@ class RequestFailure(SlashCommandError): :ivar status: Status code of failed response. :ivar msg: Message of failed response. """ + def __init__(self, status: int, msg: str): self.status = status self.msg = msg @@ -34,6 +35,7 @@ class DuplicateCommand(SlashCommandError): """ There is a duplicate command name. """ + def __init__(self, name: str): super().__init__(f"Duplicate command name detected: {name}") @@ -60,7 +62,7 @@ class IncorrectCommandData(SlashCommandError): """ Incorrect data was passed to a slash command data object """ - + class AlreadyResponded(SlashCommandError): """ diff --git a/discord_slash/http.py b/discord_slash/http.py index 6becfc078..ee5ebb056 100644 --- a/discord_slash/http.py +++ b/discord_slash/http.py @@ -1,14 +1,18 @@ import json import typing + import aiohttp import discord from discord.http import Route + from . import error +from .const import BASE_API class CustomRoute(Route): """discord.py's Route but changed ``BASE`` to use at slash command.""" - BASE = "https://discord.com/api/v8" + + BASE = BASE_API class SlashCommandRequest: @@ -29,9 +33,7 @@ def put_slash_commands(self, slash_commands: list, guild_id): :param slash_commands: List of all the slash commands to make a put request to discord with. :param guild_id: ID of the guild to set the commands on. Pass `None` for the global scope. """ - return self.command_request( - method="PUT", guild_id = guild_id, json = slash_commands - ) + return self.command_request(method="PUT", guild_id=guild_id, json=slash_commands) def remove_slash_command(self, guild_id, cmd_id): """ @@ -41,9 +43,7 @@ def remove_slash_command(self, guild_id, cmd_id): :param cmd_id: ID of the command. :return: Response code of the request. """ - return self.command_request( - method="DELETE", guild_id=guild_id, url_ending=f"/{cmd_id}" - ) + return self.command_request(method="DELETE", guild_id=guild_id, url_ending=f"/{cmd_id}") def get_all_commands(self, guild_id=None): """ @@ -70,11 +70,11 @@ def update_guild_commands_permissions(self, guild_id, perms_dict): :param guild_id: ID of the target guild to register command permissions. :return: JSON Response of the request. """ - return self.command_request(method="PUT", guild_id=guild_id, json=perms_dict, url_ending="/permissions") + return self.command_request( + method="PUT", guild_id=guild_id, json=perms_dict, url_ending="/permissions" + ) - def add_slash_command( - self, guild_id, cmd_name: str, description: str, options: list = None - ): + def add_slash_command(self, guild_id, cmd_name: str, description: str, options: list = None): """ Sends a slash command add request to Discord API. @@ -85,7 +85,7 @@ def add_slash_command( :return: JSON Response of the request. """ base = {"name": cmd_name, "description": description, "options": options or []} - return self.command_request(json=base, method="POST", guild_id = guild_id) + return self.command_request(json=base, method="POST", guild_id=guild_id) def command_request(self, method, guild_id, url_ending="", **kwargs): r""" @@ -120,7 +120,7 @@ def post_followup(self, _resp, token, files: typing.List[discord.File] = None): def post_initial_response(self, _resp, interaction_id, token): """ Sends an initial "POST" response to the Discord API. - + :param _resp: Command response. :type _resp: dict :param interaction_id: Interaction ID. @@ -129,8 +129,10 @@ def post_initial_response(self, _resp, interaction_id, token): """ return self.command_response(token, False, "POST", interaction_id, json=_resp) - def command_response(self, token, use_webhook, method, interaction_id= None, url_ending = "", **kwargs): - """ + def command_response( + self, token, use_webhook, method, interaction_id=None, url_ending="", **kwargs + ): + r""" Sends a command response to discord (POST, PATCH, DELETE) :param token: Interaction token @@ -142,21 +144,33 @@ def command_response(self, token, use_webhook, method, interaction_id= None, url :return: Coroutine """ if not use_webhook and not interaction_id: - raise error.IncorrectFormat("Internal Error! interaction_id must be set if use_webhook is False") - req_url = f"/webhooks/{self.application_id}/{token}" if use_webhook else f"/interactions/{interaction_id}/{token}/callback" + raise error.IncorrectFormat( + "Internal Error! interaction_id must be set if use_webhook is False" + ) + req_url = ( + f"/webhooks/{self.application_id}/{token}" + if use_webhook + else f"/interactions/{interaction_id}/{token}/callback" + ) req_url += url_ending route = CustomRoute(method, req_url) return self._discord.http.request(route, **kwargs) - def request_with_files(self, _resp, files: typing.List[discord.File], token, method, url_ending = ""): + def request_with_files( + self, _resp, files: typing.List[discord.File], token, method, url_ending="" + ): form = aiohttp.FormData() form.add_field("payload_json", json.dumps(_resp)) for x in range(len(files)): name = f"file{x if len(files) > 1 else ''}" sel = files[x] - form.add_field(name, sel.fp, filename=sel.filename, content_type="application/octet-stream") - return self.command_response(token, True, method, data=form, files=files, url_ending=url_ending) + form.add_field( + name, sel.fp, filename=sel.filename, content_type="application/octet-stream" + ) + return self.command_response( + token, True, method, data=form, files=files, url_ending=url_ending + ) def edit(self, _resp, token, message_id="@original", files: typing.List[discord.File] = None): """ @@ -172,8 +186,8 @@ def edit(self, _resp, token, message_id="@original", files: typing.List[discord. """ req_url = f"/messages/{message_id}" if files: - return self.request_with_files(_resp, files, token, "PATCH", url_ending = req_url) - return self.command_response(token, True, "PATCH", url_ending = req_url, json=_resp) + return self.request_with_files(_resp, files, token, "PATCH", url_ending=req_url) + return self.command_response(token, True, "PATCH", url_ending=req_url, json=_resp) def delete(self, token, message_id="@original"): """ @@ -184,4 +198,4 @@ def delete(self, token, message_id="@original"): :return: Coroutine """ req_url = f"/messages/{message_id}" - return self.command_response(token, True, "DELETE", url_ending = req_url) + return self.command_response(token, True, "DELETE", url_ending=req_url) diff --git a/discord_slash/model.py b/discord_slash/model.py index df9f7021b..c8b4f2a34 100644 --- a/discord_slash/model.py +++ b/discord_slash/model.py @@ -1,16 +1,14 @@ import asyncio import datetime - -import discord -from enum import IntEnum from contextlib import suppress +from enum import IntEnum from inspect import iscoroutinefunction -from discord.ext.commands import CooldownMapping, CommandOnCooldown +import discord +from discord.ext.commands import CommandOnCooldown, CooldownMapping -from . import http -from . import error -from . dpy_overrides import ComponentMessage +from . import error, http +from .dpy_overrides import ComponentMessage class ChoiceData: @@ -40,9 +38,7 @@ class OptionData: :ivar options: List of :class:`OptionData`, this will be present if it's a subcommand group """ - def __init__( - self, name, description, required=False, choices=None, options=None, **kwargs - ): + def __init__(self, name, description, required=False, choices=None, options=None, **kwargs): self.name = name self.description = description self.type = kwargs.get("type") @@ -83,7 +79,15 @@ class CommandData: """ def __init__( - self, name, description, options=None, default_permission=True, id=None, application_id=None, version=None, **kwargs + self, + name, + description, + options=None, + default_permission=True, + id=None, + application_id=None, + version=None, + **kwargs ): self.name = name self.description = description @@ -101,10 +105,10 @@ def __init__( def __eq__(self, other): if isinstance(other, CommandData): return ( - self.name == other.name - and self.description == other.description - and self.options == other.options - and self.default_permission == other.default_permission + self.name == other.name + and self.description == other.description + and self.options == other.options + and self.default_permission == other.default_permission ) else: return False @@ -137,7 +141,7 @@ def __init__(self, name, cmd): # Let's reuse old command formatting. # Since this isn't inherited from `discord.ext.commands.Command`, discord.py's check decorator will # add checks at this var. self.__commands_checks__ = [] - if hasattr(self.func, '__commands_checks__'): + if hasattr(self.func, "__commands_checks__"): self.__commands_checks__ = self.func.__commands_checks__ cooldown = None @@ -177,7 +181,7 @@ async def _concurrency_checks(self, ctx): try: # cooldown checks self._prepare_cooldowns(ctx) - except: + except Exception: if self._max_concurrency is not None: await self._max_concurrency.release(ctx) raise @@ -282,7 +286,10 @@ async def can_run(self, ctx) -> bool: :type ctx: .context.SlashContext :return: bool """ - res = [bool(x(ctx)) if not iscoroutinefunction(x) else bool(await x(ctx)) for x in self.__commands_checks__] + res = [ + bool(x(ctx)) if not iscoroutinefunction(x) else bool(await x(ctx)) + for x in self.__commands_checks__ + ] return False not in res @@ -307,6 +314,7 @@ def __init__(self, name, cmd): # Let's reuse old command formatting. self.default_permission = cmd["default_permission"] self.permissions = cmd["api_permissions"] or {} + class SubcommandObject(CommandObject): """ Subcommand object of this extension. @@ -362,6 +370,7 @@ class SlashCommandOptionType(IntEnum): """ Equivalent of `ApplicationCommandOptionType