diff --git a/discord/client.py b/discord/client.py index 363a89bba..ec0daa045 100644 --- a/discord/client.py +++ b/discord/client.py @@ -51,7 +51,7 @@ from .user import _UserTag, User, ClientUser, Note from .invite import Invite from .template import Template from .widget import Widget -from .guild import Guild +from .guild import Guild, UserGuild from .emoji import Emoji from .channel import _private_channel_factory, _threaded_channel_factory, GroupChannel, PartialMessageable from .enums import ActivityType, ChannelType, ClientType, ConnectionType, EntitlementType, Status @@ -1597,19 +1597,18 @@ class Client: # Guild stuff - async def fetch_guilds(self, *, with_counts: bool = True) -> List[Guild]: + async def fetch_guilds(self, *, with_counts: bool = True) -> List[UserGuild]: """|coro| - Retrieves all your your guilds. + Retrieves all your guilds. .. note:: - Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, - :attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`. + This method is an API call. For general usage, consider :attr:`guilds` instead. - .. note:: + .. versionchanged:: 2.0 - This method is an API call. For general usage, consider :attr:`guilds` instead. + This method now returns a list of :class:`.UserGuild` instead of :class:`.Guild`. Parameters ----------- @@ -1623,15 +1622,12 @@ class Client: Returns -------- - List[:class:`.Guild`] + List[:class:`.UserGuild`] A list of all your guilds. """ state = self._connection guilds = await state.http.get_guilds(with_counts) - guilds = [Guild(data=data, state=state) for data in guilds] - for guild in guilds: - guild._cs_joined = True - return guilds + return [UserGuild(data=data, state=state) for data in guilds] async def fetch_template(self, code: Union[Template, str]) -> Template: """|coro| @@ -1664,10 +1660,6 @@ class Client: Retrieves a :class:`.Guild` from an ID. - .. versionchanged:: 2.0 - - ``guild_id`` parameter is now positional-only. - .. note:: Using this, you will **not** receive :attr:`.Guild.channels` and :attr:`.Guild.members`. @@ -1685,7 +1677,7 @@ class Client: guild_id: :class:`int` The guild's ID to fetch from. with_counts: :class:`bool` - Whether to include count information in the guild. This fills the + Whether to include count information in the guild. This fills in :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count`. .. versionadded:: 2.0 diff --git a/discord/emoji.py b/discord/emoji.py index f564a8f24..0c6183152 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -188,7 +188,7 @@ class Emoji(_EmojiTag, AssetMixin): return False if not self._roles: return True - emoji_roles, my_roles = self._roles, self.guild.me._roles + emoji_roles, my_roles = self._roles, self.guild.me._roles # type: ignore # Should just error ATP return any(my_roles.has(role_id) for role_id in emoji_roles) async def delete(self, *, reason: Optional[str] = None) -> None: diff --git a/discord/guild.py b/discord/guild.py index dbb55e797..e6cbaaa1d 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -104,6 +104,7 @@ if TYPE_CHECKING: Guild as GuildPayload, PartialGuild as PartialGuildPayload, RolePositionUpdate as RolePositionUpdatePayload, + UserGuild as UserGuildPayload, ) from .types.threads import ( Thread as ThreadPayload, @@ -139,11 +140,13 @@ MISSING = utils.MISSING __all__ = ( 'Guild', + 'UserGuild', 'BanEntry', + 'ApplicationCommandCounts', ) -class CommandCounts(NamedTuple): +class ApplicationCommandCounts(NamedTuple): chat_input: int user: int message: int @@ -161,6 +164,103 @@ class _GuildLimit(NamedTuple): filesize: int +class UserGuild(Hashable): + """Represents a partial joined guild. + + .. container:: operations + + .. describe:: x == y + + Checks if two guilds are equal. + + .. describe:: x != y + + Checks if two guilds are not equal. + + .. describe:: hash(x) + + Returns the guild's hash. + + .. describe:: str(x) + + Returns the guild's name. + + .. versionadded:: 2.0 + + Attributes + ---------- + id: :class:`int` + The guild's ID. + name: :class:`str` + The guild name. + features: List[:class:`str`] + A list of features that the guild has. The features that a guild can have are + subject to arbitrary change by Discord. + owner: :class:`bool` + Whether the current user is the owner of the guild. + approximate_member_count: Optional[:class:`int`] + The approximate number of members in the guild. This is ``None`` unless the guild is obtained + using :meth:`Client.fetch_guilds` with ``with_counts=True``. + approximate_presence_count: Optional[:class:`int`] + The approximate number of members currently active in the guild. + Offline members are excluded. This is ``None`` unless the guild is obtained using + :meth:`Client.fetch_guilds` with ``with_counts=True``. + """ + + __slots__ = ( + 'id', + 'name', + '_icon', + 'owner', + '_permissions', + 'features', + 'approximate_member_count', + 'approximate_presence_count', + '_state', + ) + + def __init__(self, *, state: ConnectionState, data: UserGuildPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.name: str = data['name'] + self._icon: Optional[str] = data.get('icon') + self.owner: bool = data.get('owner', False) + self._permissions: int = int(data.get('permissions', 0)) + self.features: List[str] = data.get('features', []) + self.approximate_member_count: Optional[int] = data.get('approximate_member_count') + self.approximate_presence_count: Optional[int] = data.get('approximate_presence_count') + + def __str__(self) -> str: + return self.name or '' + + def __repr__(self) -> str: + return f'' + + @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 permissions(self) -> Permissions: + """:class:`Permissions`: Returns the calculated permissions the current user has in the guild.""" + return Permissions(self._permissions) + + def is_joined(self) -> bool: + """Returns whether you are a member of this guild. + + Always returns ``True``. + + Returns + ------- + :class:`bool` + Whether you are a member of this guild. + """ + return True + + class Guild(Hashable): """Represents a Discord guild. @@ -231,9 +331,6 @@ class Guild(Hashable): features: List[:class:`str`] A list of features that the guild has. The features that a guild can have are subject to arbitrary change by Discord. - premium_tier: :class:`int` - The premium tier for this guild. Corresponds to "Server Boost Level" in the official UI. - The number goes from 0 to 3 inclusive. premium_subscription_count: :class:`int` The number of "boosts" this guild currently has. preferred_locale: :class:`Locale` @@ -251,7 +348,10 @@ class Guild(Hashable): .. versionchanged:: 2.0 This field is now an enum instead of an :class:`int`. + application_command_counts: Optional[:class:`ApplicationCommandCounts`] + A namedtuple representing the number of application commands in the guild, separated by type. + .. versionadded:: 2.0 approximate_member_count: Optional[:class:`int`] The approximate number of members in the guild. This is ``None`` unless the guild is obtained using :meth:`Client.fetch_guild` with ``with_counts=True``. @@ -264,15 +364,9 @@ class Guild(Hashable): .. versionadded:: 2.0 premium_progress_bar_enabled: :class:`bool` - Indicates if the guild has premium AKA server boost level progress bar enabled. + Indicates if the guild has the premium (server boost) progress bar enabled. .. versionadded:: 2.0 - keywords: Optional[:class:`str`] - Discovery search keywords for the guild. - - .. versionadded:: 2.0 - primary_category_id: Optional[:class:`int`] - The ID of the primary discovery category for the guild. widget_enabled: :class:`bool` Indicates if the guild has widget enabled. @@ -295,14 +389,13 @@ class Guild(Hashable): 'max_presences', 'max_members', 'max_video_channel_users', - 'premium_tier', + '_premium_tier', 'premium_subscription_count', 'preferred_locale', 'nsfw_level', 'mfa_level', 'vanity_url_code', 'owner_application_id', - 'command_counts', 'widget_enabled', '_widget_channel_id', '_members', @@ -333,8 +426,7 @@ class Guild(Hashable): '_member_list', 'keywords', 'primary_category_id', - 'application_command_count', - '_load_id', + 'application_command_counts', '_joined_at', '_cs_joined', ) @@ -359,9 +451,10 @@ class Guild(Hashable): self._stage_instances: Dict[int, StageInstance] = {} self._scheduled_events: Dict[int, ScheduledEvent] = {} self._state: ConnectionState = state - self.command_counts: Optional[CommandCounts] = None - self._member_count: int = 0 + self.application_command_counts: Optional[ApplicationCommandCounts] = None + self._member_count: Optional[int] = None self._presence_count: Optional[int] = None + self._large: Optional[bool] = None self._from_data(data) def _add_channel(self, channel: GuildChannel, /) -> None: @@ -468,7 +561,7 @@ class Guild(Hashable): def _from_data(self, guild: Union[GuildPayload, PartialGuildPayload]) -> None: try: - self._member_count: int = guild['member_count'] # type: ignore # Handled below + self._member_count: Optional[int] = guild['member_count'] # type: ignore # Handled below except KeyError: pass @@ -481,6 +574,8 @@ class Guild(Hashable): self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0)) self.afk_timeout: int = guild.get('afk_timeout', 0) self.unavailable: bool = guild.get('unavailable', False) + if self.unavailable: + self._member_count = 0 state = self._state # Speed up attribute access @@ -509,7 +604,6 @@ class Guild(Hashable): map(lambda d: state.store_sticker(self, d), guild.get('stickers', [])) ) self.features: List[str] = guild.get('features', []) - self.keywords: List[str] = guild.get('keywords', []) self._icon: Optional[str] = guild.get('icon') self._banner: Optional[str] = guild.get('banner') self._splash: Optional[str] = guild.get('splash') @@ -518,7 +612,7 @@ class Guild(Hashable): self.max_presences: Optional[int] = guild.get('max_presences') self.max_members: Optional[int] = guild.get('max_members') self.max_video_channel_users: Optional[int] = guild.get('max_video_channel_users') - self.premium_tier: int = guild.get('premium_tier', 0) + self._premium_tier = guild.get('premium_tier') self.premium_subscription_count: int = guild.get('premium_subscription_count') or 0 self.vanity_url_code: Optional[str] = guild.get('vanity_url_code') self.widget_enabled: bool = guild.get('widget_enabled', False) @@ -536,15 +630,16 @@ class Guild(Hashable): self.owner_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id') self.owner_application_id: Optional[int] = utils._get_as_snowflake(guild, 'application_id') self.premium_progress_bar_enabled: bool = guild.get('premium_progress_bar_enabled', False) - self.application_command_count: int = guild.get('application_command_count', 0) - self.primary_category_id: Optional[int] = guild.get('primary_category_id') self._joined_at = guild.get('joined_at') - large = None if self._member_count == 0 else self._member_count >= 250 - self._large: Optional[bool] = guild.get('large', large) + try: + self._large = guild['large'] # type: ignore + except KeyError: + pass - if (counts := guild.get('application_command_counts')) is not None: - self.command_counts = CommandCounts(counts.get(0, 0), counts.get(1, 0), counts.get(2, 0)) + counts = guild.get('application_command_counts') + if counts: + self.application_command_counts = ApplicationCommandCounts(counts.get(1, 0), counts.get(2, 0), counts.get(3, 0)) for vs in guild.get('voice_states', []): self._update_voice_state(vs, int(vs['channel_id'])) @@ -588,7 +683,7 @@ class Guild(Hashable): @property def _offline_members_hidden(self) -> bool: - return self._member_count > 1000 + return (self._member_count or 0) > 1000 @property def voice_channels(self) -> List[VoiceChannel]: @@ -613,12 +708,17 @@ class Guild(Hashable): return r @property - def me(self) -> Member: - """:class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`Member`. + def me(self) -> Optional[Member]: + """Optional[:class:`Member`]: Similar to :attr:`Client.user` except an instance of :class:`Member`. This is essentially used to get the member version of yourself. + + .. versionchanged:: 2.0 + + The type has been updated to be optional, which properly reflects cases where the current user + is not a member of the guild, or the current user's member object is not cached. """ self_id = self._state.self_id - return self.get_member(self_id) # type: ignore # The self member is *always* cached + return self.get_member(self_id) # type: ignore def is_joined(self) -> bool: """Returns whether you are a member of this guild. @@ -927,6 +1027,28 @@ class Guild(Hashable): """ return self._members.get(user_id) + @property + def premium_tier(self) -> int: + """:class:`int`: The premium tier for this guild. Corresponds to "Server Boost Level" in the official UI. + The number goes from 0 to 3 inclusive. + """ + tier = self._premium_tier + if tier is not None: + return tier + if 'PREMIUM_TIER_3_OVERRIDE' in self.features: + return 3 + + # Fallback to calculating by the number of boosts + count = self.premium_subscription_count + if count < 2: + return 0 + elif count < 7: + return 1 + elif count < 14: + return 2 + else: + return 3 + @property def premium_subscribers(self) -> List[Member]: """List[:class:`Member`]: A list of members who have subscribed to (boosted) this guild.""" @@ -1068,32 +1190,30 @@ class Guild(Hashable): .. warning:: Due to a Discord limitation, this may not always be up-to-date and accurate. - - .. versionchanged:: 2.0 - - Now returns an ``Optional[int]``. """ - return self._member_count + return self._member_count if self._member_count is not None else self.approximate_member_count @property def online_count(self) -> Optional[int]: """Optional[:class:`int`]: Returns the online member count. - This is not always populated. + .. versionadded:: 1.9 - This is an alias of :attr:`presence_count`. + .. warning:: + + Due to a Discord limitation, this may not always be up-to-date and accurate. """ return self._presence_count @property - def presence_count(self) -> Optional[int]: - """Optional[:class:`int`]: Returns the online member count. - - This is not always populated. + def application_command_count(self) -> Optional[int]: + """Optional[:class:`int`]: Returns the application command count if available. - There is an alias of this called :attr:`online_count`. + .. versionadded:: 2.0 """ - return self._presence_count + counts = self.application_command_counts + if counts: + sum(counts) @property def chunked(self) -> bool: @@ -4132,8 +4252,9 @@ class Guild(Hashable): if not subscription_slots: return [] - data = await self._state.http.apply_guild_subscription_slots(self.id, [slot.id for slot in subscription_slots]) - return [PremiumGuildSubscription(state=self._state, data=sub) for sub in data] + state = self._state + data = await state.http.apply_guild_subscription_slots(self.id, [slot.id for slot in subscription_slots]) + return [PremiumGuildSubscription(state=state, data=sub) for sub in data] async def entitlements( self, *, with_sku: bool = True, with_application: bool = True, exclude_deleted: bool = False diff --git a/discord/http.py b/discord/http.py index dbb2b1b01..32a2cd8bb 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1646,9 +1646,8 @@ class HTTPClient: # Guild management - def get_guilds(self, with_counts: bool = True) -> Response[List[guild.Guild]]: + def get_guilds(self, with_counts: bool = True) -> Response[List[guild.UserGuild]]: params = {'with_counts': str(with_counts).lower()} - return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True) def join_guild( @@ -1686,7 +1685,6 @@ class HTTPClient: def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]: params = {'with_counts': str(with_counts).lower()} - 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]: diff --git a/discord/invite.py b/discord/invite.py index a2e97df56..9b3ef09ba 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -526,7 +526,7 @@ class Invite(Hashable): channel_data = data.get('channel') if channel_data and channel_data.get('type') == ChannelType.private.value: - channel_data['recipients'] = [data['inviter']] if 'inviter' in data else [] # type: ignore + channel_data['recipients'] = [data['inviter']] if 'inviter' in data else [] channel = PartialInviteChannel(channel_data, state) channel = state.get_channel(getattr(channel, 'id', None)) or channel diff --git a/discord/role.py b/discord/role.py index 77e366b6e..c8fe2c28e 100644 --- a/discord/role.py +++ b/discord/role.py @@ -319,7 +319,7 @@ class Role(Hashable): .. versionadded:: 2.0 """ me = self.guild.me - return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) + return not self.is_default() and not self.managed and (me.top_role > self or me.id == self.guild.owner_id) # type: ignore # Should just error ATP @property def permissions(self) -> Permissions: diff --git a/discord/state.py b/discord/state.py index 61e9f6382..75a884248 100644 --- a/discord/state.py +++ b/discord/state.py @@ -52,7 +52,7 @@ from math import ceil from discord_protos import UserSettingsType from .errors import ClientException, InvalidData, NotFound -from .guild import CommandCounts, Guild +from .guild import ApplicationCommandCounts, Guild from .activity import BaseActivity, create_activity, Session from .user import User, ClientUser from .emoji import Emoji @@ -216,7 +216,7 @@ class MemberSidebar: @property def safe(self): - return self.safe_override or self.guild._member_count >= 75000 + return self.safe_override or (self.guild._member_count or 0) >= 75000 @staticmethod def amalgamate(original: Tuple[int, int], value: Tuple[int, int]) -> Tuple[int, int]: @@ -269,7 +269,7 @@ class MemberSidebar: channels = [ channel for channel in self.guild.channels - if channel.type != ChannelType.stage_voice and channel.permissions_for(guild.me).read_messages + if channel.type != ChannelType.stage_voice and channel.permissions_for(guild.me).read_messages # type: ignore ] if guild.rules_channel is not None: channels.insert(0, guild.rules_channel) @@ -755,12 +755,16 @@ class ConnectionState: 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) + emoji = Emoji(guild=guild, state=self, data=data) + if not self.is_guild_evicted(guild): + self._emojis[emoji_id] = emoji return emoji def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: sticker_id = int(data['id']) - self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) + sticker = GuildSticker(state=self, data=data) + if not self.is_guild_evicted(guild): + self._stickers[sticker_id] = sticker return sticker @property @@ -936,7 +940,7 @@ class ConnectionState: try: await asyncio.wait_for(future, timeout=10) except asyncio.TimeoutError: - _log.warning('Timed out waiting for chunks for guild_id %s.', guild.id) + _log.warning('Timed out waiting for member list subscriptions for guild_id %s.', guild.id) except (ClientException, InvalidData): pass except asyncio.CancelledError: @@ -1685,7 +1689,7 @@ class ConnectionState: continue # channel will be the correct type here - message = Message(channel=channel, data=message, state=self) # type: ignore + message = Message(channel=channel, data=message, state=self) if self._messages is not None: self._messages.append(message) @@ -1974,17 +1978,20 @@ class ConnectionState: for member in actually_add: self.dispatch('member_join', member) - def parse_guild_application_command_counts_update(self, data) -> None: + def parse_guild_application_command_index_update(self, data: gw.GuildApplicationCommandIndexUpdateEvent) -> None: guild = self._get_guild(int(data['guild_id'])) if guild is None: _log.debug( - 'GUILD_APPLICATION_COMMAND_COUNTS_UPDATE referencing an unknown guild ID: %s. Discarding.', data['guild_id'] + 'GUILD_APPLICATION_COMMAND_INDEX_UPDATE referencing an unknown guild ID: %s. Discarding.', data['guild_id'] ) return - guild.command_counts = CommandCounts(data.get(0, 0), data.get(1, 0), data.get(2, 0)) - - parse_guild_application_command_index_update = parse_guild_application_command_counts_update + counts = data['application_command_counts'] + old_counts = guild.application_command_counts or ApplicationCommandCounts(0, 0, 0) + guild.application_command_counts = new_counts = ApplicationCommandCounts( + counts.get(1, 0), counts.get(2, 0), counts.get(3, 0) + ) + self.dispatch('application_command_counts_update', guild, old_counts, new_counts) def parse_guild_emojis_update(self, data: gw.GuildEmojisUpdateEvent) -> None: guild = self._get_guild(int(data['guild_id'])) @@ -2083,7 +2090,7 @@ class ConnectionState: ws = self.ws channel = None for channel in guild.channels: - if channel.permissions_for(guild.me).read_messages and channel.type != ChannelType.stage_voice: + if channel.permissions_for(guild.me).read_messages and channel.type != ChannelType.stage_voice: # type: ignore break else: raise RuntimeError('No channels viewable') @@ -2143,12 +2150,16 @@ class ConnectionState: if not guild.me: await guild.query_members(user_ids=[self.self_id], cache=True) # type: ignore # self_id is always present here - if not force_scraping and any( - { - guild.me.guild_permissions.kick_members, - guild.me.guild_permissions.ban_members, - guild.me.guild_permissions.manage_roles, - } + if ( + not force_scraping + and guild.me + and any( + { + guild.me.guild_permissions.kick_members, + guild.me.guild_permissions.ban_members, + guild.me.guild_permissions.manage_roles, + } + ) ): request = self._chunk_requests.get(guild.id) if request is None: diff --git a/discord/types/gateway.py b/discord/types/gateway.py index 45aa83be2..0414ca2fc 100644 --- a/discord/types/gateway.py +++ b/discord/types/gateway.py @@ -39,7 +39,7 @@ from .snowflake import Snowflake from .message import Message from .sticker import GuildSticker from .application import BaseAchievement, PartialApplication -from .guild import Guild, UnavailableGuild, SupplementalGuild +from .guild import ApplicationCommandCounts, Guild, UnavailableGuild, SupplementalGuild from .user import Connection, User, PartialUser, ProtoSettingsType, Relationship, RelationshipType from .threads import Thread, ThreadMember from .scheduled_event import GuildScheduledEvent @@ -498,3 +498,8 @@ class PassiveUpdateEvent(TypedDict): channels: List[PartialUpdateChannel] voice_states: NotRequired[List[GuildVoiceState]] members: NotRequired[List[MemberWithUser]] + + +class GuildApplicationCommandIndexUpdateEvent(TypedDict): + guild_id: Snowflake + application_command_counts: ApplicationCommandCounts diff --git a/discord/types/guild.py b/discord/types/guild.py index 139c7743f..d61d83380 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -22,7 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import List, Literal, Optional, TypedDict +from typing import Dict, List, Literal, Optional, TypedDict from typing_extensions import NotRequired from .scheduled_event import GuildScheduledEvent @@ -55,9 +55,17 @@ MFALevel = Literal[0, 1] VerificationLevel = Literal[0, 1, 2, 3, 4] NSFWLevel = Literal[0, 1, 2, 3] PremiumTier = Literal[0, 1, 2, 3] +ApplicationCommandCounts = Dict[Literal[1, 2, 3], int] -class PartialGuild(UnavailableGuild): +class BaseGuild(TypedDict): + id: Snowflake + name: str + icon: Optional[str] + features: List[str] + + +class PartialGuild(BaseGuild): name: str icon: Optional[str] splash: Optional[str] @@ -77,7 +85,7 @@ class GuildPreview(PartialGuild, _GuildPreviewUnique): ... -class Guild(PartialGuild): +class Guild(UnavailableGuild, PartialGuild): owner_id: Snowflake region: str afk_channel_id: Optional[Snowflake] @@ -100,7 +108,6 @@ class Guild(PartialGuild): stickers: List[GuildSticker] stage_instances: List[StageInstance] guild_scheduled_events: List[GuildScheduledEvent] - icon_hash: NotRequired[Optional[str]] owner: NotRequired[bool] permissions: NotRequired[str] widget_enabled: NotRequired[bool] @@ -117,6 +124,14 @@ class Guild(PartialGuild): max_members: NotRequired[int] premium_subscription_count: NotRequired[int] max_video_channel_users: NotRequired[int] + application_command_counts: ApplicationCommandCounts + + +class UserGuild(BaseGuild): + owner: bool + permissions: str + approximate_member_count: NotRequired[int] + approximate_presence_count: NotRequired[int] class InviteGuild(Guild, total=False): diff --git a/docs/api.rst b/docs/api.rst index 64ad96011..092bfa012 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -760,6 +760,19 @@ Guilds :param after: A list of stickers after the update. :type after: Sequence[:class:`GuildSticker`] +.. function:: on_application_command_counts_update(guild, before, after) + + Called when a :class:`Guild`\'s application command counts are updated. + + .. versionadded:: 2.0 + + :param guild: The guild who got their application command counts updated. + :type guild: :class:`Guild` + :param before: A namedtuple of application command counts before the update. + :type before: :class:`ApplicationCommandCounts` + :param after: A namedtuple of application command counts after the update. + :type after: :class:`ApplicationCommandCounts` + .. function:: on_audit_log_entry_create(entry) Called when a :class:`Guild` gets a new audit log entry. @@ -6750,6 +6763,11 @@ Guild :members: :inherited-members: +.. attributetable:: UserGuild + +.. autoclass:: UserGuild() + :members: + .. class:: BanEntry A namedtuple which represents a ban returned from :meth:`~Guild.bans`. @@ -6765,6 +6783,28 @@ Guild :type: :class:`User` +.. class:: ApplicationCommandCounts + + A namedtuple which represents the application command counts for a guild. + + .. attribute:: chat_input + + The number of chat input (slash) commands. + + :type: :class:`int` + + .. attribute:: user + + The number of user commands. + + :type: :class:`int` + + .. attribute:: message + + The number of message commands. + + :type: :class:`int` + Role ~~~~~