diff --git a/CHANGELOG.md b/CHANGELOG.md index e6ef2d73bc..27823c9943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,10 @@ These changes are available on the `master` branch, but have not yet been releas - Added `view.parent` which is set when the view was sent by `interaction.response.send_message`. ([#2036](https://github.com/Pycord-Development/pycord/pull/2036)) +- Added functions (`bridge.Bot.walk_bridge_commands` & + `BridgeCommandGroup.walk_commands`) to cycle through all bridge commands and their + children/subcommands. + ([#1867](https://github.com/Pycord-Development/pycord/pull/1867)) ### Changed @@ -62,6 +66,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2025](https://github.com/Pycord-Development/pycord/pull/2025)) - Store `view.message` on receiving Interaction for a component. ([#2036](https://github.com/Pycord-Development/pycord/pull/2036)) +- Attributes shared between ext and slash commands are now dynamically fetched on bridge + commands. ([#1867](https://github.com/Pycord-Development/pycord/pull/1867)) ### Removed diff --git a/discord/ext/bridge/bot.py b/discord/ext/bridge/bot.py index 1d9a36f37d..e44927e735 100644 --- a/discord/ext/bridge/bot.py +++ b/discord/ext/bridge/bot.py @@ -25,6 +25,7 @@ from __future__ import annotations from abc import ABC +from collections.abc import Iterator from discord.commands import ApplicationContext from discord.errors import CheckFailure, DiscordException @@ -60,6 +61,21 @@ def bridge_commands(self) -> list[BridgeCommand | BridgeCommandGroup]: return cmds + def walk_bridge_commands( + self, + ) -> Iterator[BridgeCommand | BridgeCommandGroup]: + """An iterator that recursively walks through all the bot's bridge commands. + + Yields + ------ + Union[:class:`.BridgeCommand`, :class:`.BridgeCommandGroup`] + A bridge command or bridge group of the bot. + """ + for cmd in self._bridge_commands: + yield cmd + if isinstance(cmd, BridgeCommandGroup): + yield from cmd.walk_commands() + async def get_application_context( self, interaction: Interaction, cls=None ) -> BridgeApplicationContext: diff --git a/discord/ext/bridge/core.py b/discord/ext/bridge/core.py index 4f53389d05..427bd0269f 100644 --- a/discord/ext/bridge/core.py +++ b/discord/ext/bridge/core.py @@ -25,6 +25,7 @@ from __future__ import annotations import inspect +from collections.abc import Iterator from typing import TYPE_CHECKING, Any, Callable import discord.commands.options @@ -38,7 +39,7 @@ SlashCommandOptionType, ) -from ...utils import filter_params, find, get +from ...utils import MISSING, find, get from ..commands import BadArgument from ..commands import Bot as ExtBot from ..commands import ( @@ -156,6 +157,8 @@ class BridgeCommand: The prefix-based version of this bridge command. """ + __special_attrs__ = ["slash_variant", "ext_variant", "parent"] + def __init__(self, callback, **kwargs): self.parent = kwargs.pop("parent", None) self.slash_variant: BridgeSlashCommand = kwargs.pop( @@ -166,13 +169,10 @@ def __init__(self, callback, **kwargs): ) or BridgeExtCommand(callback, **kwargs) @property - def name_localizations(self) -> dict[str, str]: + def name_localizations(self) -> dict[str, str] | None: """Returns name_localizations from :attr:`slash_variant` - You can edit/set name_localizations directly with - .. code-block:: python3 - bridge_command.name_localizations["en-UK"] = ... # or any other locale # or bridge_command.name_localizations = {"en-UK": ..., "fr-FR": ...} @@ -184,13 +184,10 @@ def name_localizations(self, value): self.slash_variant.name_localizations = value @property - def description_localizations(self) -> dict[str, str]: + def description_localizations(self) -> dict[str, str] | None: """Returns description_localizations from :attr:`slash_variant` - You can edit/set description_localizations directly with - .. code-block:: python3 - bridge_command.description_localizations["en-UK"] = ... # or any other locale # or bridge_command.description_localizations = {"en-UK": ..., "fr-FR": ...} @@ -201,9 +198,34 @@ def description_localizations(self) -> dict[str, str]: def description_localizations(self, value): self.slash_variant.description_localizations = value - @property - def qualified_name(self) -> str: - return self.slash_variant.qualified_name + def __getattribute__(self, name): + try: + # first, look for the attribute on the bridge command + return super().__getattribute__(name) + except AttributeError as e: + # if it doesn't exist, check this list, if the name of + # the parameter is here + if name is self.__special_attrs__: + raise e + + # looks up the result in the variants. + # slash cmd prioritized + result = getattr(self.slash_variant, name, MISSING) + try: + if result is MISSING: + return getattr(self.ext_variant, name) + return result + except AttributeError: + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{name}'" + ) + + def __setattr__(self, name, value) -> None: + if name not in self.__special_attrs__: + setattr(self.slash_variant, name, value) + setattr(self.ext_variant, name, value) + + return super().__setattr__(name, value) def add_to(self, bot: ExtBot) -> None: """Adds the command to a bot. This method is inherited by :class:`.BridgeCommandGroup`. @@ -321,6 +343,14 @@ class BridgeCommandGroup(BridgeCommand): If :func:`map_to` is used, the mapped slash command. """ + __special_attrs__ = [ + "slash_variant", + "ext_variant", + "parent", + "subcommands", + "mapped", + ] + ext_variant: BridgeExtGroup slash_variant: BridgeSlashGroup @@ -341,6 +371,16 @@ def __init__(self, callback, *args, **kwargs): kwargs.update(map_to) self.mapped = self.slash_variant.command(**kwargs)(callback) + def walk_commands(self) -> Iterator[BridgeCommand]: + """An iterator that recursively walks through all the bridge group's subcommands. + + Yields + ------ + :class:`.BridgeCommand` + A bridge command of this bridge group. + """ + yield from self.subcommands + def command(self, *args, **kwargs): """A decorator to register a function as a subcommand.