diff --git a/discord/client.py b/discord/client.py index ff02bf7b6f00..76c4ff4f59d1 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,29 @@ 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. + + .. versionadded:: 2.5 + + 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..991268b68d37 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -99,6 +99,7 @@ __all__ = ( 'Guild', + 'GuildPreview', 'BanEntry', ) @@ -109,6 +110,7 @@ from .types.guild import ( Ban as BanPayload, Guild as GuildPayload, + GuildPreview as GuildPreviewPayload, RolePositionUpdate as RolePositionUpdatePayload, GuildFeature, IncidentData, @@ -160,6 +162,120 @@ class _GuildLimit(NamedTuple): filesize: int +class GuildPreview(Hashable): + """Represents a preview of a Discord guild. + + .. versionadded:: 2.5 + .. 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_presence_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: 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: GuildSticker(state=state, data=d), data.get('stickers', [])) + ) + 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} description={self.description!r} ' + f'features={self.features}>' + ) + + @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 7c3f198c98c1..5cb6cb58cb01 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1453,6 +1453,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..79cf3dab9e1a 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 diff --git a/docs/api.rst b/docs/api.rst index ca0fb4ef4645..0b4015f78175 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 ~~~~~~~~~~~~~~