From 50cd870a4e758169708abdcd7649fe403170eeca Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 04:21:13 -0400 Subject: [PATCH 1/6] feat!: use the guild data provided on interactions This could in theory use an optimisation of caching the guild, but as we do not have a good way to clear the cache afterwards, this is the best we can implement at this point in time. --- disnake/interactions/base.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/disnake/interactions/base.py b/disnake/interactions/base.py index 585406ed41..535cec8843 100644 --- a/disnake/interactions/base.py +++ b/disnake/interactions/base.py @@ -183,6 +183,7 @@ class Interaction(Generic[ClientT]): "_state", "_session", "_original_response", + "_guild", "_cs_response", "_cs_followup", "_cs_me", @@ -203,6 +204,7 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState) -> None: self.version: int = data["version"] self.application_id: int = int(data["application_id"]) self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") + self._guild = data.get("guild") self.locale: Locale = try_enum(Locale, data["locale"]) guild_locale = data.get("guild_locale") @@ -216,18 +218,12 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState) -> None: self.author: Union[User, Member] = MISSING guild_fallback: Optional[Union[Guild, Object]] = None - if self.guild_id: - guild_fallback = self.guild or Object(self.guild_id) - - if guild_fallback and (member := data.get("member")): - self.author = ( - isinstance(guild_fallback, Guild) - and guild_fallback.get_member(int(member["user"]["id"])) - or Member( - state=self._state, - guild=guild_fallback, # type: ignore # may be `Object` - data=member, - ) + + if self.guild_id and (guild_fallback := self.guild) and (member := data.get("member")): + self.author = guild_fallback.get_member(int(member["user"]["id"])) or Member( + state=self._state, + guild=guild_fallback, + data=member, ) self._permissions = int(member.get("permissions", 0)) elif user := data.get("user"): @@ -263,8 +259,20 @@ def user(self) -> Union[User, Member]: @property def guild(self) -> Optional[Guild]: - """Optional[:class:`Guild`]: The guild the interaction was sent from.""" - return self._state._get_guild(self.guild_id) + """Optional[:class:`Guild`]: The guild the interaction was sent from. + + .. versionchanged:: 2.10 + Returns a :class:`Guild` object when the guild could not be resolved from cache. + This object is created from the data provided by Discord, but it is not complete. + The only populated attributes are: + - :attr:`Guild.id` + - :attr:`Guild.locale` + - :attr:`Guild.features` + """ + if self.guild_id is None: + return None + + return self._state._get_guild(self.guild_id) or Guild(data=self._guild, state=self._state) @utils.cached_slot_property("_cs_me") def me(self) -> Union[Member, ClientUser]: From ceac6ebcb3d1fbec4cf1bd17ae91fa7f2c12a696 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 04:26:20 -0400 Subject: [PATCH 2/6] add changelog entry --- changelog/1235.breaking.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/1235.breaking.rst diff --git a/changelog/1235.breaking.rst b/changelog/1235.breaking.rst new file mode 100644 index 0000000000..602504b8e6 --- /dev/null +++ b/changelog/1235.breaking.rst @@ -0,0 +1 @@ +:attr:`Interaction.guild` is now a :class:`Guild` instance if the guild could not be found in cache. From 5e7b9bc390692d1b3f44f56c1e3f69b040a19f2b Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 16:37:44 -0400 Subject: [PATCH 3/6] fix: provide a fake default_role --- disnake/interactions/base.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/disnake/interactions/base.py b/disnake/interactions/base.py index 535cec8843..ccca3d3fa1 100644 --- a/disnake/interactions/base.py +++ b/disnake/interactions/base.py @@ -204,7 +204,7 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState) -> None: self.version: int = data["version"] self.application_id: int = int(data["application_id"]) self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") - self._guild = data.get("guild") + self._guild: Optional[Mapping[str, Any]] = data.get("guild") self.locale: Locale = try_enum(Locale, data["locale"]) guild_locale = data.get("guild_locale") @@ -272,7 +272,16 @@ def guild(self) -> Optional[Guild]: if self.guild_id is None: return None - return self._state._get_guild(self.guild_id) or Guild(data=self._guild, state=self._state) + guild = self._state._get_guild(self.guild_id) + if guild: + return guild + + # create a guild mash + # honestly we should cache this for the duration of the interaction + # but not if we fetch it from the cache, just the result of this creation + guild = Guild(data=self._guild, state=self._state) + guild._add_role(Role(state=self._state, guild=guild, data={"id": 1, "name": "@everyone"})) + return guild @utils.cached_slot_property("_cs_me") def me(self) -> Union[Member, ClientUser]: From e4e27c0581b32248487e85046e82b6633d3a55c3 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 16:39:24 -0400 Subject: [PATCH 4/6] chore: guild.preferred_locale --- disnake/interactions/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/interactions/base.py b/disnake/interactions/base.py index ccca3d3fa1..918e98cddd 100644 --- a/disnake/interactions/base.py +++ b/disnake/interactions/base.py @@ -266,7 +266,7 @@ def guild(self) -> Optional[Guild]: This object is created from the data provided by Discord, but it is not complete. The only populated attributes are: - :attr:`Guild.id` - - :attr:`Guild.locale` + - :attr:`Guild.preferred_locale` - :attr:`Guild.features` """ if self.guild_id is None: From fc05d78d91c1b615d9638dcf530e115681c8e073 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 16:55:55 -0400 Subject: [PATCH 5/6] fix typing issues --- disnake/interactions/base.py | 13 +++++++++++-- disnake/types/guild.py | 4 ++++ disnake/types/interactions.py | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/disnake/interactions/base.py b/disnake/interactions/base.py index 918e98cddd..4c1291243e 100644 --- a/disnake/interactions/base.py +++ b/disnake/interactions/base.py @@ -75,6 +75,7 @@ from ..poll import Poll from ..state import ConnectionState from ..types.components import Modal as ModalPayload + from ..types.guild import PartialGuild as PartialGuildPayload from ..types.interactions import ( ApplicationCommandOptionChoice as ApplicationCommandOptionChoicePayload, Interaction as InteractionPayload, @@ -204,7 +205,7 @@ def __init__(self, *, data: InteractionPayload, state: ConnectionState) -> None: self.version: int = data["version"] self.application_id: int = int(data["application_id"]) self.guild_id: Optional[int] = utils._get_as_snowflake(data, "guild_id") - self._guild: Optional[Mapping[str, Any]] = data.get("guild") + self._guild: Optional[PartialGuildPayload] = data.get("guild") self.locale: Locale = try_enum(Locale, data["locale"]) guild_locale = data.get("guild_locale") @@ -275,12 +276,20 @@ def guild(self) -> Optional[Guild]: guild = self._state._get_guild(self.guild_id) if guild: return guild + if self._guild is None: + return None # create a guild mash # honestly we should cache this for the duration of the interaction # but not if we fetch it from the cache, just the result of this creation guild = Guild(data=self._guild, state=self._state) - guild._add_role(Role(state=self._state, guild=guild, data={"id": 1, "name": "@everyone"})) + guild._add_role( + Role( + state=self._state, + guild=guild, + data={"id": 1, "name": "@everyone"}, # type: ignore + ) + ) return guild @utils.cached_slot_property("_cs_me") diff --git a/disnake/types/guild.py b/disnake/types/guild.py index 76d8d2f6b5..ee0b6f9296 100644 --- a/disnake/types/guild.py +++ b/disnake/types/guild.py @@ -149,6 +149,10 @@ class Guild(_BaseGuildPreview): guild_scheduled_events: NotRequired[List[GuildScheduledEvent]] +class PartialGuild(Guild, total=False): + pass + + class InviteGuild(Guild, total=False): welcome_screen: WelcomeScreen diff --git a/disnake/types/interactions.py b/disnake/types/interactions.py index 9cb8393ea5..083518d569 100644 --- a/disnake/types/interactions.py +++ b/disnake/types/interactions.py @@ -10,6 +10,7 @@ from .components import Component, Modal from .embed import Embed from .entitlement import Entitlement +from .guild import PartialGuild from .i18n import LocalizationDict from .member import Member, MemberWithUser from .role import Role @@ -265,6 +266,7 @@ class _BaseUserInteraction(_BaseInteraction): locale: str app_permissions: NotRequired[str] guild_id: NotRequired[Snowflake] + guild: NotRequired[PartialGuild] guild_locale: NotRequired[str] entitlements: NotRequired[List[Entitlement]] # one of these two will always exist, according to docs From da11276cbec15742de9400ae30c13d67326553cd Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Mon, 23 Sep 2024 17:54:58 -0400 Subject: [PATCH 6/6] try a different approach --- disnake/guild.py | 29 +++++++++++++++++++++++++++++ disnake/interactions/base.py | 11 ++--------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/disnake/guild.py b/disnake/guild.py index 97ea1e80ac..aef3ce35f3 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -95,6 +95,7 @@ from .app_commands import APIApplicationCommand from .asset import AssetBytes from .automod import AutoModTriggerMetadata + from .interactions import Interaction from .permissions import Permissions from .state import ConnectionState from .template import Template @@ -107,6 +108,7 @@ Guild as GuildPayload, GuildFeature, MFALevel, + PartialGuild as PartialGuildPayload, ) from .types.integration import IntegrationType from .types.role import CreateRole as CreateRolePayload @@ -4963,6 +4965,33 @@ async def onboarding(self) -> Onboarding: return Onboarding(data=data, guild=self) +class PartialInteractionGuild(Guild): + """Reimplementation of :class:`Guild` for guilds interactions.""" + + def __init__( + self, + *, + state: ConnectionState, + data: PartialGuildPayload, + interaction: Interaction, + ) -> None: + super().__init__(state=state, data=data) + # init the fake data + self._add_role( + Role( + state=state, + guild=self, + data={"id": self.id, "name": "@everyone"}, # type: ignore + ) + ) + self._add_channel(interaction.channel) # type: ignore + # honestly we cannot set me, because we do not necessarily have a user in the guild + + @property + def me(self) -> Any: + return self._state.user + + PlaceholderID = NewType("PlaceholderID", int) diff --git a/disnake/interactions/base.py b/disnake/interactions/base.py index 4c1291243e..06cb1b2605 100644 --- a/disnake/interactions/base.py +++ b/disnake/interactions/base.py @@ -42,7 +42,7 @@ NotFound, ) from ..flags import MessageFlags -from ..guild import Guild +from ..guild import Guild, PartialInteractionGuild from ..i18n import Localized from ..member import Member from ..message import Attachment, Message @@ -282,14 +282,7 @@ def guild(self) -> Optional[Guild]: # create a guild mash # honestly we should cache this for the duration of the interaction # but not if we fetch it from the cache, just the result of this creation - guild = Guild(data=self._guild, state=self._state) - guild._add_role( - Role( - state=self._state, - guild=guild, - data={"id": 1, "name": "@everyone"}, # type: ignore - ) - ) + guild = PartialInteractionGuild(data=self._guild, state=self._state, interaction=self) return guild @utils.cached_slot_property("_cs_me")