Browse Source

Add separate data class for Client.fetch_guilds(), add application_command_counts_update event, fix various guild issues

pull/10109/head
dolfies 2 years ago
parent
commit
07203e576f
  1. 26
      discord/client.py
  2. 2
      discord/emoji.py
  3. 211
      discord/guild.py
  4. 4
      discord/http.py
  5. 2
      discord/invite.py
  6. 2
      discord/role.py
  7. 49
      discord/state.py
  8. 7
      discord/types/gateway.py
  9. 23
      discord/types/guild.py
  10. 40
      docs/api.rst

26
discord/client.py

@ -51,7 +51,7 @@ from .user import _UserTag, User, ClientUser, Note
from .invite import Invite from .invite import Invite
from .template import Template from .template import Template
from .widget import Widget from .widget import Widget
from .guild import Guild from .guild import Guild, UserGuild
from .emoji import Emoji from .emoji import Emoji
from .channel import _private_channel_factory, _threaded_channel_factory, GroupChannel, PartialMessageable from .channel import _private_channel_factory, _threaded_channel_factory, GroupChannel, PartialMessageable
from .enums import ActivityType, ChannelType, ClientType, ConnectionType, EntitlementType, Status from .enums import ActivityType, ChannelType, ClientType, ConnectionType, EntitlementType, Status
@ -1597,19 +1597,18 @@ class Client:
# Guild stuff # 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| """|coro|
Retrieves all your your guilds. Retrieves all your guilds.
.. note:: .. note::
Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, This method is an API call. For general usage, consider :attr:`guilds` instead.
:attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`.
.. 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 Parameters
----------- -----------
@ -1623,15 +1622,12 @@ class Client:
Returns Returns
-------- --------
List[:class:`.Guild`] List[:class:`.UserGuild`]
A list of all your guilds. A list of all your guilds.
""" """
state = self._connection state = self._connection
guilds = await state.http.get_guilds(with_counts) guilds = await state.http.get_guilds(with_counts)
guilds = [Guild(data=data, state=state) for data in guilds] return [UserGuild(data=data, state=state) for data in guilds]
for guild in guilds:
guild._cs_joined = True
return guilds
async def fetch_template(self, code: Union[Template, str]) -> Template: async def fetch_template(self, code: Union[Template, str]) -> Template:
"""|coro| """|coro|
@ -1664,10 +1660,6 @@ class Client:
Retrieves a :class:`.Guild` from an ID. Retrieves a :class:`.Guild` from an ID.
.. versionchanged:: 2.0
``guild_id`` parameter is now positional-only.
.. note:: .. note::
Using this, you will **not** receive :attr:`.Guild.channels` and :attr:`.Guild.members`. Using this, you will **not** receive :attr:`.Guild.channels` and :attr:`.Guild.members`.
@ -1685,7 +1677,7 @@ class Client:
guild_id: :class:`int` guild_id: :class:`int`
The guild's ID to fetch from. The guild's ID to fetch from.
with_counts: :class:`bool` 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`. :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count`.
.. versionadded:: 2.0 .. versionadded:: 2.0

2
discord/emoji.py

@ -188,7 +188,7 @@ class Emoji(_EmojiTag, AssetMixin):
return False return False
if not self._roles: if not self._roles:
return True 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) return any(my_roles.has(role_id) for role_id in emoji_roles)
async def delete(self, *, reason: Optional[str] = None) -> None: async def delete(self, *, reason: Optional[str] = None) -> None:

211
discord/guild.py

@ -104,6 +104,7 @@ if TYPE_CHECKING:
Guild as GuildPayload, Guild as GuildPayload,
PartialGuild as PartialGuildPayload, PartialGuild as PartialGuildPayload,
RolePositionUpdate as RolePositionUpdatePayload, RolePositionUpdate as RolePositionUpdatePayload,
UserGuild as UserGuildPayload,
) )
from .types.threads import ( from .types.threads import (
Thread as ThreadPayload, Thread as ThreadPayload,
@ -139,11 +140,13 @@ MISSING = utils.MISSING
__all__ = ( __all__ = (
'Guild', 'Guild',
'UserGuild',
'BanEntry', 'BanEntry',
'ApplicationCommandCounts',
) )
class CommandCounts(NamedTuple): class ApplicationCommandCounts(NamedTuple):
chat_input: int chat_input: int
user: int user: int
message: int message: int
@ -161,6 +164,103 @@ class _GuildLimit(NamedTuple):
filesize: int 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'<UserGuild id={self.id} name={self.name!r}>'
@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): class Guild(Hashable):
"""Represents a Discord guild. """Represents a Discord guild.
@ -231,9 +331,6 @@ class Guild(Hashable):
features: List[:class:`str`] features: List[:class:`str`]
A list of features that the guild has. The features that a guild can have are A list of features that the guild has. The features that a guild can have are
subject to arbitrary change by Discord. 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` premium_subscription_count: :class:`int`
The number of "boosts" this guild currently has. The number of "boosts" this guild currently has.
preferred_locale: :class:`Locale` preferred_locale: :class:`Locale`
@ -251,7 +348,10 @@ class Guild(Hashable):
.. versionchanged:: 2.0 .. versionchanged:: 2.0
This field is now an enum instead of an :class:`int`. 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`] approximate_member_count: Optional[:class:`int`]
The approximate number of members in the guild. This is ``None`` unless the guild is obtained 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``. using :meth:`Client.fetch_guild` with ``with_counts=True``.
@ -264,15 +364,9 @@ class Guild(Hashable):
.. versionadded:: 2.0 .. versionadded:: 2.0
premium_progress_bar_enabled: :class:`bool` 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 .. 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` widget_enabled: :class:`bool`
Indicates if the guild has widget enabled. Indicates if the guild has widget enabled.
@ -295,14 +389,13 @@ class Guild(Hashable):
'max_presences', 'max_presences',
'max_members', 'max_members',
'max_video_channel_users', 'max_video_channel_users',
'premium_tier', '_premium_tier',
'premium_subscription_count', 'premium_subscription_count',
'preferred_locale', 'preferred_locale',
'nsfw_level', 'nsfw_level',
'mfa_level', 'mfa_level',
'vanity_url_code', 'vanity_url_code',
'owner_application_id', 'owner_application_id',
'command_counts',
'widget_enabled', 'widget_enabled',
'_widget_channel_id', '_widget_channel_id',
'_members', '_members',
@ -333,8 +426,7 @@ class Guild(Hashable):
'_member_list', '_member_list',
'keywords', 'keywords',
'primary_category_id', 'primary_category_id',
'application_command_count', 'application_command_counts',
'_load_id',
'_joined_at', '_joined_at',
'_cs_joined', '_cs_joined',
) )
@ -359,9 +451,10 @@ class Guild(Hashable):
self._stage_instances: Dict[int, StageInstance] = {} self._stage_instances: Dict[int, StageInstance] = {}
self._scheduled_events: Dict[int, ScheduledEvent] = {} self._scheduled_events: Dict[int, ScheduledEvent] = {}
self._state: ConnectionState = state self._state: ConnectionState = state
self.command_counts: Optional[CommandCounts] = None self.application_command_counts: Optional[ApplicationCommandCounts] = None
self._member_count: int = 0 self._member_count: Optional[int] = None
self._presence_count: Optional[int] = None self._presence_count: Optional[int] = None
self._large: Optional[bool] = None
self._from_data(data) self._from_data(data)
def _add_channel(self, channel: GuildChannel, /) -> None: def _add_channel(self, channel: GuildChannel, /) -> None:
@ -468,7 +561,7 @@ class Guild(Hashable):
def _from_data(self, guild: Union[GuildPayload, PartialGuildPayload]) -> None: def _from_data(self, guild: Union[GuildPayload, PartialGuildPayload]) -> None:
try: 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: except KeyError:
pass pass
@ -481,6 +574,8 @@ class Guild(Hashable):
self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0)) self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0))
self.afk_timeout: int = guild.get('afk_timeout', 0) self.afk_timeout: int = guild.get('afk_timeout', 0)
self.unavailable: bool = guild.get('unavailable', False) self.unavailable: bool = guild.get('unavailable', False)
if self.unavailable:
self._member_count = 0
state = self._state # Speed up attribute access 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', [])) map(lambda d: state.store_sticker(self, d), guild.get('stickers', []))
) )
self.features: List[str] = guild.get('features', []) self.features: List[str] = guild.get('features', [])
self.keywords: List[str] = guild.get('keywords', [])
self._icon: Optional[str] = guild.get('icon') self._icon: Optional[str] = guild.get('icon')
self._banner: Optional[str] = guild.get('banner') self._banner: Optional[str] = guild.get('banner')
self._splash: Optional[str] = guild.get('splash') 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_presences: Optional[int] = guild.get('max_presences')
self.max_members: Optional[int] = guild.get('max_members') self.max_members: Optional[int] = guild.get('max_members')
self.max_video_channel_users: Optional[int] = guild.get('max_video_channel_users') 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.premium_subscription_count: int = guild.get('premium_subscription_count') or 0
self.vanity_url_code: Optional[str] = guild.get('vanity_url_code') self.vanity_url_code: Optional[str] = guild.get('vanity_url_code')
self.widget_enabled: bool = guild.get('widget_enabled', False) 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_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id')
self.owner_application_id: Optional[int] = utils._get_as_snowflake(guild, 'application_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.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') self._joined_at = guild.get('joined_at')
large = None if self._member_count == 0 else self._member_count >= 250 try:
self._large: Optional[bool] = guild.get('large', large) self._large = guild['large'] # type: ignore
except KeyError:
pass
if (counts := guild.get('application_command_counts')) is not None: counts = guild.get('application_command_counts')
self.command_counts = CommandCounts(counts.get(0, 0), counts.get(1, 0), counts.get(2, 0)) 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', []): for vs in guild.get('voice_states', []):
self._update_voice_state(vs, int(vs['channel_id'])) self._update_voice_state(vs, int(vs['channel_id']))
@ -588,7 +683,7 @@ class Guild(Hashable):
@property @property
def _offline_members_hidden(self) -> bool: def _offline_members_hidden(self) -> bool:
return self._member_count > 1000 return (self._member_count or 0) > 1000
@property @property
def voice_channels(self) -> List[VoiceChannel]: def voice_channels(self) -> List[VoiceChannel]:
@ -613,12 +708,17 @@ class Guild(Hashable):
return r return r
@property @property
def me(self) -> Member: def me(self) -> Optional[Member]:
""":class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`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. 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 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: def is_joined(self) -> bool:
"""Returns whether you are a member of this guild. """Returns whether you are a member of this guild.
@ -927,6 +1027,28 @@ class Guild(Hashable):
""" """
return self._members.get(user_id) 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 @property
def premium_subscribers(self) -> List[Member]: def premium_subscribers(self) -> List[Member]:
"""List[:class:`Member`]: A list of members who have subscribed to (boosted) this guild.""" """List[:class:`Member`]: A list of members who have subscribed to (boosted) this guild."""
@ -1068,32 +1190,30 @@ class Guild(Hashable):
.. warning:: .. warning::
Due to a Discord limitation, this may not always be up-to-date and accurate. 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 @property
def online_count(self) -> Optional[int]: def online_count(self) -> Optional[int]:
"""Optional[:class:`int`]: Returns the online member count. """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 return self._presence_count
@property @property
def presence_count(self) -> Optional[int]: def application_command_count(self) -> Optional[int]:
"""Optional[:class:`int`]: Returns the online member count. """Optional[:class:`int`]: Returns the application command count if available.
This is not always populated.
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 @property
def chunked(self) -> bool: def chunked(self) -> bool:
@ -4132,8 +4252,9 @@ class Guild(Hashable):
if not subscription_slots: if not subscription_slots:
return [] return []
data = await self._state.http.apply_guild_subscription_slots(self.id, [slot.id for slot in subscription_slots]) state = self._state
return [PremiumGuildSubscription(state=self._state, data=sub) for sub in data] 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( async def entitlements(
self, *, with_sku: bool = True, with_application: bool = True, exclude_deleted: bool = False self, *, with_sku: bool = True, with_application: bool = True, exclude_deleted: bool = False

4
discord/http.py

@ -1646,9 +1646,8 @@ class HTTPClient:
# Guild management # 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()} params = {'with_counts': str(with_counts).lower()}
return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True) return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True)
def join_guild( def join_guild(
@ -1686,7 +1685,6 @@ class HTTPClient:
def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]: def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]:
params = {'with_counts': str(with_counts).lower()} params = {'with_counts': str(with_counts).lower()}
return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params) 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]: def get_guild_preview(self, guild_id: Snowflake) -> Response[guild.GuildPreview]:

2
discord/invite.py

@ -526,7 +526,7 @@ class Invite(Hashable):
channel_data = data.get('channel') channel_data = data.get('channel')
if channel_data and channel_data.get('type') == ChannelType.private.value: 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 = PartialInviteChannel(channel_data, state)
channel = state.get_channel(getattr(channel, 'id', None)) or channel channel = state.get_channel(getattr(channel, 'id', None)) or channel

2
discord/role.py

@ -319,7 +319,7 @@ class Role(Hashable):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
me = self.guild.me 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 @property
def permissions(self) -> Permissions: def permissions(self) -> Permissions:

49
discord/state.py

@ -52,7 +52,7 @@ from math import ceil
from discord_protos import UserSettingsType from discord_protos import UserSettingsType
from .errors import ClientException, InvalidData, NotFound from .errors import ClientException, InvalidData, NotFound
from .guild import CommandCounts, Guild from .guild import ApplicationCommandCounts, Guild
from .activity import BaseActivity, create_activity, Session from .activity import BaseActivity, create_activity, Session
from .user import User, ClientUser from .user import User, ClientUser
from .emoji import Emoji from .emoji import Emoji
@ -216,7 +216,7 @@ class MemberSidebar:
@property @property
def safe(self): 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 @staticmethod
def amalgamate(original: Tuple[int, int], value: Tuple[int, int]) -> Tuple[int, int]: def amalgamate(original: Tuple[int, int], value: Tuple[int, int]) -> Tuple[int, int]:
@ -269,7 +269,7 @@ class MemberSidebar:
channels = [ channels = [
channel channel
for channel in self.guild.channels 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: if guild.rules_channel is not None:
channels.insert(0, guild.rules_channel) channels.insert(0, guild.rules_channel)
@ -755,12 +755,16 @@ class ConnectionState:
def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji: def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji:
# The id will be present here # The id will be present here
emoji_id = int(data['id']) # type: ignore 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 return emoji
def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker:
sticker_id = int(data['id']) 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 return sticker
@property @property
@ -936,7 +940,7 @@ class ConnectionState:
try: try:
await asyncio.wait_for(future, timeout=10) await asyncio.wait_for(future, timeout=10)
except asyncio.TimeoutError: 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): except (ClientException, InvalidData):
pass pass
except asyncio.CancelledError: except asyncio.CancelledError:
@ -1685,7 +1689,7 @@ class ConnectionState:
continue continue
# channel will be the correct type here # 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: if self._messages is not None:
self._messages.append(message) self._messages.append(message)
@ -1974,17 +1978,20 @@ class ConnectionState:
for member in actually_add: for member in actually_add:
self.dispatch('member_join', member) 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'])) guild = self._get_guild(int(data['guild_id']))
if guild is None: if guild is None:
_log.debug( _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 return
guild.command_counts = CommandCounts(data.get(0, 0), data.get(1, 0), data.get(2, 0)) counts = data['application_command_counts']
old_counts = guild.application_command_counts or ApplicationCommandCounts(0, 0, 0)
parse_guild_application_command_index_update = parse_guild_application_command_counts_update 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: def parse_guild_emojis_update(self, data: gw.GuildEmojisUpdateEvent) -> None:
guild = self._get_guild(int(data['guild_id'])) guild = self._get_guild(int(data['guild_id']))
@ -2083,7 +2090,7 @@ class ConnectionState:
ws = self.ws ws = self.ws
channel = None channel = None
for channel in guild.channels: 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 break
else: else:
raise RuntimeError('No channels viewable') raise RuntimeError('No channels viewable')
@ -2143,12 +2150,16 @@ class ConnectionState:
if not guild.me: if not guild.me:
await guild.query_members(user_ids=[self.self_id], cache=True) # type: ignore # self_id is always present here 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( if (
{ not force_scraping
guild.me.guild_permissions.kick_members, and guild.me
guild.me.guild_permissions.ban_members, and any(
guild.me.guild_permissions.manage_roles, {
} 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) request = self._chunk_requests.get(guild.id)
if request is None: if request is None:

7
discord/types/gateway.py

@ -39,7 +39,7 @@ from .snowflake import Snowflake
from .message import Message from .message import Message
from .sticker import GuildSticker from .sticker import GuildSticker
from .application import BaseAchievement, PartialApplication 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 .user import Connection, User, PartialUser, ProtoSettingsType, Relationship, RelationshipType
from .threads import Thread, ThreadMember from .threads import Thread, ThreadMember
from .scheduled_event import GuildScheduledEvent from .scheduled_event import GuildScheduledEvent
@ -498,3 +498,8 @@ class PassiveUpdateEvent(TypedDict):
channels: List[PartialUpdateChannel] channels: List[PartialUpdateChannel]
voice_states: NotRequired[List[GuildVoiceState]] voice_states: NotRequired[List[GuildVoiceState]]
members: NotRequired[List[MemberWithUser]] members: NotRequired[List[MemberWithUser]]
class GuildApplicationCommandIndexUpdateEvent(TypedDict):
guild_id: Snowflake
application_command_counts: ApplicationCommandCounts

23
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. 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 typing_extensions import NotRequired
from .scheduled_event import GuildScheduledEvent from .scheduled_event import GuildScheduledEvent
@ -55,9 +55,17 @@ MFALevel = Literal[0, 1]
VerificationLevel = Literal[0, 1, 2, 3, 4] VerificationLevel = Literal[0, 1, 2, 3, 4]
NSFWLevel = Literal[0, 1, 2, 3] NSFWLevel = Literal[0, 1, 2, 3]
PremiumTier = 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 name: str
icon: Optional[str] icon: Optional[str]
splash: Optional[str] splash: Optional[str]
@ -77,7 +85,7 @@ class GuildPreview(PartialGuild, _GuildPreviewUnique):
... ...
class Guild(PartialGuild): class Guild(UnavailableGuild, PartialGuild):
owner_id: Snowflake owner_id: Snowflake
region: str region: str
afk_channel_id: Optional[Snowflake] afk_channel_id: Optional[Snowflake]
@ -100,7 +108,6 @@ class Guild(PartialGuild):
stickers: List[GuildSticker] stickers: List[GuildSticker]
stage_instances: List[StageInstance] stage_instances: List[StageInstance]
guild_scheduled_events: List[GuildScheduledEvent] guild_scheduled_events: List[GuildScheduledEvent]
icon_hash: NotRequired[Optional[str]]
owner: NotRequired[bool] owner: NotRequired[bool]
permissions: NotRequired[str] permissions: NotRequired[str]
widget_enabled: NotRequired[bool] widget_enabled: NotRequired[bool]
@ -117,6 +124,14 @@ class Guild(PartialGuild):
max_members: NotRequired[int] max_members: NotRequired[int]
premium_subscription_count: NotRequired[int] premium_subscription_count: NotRequired[int]
max_video_channel_users: 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): class InviteGuild(Guild, total=False):

40
docs/api.rst

@ -760,6 +760,19 @@ Guilds
:param after: A list of stickers after the update. :param after: A list of stickers after the update.
:type after: Sequence[:class:`GuildSticker`] :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) .. function:: on_audit_log_entry_create(entry)
Called when a :class:`Guild` gets a new audit log entry. Called when a :class:`Guild` gets a new audit log entry.
@ -6750,6 +6763,11 @@ Guild
:members: :members:
:inherited-members: :inherited-members:
.. attributetable:: UserGuild
.. autoclass:: UserGuild()
:members:
.. class:: BanEntry .. class:: BanEntry
A namedtuple which represents a ban returned from :meth:`~Guild.bans`. A namedtuple which represents a ban returned from :meth:`~Guild.bans`.
@ -6765,6 +6783,28 @@ Guild
:type: :class:`User` :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 Role
~~~~~ ~~~~~

Loading…
Cancel
Save