diff --git a/discord/components.py b/discord/components.py index aa67b87ca..874a59102 100644 --- a/discord/components.py +++ b/discord/components.py @@ -28,7 +28,7 @@ from asyncio import TimeoutError from datetime import datetime from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union -from .enums import try_enum, ComponentType, ButtonStyle, InteractionType +from .enums import try_enum, ComponentType, ButtonStyle from .errors import InvalidData from .utils import get_slots, MISSING, time_snowflake from .partial_emoji import PartialEmoji, _EmojiTag @@ -41,11 +41,8 @@ if TYPE_CHECKING: SelectOption as SelectOptionPayload, ActionRow as ActionRowPayload, ) - from .types.snowflake import Snowflake from .emoji import Emoji from .message import Message - from .state import ConnectionState - from .user import BaseUser __all__ = ( @@ -59,71 +56,6 @@ __all__ = ( C = TypeVar('C', bound='Component') -class Interaction: - """Represents an interaction. - - Attributes - ------------ - id: :class:`int` - The interaction ID. - nonce: Optional[Union[:class:`int`, :class:`str`]] - The interaction's nonce. Not always present. - name: Optional[:class:`str`] - The name of the application command, if applicable. - type: :class:`InteractionType` - The type of interaction. - successful: Optional[:class:`bool`] - Whether the interaction succeeded. - If this is your interaction, this is not immediately available. - It is filled when Discord notifies us about the outcome of the interaction. - user: :class:`User` - The user who initiated the interaction. - """ - - __slots__ = ('id', 'type', 'nonce', 'user', 'name', 'successful') - - def __init__( - self, - id: int, - type: int, - nonce: Optional[Snowflake] = None, - *, - user: BaseUser, - name: Optional[str] = None, - ) -> None: - self.id = id - self.nonce = nonce - self.type = try_enum(InteractionType, type) - self.user = user - self.name = name - self.successful: Optional[bool] = None - - @classmethod - def _from_self( - cls, *, id: Snowflake, type: int, nonce: Optional[Snowflake] = None, user: BaseUser - ) -> Interaction: - return cls(int(id), type, nonce, user=user) - - @classmethod - def _from_message( - cls, state: ConnectionState, *, id: Snowflake, type: int, user: BaseUser, **data: Dict[str, Any] - ) -> Interaction: - name = data.get('name') - user = state.store_user(user) - inst = cls(id, type, user=user, name=name) - inst.successful = True - return inst - - def __repr__(self) -> str: - s = self.successful - return f'' - - def __bool__(self) -> bool: - if self.successful is not None: - return self.successful - raise TypeError('Interaction has not been resolved yet') - - class Component: """Represents a Discord Bot UI Kit Component. diff --git a/discord/guild.py b/discord/guild.py index 5ad8ec8e0..021ef9b82 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -100,13 +100,17 @@ if TYPE_CHECKING: from .state import ConnectionState from .voice_client import VoiceProtocol - import datetime - VocalGuildChannel = Union[VoiceChannel, StageChannel] GuildChannel = Union[VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel] ByCategoryItem = Tuple[Optional[CategoryChannel], List[GuildChannel]] +class CommandCounts(NamedTuple): + chat_input: int + user: int + message: int + + class BanEntry(NamedTuple): reason: Optional[str] user: User @@ -271,6 +275,7 @@ class Guild(Hashable): 'vanity_code', 'premium_progress_bar_enabled', 'notification_settings', + 'command_counts', '_members', '_channels', '_icon', @@ -311,6 +316,7 @@ class Guild(Hashable): self._stage_instances: Dict[int, StageInstance] = {} self._state: ConnectionState = state self.notification_settings: Optional[GuildSettings] = None + self.command_counts: Optional[CommandCounts] = None self._from_data(data) # Get it running @@ -480,6 +486,9 @@ class Guild(Hashable): if (settings := guild.get('settings')) is not None: self.notification_settings = GuildSettings(state=state, data=settings) + 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)) + for mdata in guild.get('merged_members', []): try: member = Member(data=mdata, guild=self, state=state) @@ -914,7 +923,7 @@ class Guild(Hashable): return count == len(self._members) @property - def created_at(self) -> datetime.datetime: + def created_at(self) -> datetime: """:class:`datetime.datetime`: Returns the guild's creation time in UTC.""" return utils.snowflake_time(self.id) diff --git a/discord/interactions.py b/discord/interactions.py new file mode 100644 index 000000000..a4a981813 --- /dev/null +++ b/discord/interactions.py @@ -0,0 +1,100 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Dolfies + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING + +from .enums import InteractionType, try_enum + +if TYPE_CHECKING: + from .state import ConnectionState + from .types.snowflake import Snowflake + from .types.user import User as UserPayload + from .user import BaseUser, ClientUser + + +class Interaction: + """Represents an interaction. + + Attributes + ------------ + id: :class:`int` + The interaction ID. + nonce: Optional[Union[:class:`int`, :class:`str`]] + The interaction's nonce. Not always present. + name: Optional[:class:`str`] + The name of the application command, if applicable. + type: :class:`InteractionType` + The type of interaction. + successful: Optional[:class:`bool`] + Whether the interaction succeeded. + If this is your interaction, this is not immediately available. + It is filled when Discord notifies us about the outcome of the interaction. + user: :class:`User` + The user who initiated the interaction. + """ + + __slots__ = ('id', 'type', 'nonce', 'user', 'name', 'successful') + + def __init__( + self, + id: int, + type: int, + nonce: Optional[Snowflake] = None, + *, + user: BaseUser, + name: Optional[str] = None, + ) -> None: + self.id = id + self.nonce = nonce + self.type = try_enum(InteractionType, type) + self.user = user + self.name = name + self.successful: Optional[bool] = None + + @classmethod + def _from_self( + cls, *, id: Snowflake, type: int, nonce: Optional[Snowflake] = None, user: ClientUser + ) -> Interaction: + return cls(int(id), type, nonce, user=user) + + @classmethod + def _from_message( + cls, state: ConnectionState, *, id: Snowflake, type: int, user: UserPayload, **data + ) -> Interaction: + name = data.get('name') + user = state.store_user(user) + inst = cls(int(id), type, user=user, name=name) + inst.successful = True + return inst + + def __repr__(self) -> str: + s = self.successful + return f'' + + def __bool__(self) -> bool: + if self.successful is not None: + return self.successful + raise TypeError('Interaction has not been resolved yet') \ No newline at end of file diff --git a/discord/state.py b/discord/state.py index 6e2b04abf..cad92dc19 100644 --- a/discord/state.py +++ b/discord/state.py @@ -36,7 +36,7 @@ import os import random from .errors import NotFound -from .guild import Guild +from .guild import CommandCounts, Guild from .activity import BaseActivity from .user import User, ClientUser from .emoji import Emoji @@ -59,7 +59,7 @@ from .threads import Thread, ThreadMember from .sticker import GuildSticker from .settings import UserSettings from .tracking import Tracking -from .components import Interaction +from .interactions import Interaction if TYPE_CHECKING: @@ -544,7 +544,7 @@ class ConnectionState: def chunker( self, guild_id: int, query: str = '', limit: int = 0, presences: bool = True, *, nonce: Optional[str] = None - ) -> None: + ): return self.ws.request_chunks(guild_id, query=query, limit=limit, presences=presences, nonce=nonce) async def query_members(self, guild: Guild, query: str, limit: int, user_ids: List[int], cache: bool, presences: bool): @@ -812,7 +812,6 @@ class ConnectionState: def parse_presence_update(self, data) -> None: guild_id = utils._get_as_snowflake(data, 'guild_id') - # guild_id won't be None here guild = self._get_guild(guild_id) if guild is None: _log.debug('PRESENCE_UPDATE referencing an unknown guild ID: %s. Discarding.', guild_id) @@ -853,6 +852,10 @@ class ConnectionState: new_settings._update(data) self.dispatch('guild_settings_update', old_settings, new_settings) + def parse_user_required_action_update(self, data) -> None: + required_action = try_enum(RequiredActionType, data['required_action']) + self.dispatch('required_action_update', required_action) + def parse_invite_create(self, data) -> None: invite = Invite.from_gateway(state=self, data=data) self.dispatch('invite_create', invite) @@ -1274,6 +1277,14 @@ class ConnectionState: else: _log.debug('GUILD_MEMBER_LIST_UPDATE type UPDATE referencing an unknown member ID: %s. Discarding.', user_id) + def parse_guild_application_command_counts_update(self, data) -> 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']) + return + + guild.command_counts = CommandCounts(data.get(0, 0), data.get(1, 0), data.get(2, 0)) + def parse_guild_emojis_update(self, data) -> None: guild = self._get_guild(int(data['guild_id'])) if guild is None: @@ -1632,10 +1643,6 @@ class ConnectionState: timestamp = datetime.datetime.fromtimestamp(data.get('timestamp'), tz=datetime.timezone.utc) self.dispatch('typing', channel, member, timestamp) - def parse_user_required_action_update(self, data) -> None: - required_action = try_enum(RequiredActionType, data['required_action']) - self.dispatch('required_action_update', required_action) - def parse_relationship_add(self, data) -> None: key = int(data['id']) old = self.user.get_relationship(key)