From e641582be00453e59805aeb276f83bf600df5469 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:46:20 +0000 Subject: [PATCH 1/7] Add a method to fetch a preview of a discoverable guild --- discord/client.py | 23 +++++++++- discord/guild.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++ discord/http.py | 3 ++ discord/state.py | 6 +-- 4 files changed, 142 insertions(+), 4 deletions(-) diff --git a/discord/client.py b/discord/client.py index ff02bf7b6f00..f1b18694a864 100644 --- a/discord/client.py +++ b/discord/client.py @@ -53,7 +53,7 @@ from .invite import Invite from .template import Template from .widget import Widget -from .guild import Guild +from .guild import Guild, GuildPreview from .emoji import Emoji from .channel import _threaded_channel_factory, PartialMessageable from .enums import ChannelType, EntitlementOwnerType @@ -2356,6 +2356,27 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts: bool = True) -> Gu data = await self.http.get_guild(guild_id, with_counts=with_counts) return Guild(data=data, state=self._connection) + async def fetch_guild_preview(self, guild_id: int) -> GuildPreview: + """|coro| + + Retrieves a preview of a :class:`Guild` from an ID. If the guild is discoverable, you don't have to be + a member of it. + + Raises + ------ + NotFound + The guild doesn't exist, or is not discoverable and you are not in it. + HTTPException + Getting the guild failed. + + Returns + -------- + :class:`.GuildPreview` + The guild preview from the ID. + """ + data = await self.http.get_guild_preview(guild_id) + return GuildPreview(data=data, state=self._connection) + async def create_guild( self, *, diff --git a/discord/guild.py b/discord/guild.py index fc39179abeb2..0935a7f863d7 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -109,6 +109,7 @@ from .types.guild import ( Ban as BanPayload, Guild as GuildPayload, + GuildPreview as GuildPreviewPayload, RolePositionUpdate as RolePositionUpdatePayload, GuildFeature, IncidentData, @@ -159,6 +160,119 @@ class _GuildLimit(NamedTuple): bitrate: float filesize: int +class GuildPreview(Hashable): + """Represents a preview of a Discord guild. + + .. container:: operations + + .. describe:: x == y + + Checks if two guild previews are equal. + + .. describe:: x != y + + Checks if two guild previews are not equal. + + .. describe:: hash(x) + + Returns the guild's hash. + + .. describe:: str(x) + + Returns the guild's name. + + Attributes + ---------- + name: :class:`str` + The guild preview's name. + id: :class:`int` + The guild preview's ID. + features: List[:class:`str`] + A list of features the guild has. See :attr:`Guild.features` for more information. + description: Optional[:class:`str`] + The guild preview's description. + emojis: Tuple[:class:`Emoji`, ...] + All emojis that the guild owns. + stickers: Tuple[:class:`GuildSticker`, ...] + All stickers that the guild owns. + approximate_member_count: :class:`int` + The approximate number of members in the guild. + approximate_member_count: :class:`int` + The approximate number of members currently active in in the guild. Offline members are excluded. + """ + + __slots__ = ( + '_state', + '_icon', + '_splash', + '_discovery_splash', + 'id', + 'name', + 'emojis', + 'stickers', + 'features', + 'description', + "approximate_member_count", + "approximate_presence_count", + ) + + def __init__(self, *, data: GuildPreviewPayload, state: ConnectionState) -> None: + self._state: ConnectionState = state + self.id = int(data['id']) + self.name: str = data['name'] + self._icon: Optional[str] = data.get('icon') + self._splash: Optional[str] = data.get('splash') + self._discovery_splash: Optional[str] = data.get('discovery_splash') + self.emojis: Tuple[Emoji, ...] = ( + tuple(map(lambda d: state.store_emoji(self, d), data.get('emojis', []))) + if state.cache_guild_expressions + else () + ) + self.stickers: Tuple[GuildSticker, ...] = ( + tuple(map(lambda d: state.store_sticker(self, d), data.get('stickers', []))) + if state.cache_guild_expressions + else () + ) + self.features: List[GuildFeature] = data.get('features', []) + self.description: Optional[str] = data.get('description') + self.approximate_member_count: int = data.get('approximate_member_count') + self.approximate_presence_count: int = data.get('approximate_presence_count') + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return ( + f'<{self.__class__.__name__} id={self.id} name={self.name!r} features={self.features} ' + f'description={self.description!r}>' + ) + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the guild's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's icon asset, if available.""" + if self._icon is None: + return None + return Asset._from_guild_icon(self._state, self.id, self._icon) + + @property + def splash(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's invite splash asset, if available.""" + if self._splash is None: + return None + return Asset._from_guild_image(self._state, self.id, self._splash, path='splashes') + + @property + def discovery_splash(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the guild's discovery splash asset, if available.""" + if self._discovery_splash is None: + return None + return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path='discovery-splashes') + class Guild(Hashable): """Represents a Discord guild. diff --git a/discord/http.py b/discord/http.py index fbaf447aa415..8558b34f266e 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1451,6 +1451,9 @@ def get_guild(self, guild_id: Snowflake, *, with_counts: bool = True) -> Respons params = {'with_counts': int(with_counts)} return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params) + def get_guild_preview(self, guild_id: Snowflake) -> Response[guild.GuildPreview]: + return self.request(Route('GET', '/guilds/{guild_id}/preview', guild_id=guild_id)) + def delete_guild(self, guild_id: Snowflake) -> Response[None]: return self.request(Route('DELETE', '/guilds/{guild_id}', guild_id=guild_id)) diff --git a/discord/state.py b/discord/state.py index df6073985d7c..4ed8d892500e 100644 --- a/discord/state.py +++ b/discord/state.py @@ -51,7 +51,7 @@ import os -from .guild import Guild +from .guild import Guild, GuildPreview from .activity import BaseActivity from .sku import Entitlement from .user import User, ClientUser @@ -396,13 +396,13 @@ def create_user(self, data: Union[UserPayload, PartialUserPayload]) -> User: def get_user(self, id: int) -> Optional[User]: return self._users.get(id) - def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji: + def store_emoji(self, guild: Guild | GuildPreview, data: EmojiPayload) -> Emoji: # the id will be present here emoji_id = int(data['id']) # type: ignore self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data) return emoji - def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: + def store_sticker(self, guild: Guild | GuildPreview, data: GuildStickerPayload) -> GuildSticker: sticker_id = int(data['id']) self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) return sticker From 20d156ce14f525a6f1cbdca166d04772eaaae546 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:54:23 +0000 Subject: [PATCH 2/7] Add GuildPreview to API docs --- docs/api.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 338368910145..df3e1caa8ced 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5682,6 +5682,14 @@ CallMessage .. autoclass:: CallMessage() :members: +GuildPreview +~~~~~~~~~~~~ + +.. attributetable:: GuildPreview + +.. autoclass:: GuildPreview + :members: + Exceptions ------------ From a1c6a3f0bf50562c392f6a0c3e024db4a5818b32 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:02:12 +0000 Subject: [PATCH 3/7] Add GuildPreview to __all__ --- discord/guild.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/guild.py b/discord/guild.py index 0935a7f863d7..ed5416bd9562 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -99,6 +99,7 @@ __all__ = ( 'Guild', + 'GuildPreview', 'BanEntry', ) From 82e926cc0faf231fc3afe42856c97eec33fd2eca Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:00:25 +0000 Subject: [PATCH 4/7] Apply requested changes --- discord/guild.py | 24 ++++++++++++------------ discord/state.py | 4 ++-- docs/api.rst | 15 +++++++-------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index ed5416bd9562..e014adcfff9e 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -161,9 +161,10 @@ class _GuildLimit(NamedTuple): bitrate: float filesize: int + class GuildPreview(Hashable): """Represents a preview of a Discord guild. - + .. container:: operations .. describe:: x == y @@ -181,7 +182,7 @@ class GuildPreview(Hashable): .. describe:: str(x) Returns the guild's name. - + Attributes ---------- name: :class:`str` @@ -198,7 +199,7 @@ class GuildPreview(Hashable): All stickers that the guild owns. approximate_member_count: :class:`int` The approximate number of members in the guild. - approximate_member_count: :class:`int` + approximate_presence_count: :class:`int` The approximate number of members currently active in in the guild. Offline members are excluded. """ @@ -224,15 +225,14 @@ def __init__(self, *, data: GuildPreviewPayload, state: ConnectionState) -> None self._icon: Optional[str] = data.get('icon') self._splash: Optional[str] = data.get('splash') self._discovery_splash: Optional[str] = data.get('discovery_splash') - self.emojis: Tuple[Emoji, ...] = ( - tuple(map(lambda d: state.store_emoji(self, d), data.get('emojis', []))) - if state.cache_guild_expressions - else () + self.emojis: Tuple[Emoji, ...] = tuple( + map( + lambda d: Emoji(guild=state._get_or_create_unavailable_guild(self.id), state=state, data=d), + data.get('emojis', []), + ) ) - self.stickers: Tuple[GuildSticker, ...] = ( - tuple(map(lambda d: state.store_sticker(self, d), data.get('stickers', []))) - if state.cache_guild_expressions - else () + self.stickers: Tuple[GuildSticker, ...] = tuple( + map(lambda d: GuildSticker(state=state, data=d), data.get('stickers', [])) ) self.features: List[GuildFeature] = data.get('features', []) self.description: Optional[str] = data.get('description') @@ -273,7 +273,7 @@ def discovery_splash(self) -> Optional[Asset]: if self._discovery_splash is None: return None return Asset._from_guild_image(self._state, self.id, self._discovery_splash, path='discovery-splashes') - + class Guild(Hashable): """Represents a Discord guild. diff --git a/discord/state.py b/discord/state.py index 4ed8d892500e..79cf3dab9e1a 100644 --- a/discord/state.py +++ b/discord/state.py @@ -396,13 +396,13 @@ def create_user(self, data: Union[UserPayload, PartialUserPayload]) -> User: def get_user(self, id: int) -> Optional[User]: return self._users.get(id) - def store_emoji(self, guild: Guild | GuildPreview, data: EmojiPayload) -> Emoji: + def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji: # the id will be present here emoji_id = int(data['id']) # type: ignore self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data) return emoji - def store_sticker(self, guild: Guild | GuildPreview, data: GuildStickerPayload) -> GuildSticker: + def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: sticker_id = int(data['id']) self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) return sticker diff --git a/docs/api.rst b/docs/api.rst index df3e1caa8ced..f8fb33cb3a3a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4815,6 +4815,13 @@ Guild :type: List[:class:`Object`] +GuildPreview +~~~~~~~~~~~~ + +.. attributetable:: GuildPreview + +.. autoclass:: GuildPreview + :members: ScheduledEvent ~~~~~~~~~~~~~~ @@ -5682,14 +5689,6 @@ CallMessage .. autoclass:: CallMessage() :members: -GuildPreview -~~~~~~~~~~~~ - -.. attributetable:: GuildPreview - -.. autoclass:: GuildPreview - :members: - Exceptions ------------ From 8b4f1208bab377608804e5705da3feab7d65f3b8 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Sat, 26 Oct 2024 16:08:55 +0000 Subject: [PATCH 5/7] Reorder GuildPreview __repr__ --- discord/guild.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index e014adcfff9e..e94aaa808ae1 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -244,8 +244,8 @@ def __str__(self) -> str: def __repr__(self) -> str: return ( - f'<{self.__class__.__name__} id={self.id} name={self.name!r} features={self.features} ' - f'description={self.description!r}>' + f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r} ' + f'features={self.features}>' ) @property From acb4f4747a56162da5816fda3ffda35d50c2a7e7 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:28:16 +0000 Subject: [PATCH 6/7] Add versionadded strings --- discord/client.py | 6 ++++-- discord/guild.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/discord/client.py b/discord/client.py index f1b18694a864..76c4ff4f59d1 100644 --- a/discord/client.py +++ b/discord/client.py @@ -2359,8 +2359,10 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts: bool = True) -> Gu async def fetch_guild_preview(self, guild_id: int) -> GuildPreview: """|coro| - Retrieves a preview of a :class:`Guild` from an ID. If the guild is discoverable, you don't have to be - a member of it. + Retrieves a preview of a :class:`Guild` from an ID. If the guild is discoverable, + you don't have to be a member of it. + + .. versionadded:: 2.5 Raises ------ diff --git a/discord/guild.py b/discord/guild.py index e94aaa808ae1..991268b68d37 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -165,6 +165,7 @@ class _GuildLimit(NamedTuple): class GuildPreview(Hashable): """Represents a preview of a Discord guild. + .. versionadded:: 2.5 .. container:: operations .. describe:: x == y From c7e97c461b807a89bde28cbf42853e93859d7396 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingershaped@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:30:06 +0000 Subject: [PATCH 7/7] Fix minor formatting problems --- discord/client.py | 2 +- discord/guild.py | 1 + discord/state.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/client.py b/discord/client.py index 76c4ff4f59d1..8ecff6ec23d4 100644 --- a/discord/client.py +++ b/discord/client.py @@ -2359,7 +2359,7 @@ async def fetch_guild(self, guild_id: int, /, *, with_counts: bool = True) -> Gu async def fetch_guild_preview(self, guild_id: int) -> GuildPreview: """|coro| - Retrieves a preview of a :class:`Guild` from an ID. If the guild is discoverable, + Retrieves a preview of a :class:`.Guild` from an ID. If the guild is discoverable, you don't have to be a member of it. .. versionadded:: 2.5 diff --git a/discord/guild.py b/discord/guild.py index 991268b68d37..faf64e27923c 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -166,6 +166,7 @@ class GuildPreview(Hashable): """Represents a preview of a Discord guild. .. versionadded:: 2.5 + .. container:: operations .. describe:: x == y diff --git a/discord/state.py b/discord/state.py index 79cf3dab9e1a..df6073985d7c 100644 --- a/discord/state.py +++ b/discord/state.py @@ -51,7 +51,7 @@ import os -from .guild import Guild, GuildPreview +from .guild import Guild from .activity import BaseActivity from .sku import Entitlement from .user import User, ClientUser