Browse Source

Add missing types

pull/10109/head
dolfies 2 years ago
parent
commit
fd240be395
  1. 8
      discord/abc.py
  2. 10
      discord/calls.py
  3. 127
      discord/commands.py
  4. 81
      discord/components.py
  5. 19
      discord/file.py
  6. 58
      discord/http.py
  7. 3
      discord/interactions.py
  8. 6
      discord/member.py
  9. 9
      discord/message.py
  10. 11
      discord/modal.py
  11. 81
      discord/settings.py
  12. 90
      discord/state.py
  13. 7
      discord/types/activity.py
  14. 4
      discord/types/channel.py
  15. 40
      discord/types/command.py
  16. 16
      discord/types/components.py
  17. 153
      discord/types/gateway.py
  18. 121
      discord/types/interactions.py
  19. 6
      discord/types/invite.py
  20. 6
      discord/types/member.py
  21. 11
      discord/types/message.py
  22. 65
      discord/types/user.py

8
discord/abc.py

@ -262,7 +262,7 @@ async def _handle_commands(
prev_cursor = cursor prev_cursor = cursor
cursor = data['cursor'].get('next') cursor = data['cursor'].get('next')
cmds = data['application_commands'] cmds = data['application_commands']
apps: Dict[int, dict] = {int(app['id']): app for app in data.get('applications') or []} apps = {int(app['id']): app for app in data.get('applications') or []}
for cmd in cmds: for cmd in cmds:
# Handle faked parameters # Handle faked parameters
@ -280,8 +280,10 @@ async def _handle_commands(
except ValueError: except ValueError:
pass pass
cmd['application'] = apps.get(int(cmd['application_id'])) application_data = apps.get(int(cmd['application_id']))
yield cls(state=state, data=cmd, channel=channel, target=target) application = state.create_integration_application(application_data) if application_data else None
yield cls(state=state, data=cmd, channel=channel, target=target, application=application)
cmd_ids = None cmd_ids = None
if application_id or len(cmds) < min(limit if limit else 25, 25) or len(cmds) == limit == 25: if application_id or len(cmds) < min(limit if limit else 25, 25) or len(cmds) == limit == 25:

10
discord/calls.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import Callable, Dict, List, Optional, Tuple, TYPE_CHECKING, Union from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
from . import utils from . import utils
from .errors import ClientException from .errors import ClientException
@ -39,6 +39,7 @@ if TYPE_CHECKING:
from .member import VoiceState from .member import VoiceState
from .message import Message from .message import Message
from .state import ConnectionState from .state import ConnectionState
from .types.gateway import CallCreateEvent, CallUpdateEvent
from .user import BaseUser, User from .user import BaseUser, User
_PrivateChannel = Union[abc.DMChannel, abc.GroupChannel] _PrivateChannel = Union[abc.DMChannel, abc.GroupChannel]
@ -144,7 +145,7 @@ class PrivateCall:
def __init__( def __init__(
self, self,
*, *,
data: dict, data: Union[CallCreateEvent, CallUpdateEvent],
state: ConnectionState, state: ConnectionState,
message: Optional[Message], message: Optional[Message],
channel: abc.PrivateChannel, channel: abc.PrivateChannel,
@ -153,7 +154,6 @@ class PrivateCall:
self._cs_message = message self._cs_message = message
self.channel = channel # type: ignore # Will always be a DMChannel here self.channel = channel # type: ignore # Will always be a DMChannel here
self._ended: bool = False self._ended: bool = False
self._update(data) self._update(data)
def _delete(self) -> None: def _delete(self) -> None:
@ -168,7 +168,7 @@ class PrivateCall:
state = self.voice_state_for(user) state = self.voice_state_for(user)
return bool(state and state.channel and state.channel.id == self.channel.id) return bool(state and state.channel and state.channel.id == self.channel.id)
def _update(self, data) -> None: def _update(self, data: Union[CallCreateEvent, CallUpdateEvent]) -> None:
self._message_id = int(data['message_id']) self._message_id = int(data['message_id'])
self.unavailable = data.get('unavailable', False) self.unavailable = data.get('unavailable', False)
try: try:
@ -179,7 +179,7 @@ class PrivateCall:
channel = self.channel channel = self.channel
recipients = self._get_recipients() recipients = self._get_recipients()
lookup = {u.id: u for u in recipients} lookup = {u.id: u for u in recipients}
self._ringing = tuple(filter(None, map(lookup.get, data.get('ringing', [])))) self._ringing = tuple(filter(None, map(lookup.get, [int(x) for x in data.get('ringing', [])])))
for vs in data.get('voice_states', []): for vs in data.get('voice_states', []):
self._state._update_voice_state(vs, channel.id) self._state._update_voice_state(vs, channel.id)

127
discord/commands.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, Tuple, Type, Union, runtime_checkable from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Protocol, Tuple, Type, Union, runtime_checkable
from .enums import ApplicationCommandOptionType, ApplicationCommandType, ChannelType, InteractionType, try_enum from .enums import ApplicationCommandOptionType, ApplicationCommandType, ChannelType, InteractionType, try_enum
from .interactions import _wrapped_interaction from .interactions import _wrapped_interaction
@ -38,8 +38,23 @@ if TYPE_CHECKING:
from .file import _FileBase from .file import _FileBase
from .guild import Guild from .guild import Guild
from .interactions import Interaction from .interactions import Interaction
from .message import Attachment, Message from .message import Message
from .state import ConnectionState from .state import ConnectionState
from .types.command import (
ApplicationCommand as ApplicationCommandPayload,
ApplicationCommandOption,
ApplicationCommandOptionChoice as OptionChoicePayload,
SubCommand as SubCommandPayload,
_ValueApplicationCommandOption as OptionPayload,
)
from .types.interactions import (
ApplicationCommandInteractionData,
ChatInputCommandInteractionData,
MessageCommandInteractionData,
UserCommandInteractionData,
ApplicationCommandInteractionDataOption as InteractionDataOption,
)
from .types.message import PartialAttachment as PartialAttachmentPayload
__all__ = ( __all__ = (
@ -113,7 +128,10 @@ class ApplicationCommand(Protocol):
return self.name return self.name
async def __call__( async def __call__(
self, data: dict, files: Optional[List[_FileBase]] = None, channel: Optional[Messageable] = None self,
data: ApplicationCommandInteractionData,
files: Optional[List[_FileBase]] = None,
channel: Optional[Messageable] = None,
) -> Interaction: ) -> Interaction:
channel = channel or self.target_channel channel = channel or self.target_channel
if channel is None: if channel is None:
@ -174,7 +192,6 @@ class BaseCommand(ApplicationCommand, Hashable):
'description', 'description',
'id', 'id',
'version', 'version',
'type',
'application', 'application',
'application_id', 'application_id',
'dm_permission', 'dm_permission',
@ -186,21 +203,27 @@ class BaseCommand(ApplicationCommand, Hashable):
'_default_member_permissions', '_default_member_permissions',
) )
if TYPE_CHECKING:
type: ApplicationCommandType
def __init__( def __init__(
self, *, state: ConnectionState, data: Dict[str, Any], channel: Optional[Messageable] = None, **kwargs self,
*,
state: ConnectionState,
data: ApplicationCommandPayload,
channel: Optional[Messageable] = None,
application: Optional[IntegrationApplication] = None,
**kwargs,
) -> None: ) -> None:
self._state = state self._state = state
self._data = data self._data = data
self.application = application
self.name = data['name'] self.name = data['name']
self.description = data['description'] self.description = data['description']
self._channel = channel self._channel = channel
self.application_id: int = int(data['application_id']) self.application_id: int = int(data['application_id'])
self.id: int = int(data['id']) self.id: int = int(data['id'])
self.version = int(data['version']) self.version = int(data['version'])
self.type = try_enum(ApplicationCommandType, data['type'])
application = data.get('application')
self.application = state.create_integration_application(application) if application else None
self._default_member_permissions = _get_as_snowflake(data, 'default_member_permissions') self._default_member_permissions = _get_as_snowflake(data, 'default_member_permissions')
dm_permission = data.get('dm_permission') # Null means true? dm_permission = data.get('dm_permission') # Null means true?
@ -225,28 +248,29 @@ class SlashMixin(ApplicationCommand, Protocol):
async def __call__( async def __call__(
self, self,
options: List[dict], options: List[InteractionDataOption],
files: Optional[List[_FileBase]], files: Optional[List[_FileBase]],
attachments: List[Attachment], attachments: List[PartialAttachmentPayload],
channel: Optional[Messageable] = None, channel: Optional[Messageable] = None,
) -> Interaction: ) -> Interaction:
obj = self._parent obj = self._parent
command = obj._data command = obj._data
command['name_localized'] = command['name'] data: ChatInputCommandInteractionData = {
data = {
'application_command': command, 'application_command': command,
'attachments': attachments, 'attachments': attachments,
'id': str(obj.id), 'id': str(obj.id),
'name': obj.name, 'name': obj.name,
'options': options, 'options': options,
'type': obj.type.value, 'type': ApplicationCommandType.chat_input.value,
'version': str(obj.version), 'version': str(obj.version),
} }
if self.guild_id: if self.guild_id:
data['guild_id'] = str(self.guild_id) data['guild_id'] = str(self.guild_id)
return await super().__call__(data, files, channel) return await super().__call__(data, files, channel)
def _parse_kwargs(self, kwargs: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], List[_FileBase], List[Attachment]]: def _parse_kwargs(
self, kwargs: Dict[str, Any]
) -> Tuple[List[InteractionDataOption], List[_FileBase], List[PartialAttachmentPayload]]:
possible_options = {o.name: o for o in self.options} possible_options = {o.name: o for o in self.options}
kwargs = {k: v for k, v in kwargs.items() if k in possible_options} kwargs = {k: v for k, v in kwargs.items() if k in possible_options}
options = [] options = []
@ -286,18 +310,18 @@ class SlashMixin(ApplicationCommand, Protocol):
return options, files, attachments return options, files, attachments
def _unwrap_options(self, data: List[Dict[str, Any]]) -> None: def _unwrap_options(self, data: List[ApplicationCommandOption]) -> None:
options = [] options = []
children = [] children = []
for option in data: for option in data:
type = try_enum(ApplicationCommandOptionType, option['type']) type = try_enum(ApplicationCommandOptionType, option['type'])
if type in { if type in (
ApplicationCommandOptionType.sub_command, ApplicationCommandOptionType.sub_command,
ApplicationCommandOptionType.sub_command_group, ApplicationCommandOptionType.sub_command_group,
}: ):
children.append(SubCommand(parent=self, data=option)) children.append(SubCommand(parent=self, data=option)) # type: ignore
else: else:
options.append(Option(option)) options.append(Option(option)) # type: ignore
self.options = options self.options = options
self.children = children self.children = children
@ -340,8 +364,6 @@ class UserCommand(BaseCommand):
The command's name. The command's name.
description: :class:`str` description: :class:`str`
The command's description, if any. The command's description, if any.
type: :class:`ApplicationCommandType`
The type of application command. This will always be :attr:`ApplicationCommandType.user`.
dm_permission: :class:`bool` dm_permission: :class:`bool`
Whether the command is enabled in DMs. Whether the command is enabled in DMs.
nsfw: :class:`bool` nsfw: :class:`bool`
@ -386,18 +408,23 @@ class UserCommand(BaseCommand):
raise TypeError('__call__() missing 1 required positional argument: \'user\'') raise TypeError('__call__() missing 1 required positional argument: \'user\'')
command = self._data command = self._data
data = { data: UserCommandInteractionData = {
'application_command': command, 'application_command': command,
'attachments': [], 'attachments': [],
'id': str(self.id), 'id': str(self.id),
'name': self.name, 'name': self.name,
'options': [], 'options': [],
'target_id': str(user.id), 'target_id': str(user.id),
'type': self.type.value, 'type': ApplicationCommandType.user.value,
'version': str(self.version), 'version': str(self.version),
} }
return await super().__call__(data, None, channel) return await super().__call__(data, None, channel)
@property
def type(self) -> Literal[ApplicationCommandType.user]:
""":class:`ApplicationCommandType`: The type of application command. This is always :attr:`ApplicationCommandType.user`."""
return ApplicationCommandType.user
@property @property
def target_user(self) -> Optional[Snowflake]: def target_user(self) -> Optional[Snowflake]:
"""Optional[:class:`~abc.Snowflake`]: The user this application command will be used on. """Optional[:class:`~abc.Snowflake`]: The user this application command will be used on.
@ -452,8 +479,6 @@ class MessageCommand(BaseCommand):
The command's name. The command's name.
description: :class:`str` description: :class:`str`
The command's description, if any. The command's description, if any.
type: :class:`ApplicationCommandType`
The type of application command. This will always be :attr:`ApplicationCommandType.message`.
dm_permission: :class:`bool` dm_permission: :class:`bool`
Whether the command is enabled in DMs. Whether the command is enabled in DMs.
nsfw: :class:`bool` nsfw: :class:`bool`
@ -498,8 +523,7 @@ class MessageCommand(BaseCommand):
raise TypeError('__call__() missing 1 required positional argument: \'message\'') raise TypeError('__call__() missing 1 required positional argument: \'message\'')
command = self._data command = self._data
command['name_localized'] = command['name'] data: MessageCommandInteractionData = {
data = {
'application_command': command, 'application_command': command,
'attachments': [], 'attachments': [],
'id': str(self.id), 'id': str(self.id),
@ -511,6 +535,11 @@ class MessageCommand(BaseCommand):
} }
return await super().__call__(data, None, channel) return await super().__call__(data, None, channel)
@property
def type(self) -> Literal[ApplicationCommandType.message]:
""":class:`ApplicationCommandType`: The type of application command. This is always :attr:`ApplicationCommandType.message`."""
return ApplicationCommandType.message
@property @property
def target_message(self) -> Optional[Message]: def target_message(self) -> Optional[Message]:
"""Optional[:class:`Message`]: The message this application command will be used on. """Optional[:class:`Message`]: The message this application command will be used on.
@ -565,8 +594,6 @@ class SlashCommand(BaseCommand, SlashMixin):
The command's name. The command's name.
description: :class:`str` description: :class:`str`
The command's description, if any. The command's description, if any.
type: :class:`ApplicationCommandType`
The type of application command.
dm_permission: :class:`bool` dm_permission: :class:`bool`
Whether the command is enabled in DMs. Whether the command is enabled in DMs.
nsfw: :class:`bool` nsfw: :class:`bool`
@ -587,7 +614,7 @@ class SlashCommand(BaseCommand, SlashMixin):
__slots__ = ('_parent', 'options', 'children') __slots__ = ('_parent', 'options', 'children')
def __init__(self, *, data: Dict[str, Any], **kwargs) -> None: def __init__(self, *, data: ApplicationCommandPayload, **kwargs) -> None:
super().__init__(data=data, **kwargs) super().__init__(data=data, **kwargs)
self._parent = self self._parent = self
self._unwrap_options(data.get('options', [])) self._unwrap_options(data.get('options', []))
@ -629,6 +656,11 @@ class SlashCommand(BaseCommand, SlashMixin):
BASE += f' children={len(self.children)}' BASE += f' children={len(self.children)}'
return BASE + '>' return BASE + '>'
@property
def type(self) -> Literal[ApplicationCommandType.chat_input]:
""":class:`ApplicationCommandType`: The type of application command. This is always :attr:`ApplicationCommandType.chat_input`."""
return ApplicationCommandType.chat_input
def is_group(self) -> bool: def is_group(self) -> bool:
"""Query whether this command is a group. """Query whether this command is a group.
@ -663,8 +695,6 @@ class SubCommand(SlashMixin):
The subcommand's name. The subcommand's name.
description: :class:`str` description: :class:`str`
The subcommand's description, if any. The subcommand's description, if any.
type: :class:`ApplicationCommandType`
The type of application command. Always :attr:`ApplicationCommandType.chat_input`.
parent: Union[:class:`SlashCommand`, :class:`SubCommand`] parent: Union[:class:`SlashCommand`, :class:`SubCommand`]
The parent command. The parent command.
options: List[:class:`Option`] options: List[:class:`Option`]
@ -680,18 +710,20 @@ class SubCommand(SlashMixin):
'parent', 'parent',
'options', 'options',
'children', 'children',
'type',
) )
def __init__(self, *, parent, data): def __init__(self, *, parent: Union[SlashCommand, SubCommand], data: SubCommandPayload):
self.name = data['name'] self.name = data['name']
self.description = data.get('description') self.description = data.get('description')
self._state = parent._state self._state = parent._state
self.parent: Union[SlashCommand, SubCommand] = parent self.parent = parent
self._parent: SlashCommand = getattr(parent, 'parent', parent) # type: ignore self._parent: SlashCommand = getattr(parent, 'parent', parent) # type: ignore
self.type = ApplicationCommandType.chat_input # Avoid confusion I guess self._type: Literal[
self._type: ApplicationCommandOptionType = try_enum(ApplicationCommandOptionType, data['type']) ApplicationCommandOptionType.sub_command, ApplicationCommandOptionType.sub_command_group
self._unwrap_options(data.get('options', [])) ] = try_enum(
ApplicationCommandOptionType, data['type']
) # type: ignore
self._unwrap_options(data.get('options', [])) # type: ignore # ???
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -733,8 +765,7 @@ class SubCommand(SlashMixin):
raise TypeError('Cannot use a group') raise TypeError('Cannot use a group')
options, files, attachments = self._parse_kwargs(kwargs) options, files, attachments = self._parse_kwargs(kwargs)
options: List[InteractionDataOption] = [
options = [
{ {
'type': self._type.value, 'type': self._type.value,
'name': self.name, 'name': self.name,
@ -742,7 +773,7 @@ class SubCommand(SlashMixin):
} }
] ]
for parent in self._walk_parents(): for parent in self._walk_parents():
options = [ options: List[InteractionDataOption] = [
{ {
'type': parent._type.value, 'type': parent._type.value,
'name': parent.name, 'name': parent.name,
@ -760,6 +791,12 @@ class SubCommand(SlashMixin):
BASE += f' children={len(self.children)}' BASE += f' children={len(self.children)}'
return BASE + '>' return BASE + '>'
@property
def type(self) -> Literal[ApplicationCommandType.chat_input]:
""":class:`ApplicationCommandType`: The type of application command. Always :attr:`ApplicationCommandType.chat_input`."""
# Avoid confusion I guess
return ApplicationCommandType.chat_input
@property @property
def qualified_name(self) -> str: def qualified_name(self) -> str:
""":class:`str`: Returns the fully qualified command name. """:class:`str`: Returns the fully qualified command name.
@ -889,13 +926,13 @@ class Option:
'autocomplete', 'autocomplete',
) )
def __init__(self, data): def __init__(self, data: OptionPayload):
self.name: str = data['name'] self.name: str = data['name']
self.description: str = data['description'] self.description: str = data['description']
self.type: ApplicationCommandOptionType = try_enum(ApplicationCommandOptionType, data['type']) self.type: ApplicationCommandOptionType = try_enum(ApplicationCommandOptionType, data['type'])
self.required: bool = data.get('required', False) self.required: bool = data.get('required', False)
self.min_value: Optional[Union[int, float]] = data.get('min_value') self.min_value: Optional[Union[int, float]] = data.get('min_value')
self.max_value: Optional[int] = data.get('max_value') self.max_value: Optional[Union[int, float]] = data.get('max_value')
self.choices = [OptionChoice(choice, self.type) for choice in data.get('choices', [])] self.choices = [OptionChoice(choice, self.type) for choice in data.get('choices', [])]
self.channel_types: List[ChannelType] = [try_enum(ChannelType, c) for c in data.get('channel_types', [])] self.channel_types: List[ChannelType] = [try_enum(ChannelType, c) for c in data.get('channel_types', [])]
self.autocomplete: bool = data.get('autocomplete', False) self.autocomplete: bool = data.get('autocomplete', False)
@ -934,7 +971,7 @@ class OptionChoice:
__slots__ = ('name', 'value') __slots__ = ('name', 'value')
def __init__(self, data: Dict[str, str], type: ApplicationCommandOptionType): def __init__(self, data: OptionChoicePayload, type: ApplicationCommandOptionType):
self.name: str = data['name'] self.name: str = data['name']
self.value: Union[str, int, float] self.value: Union[str, int, float]
if type is ApplicationCommandOptionType.string: if type is ApplicationCommandOptionType.string:

81
discord/components.py

@ -24,30 +24,40 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import ClassVar, List, Literal, Optional, TYPE_CHECKING, Tuple, Union, overload from typing import TYPE_CHECKING, ClassVar, List, Literal, Optional, Tuple, Union, overload
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, InteractionType from .enums import ButtonStyle, ComponentType, InteractionType, TextStyle, try_enum
from .interactions import _wrapped_interaction from .interactions import _wrapped_interaction
from .utils import _generate_nonce, get_slots, MISSING
from .partial_emoji import PartialEmoji, _EmojiTag from .partial_emoji import PartialEmoji, _EmojiTag
from .utils import MISSING, _generate_nonce, get_slots
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self
from .emoji import Emoji
from .interactions import Interaction
from .message import Message
from .types.components import ( from .types.components import (
ActionRow as ActionRowPayload, ActionRow as ActionRowPayload,
Component as ComponentPayload, ActionRowChildComponent,
ButtonComponent as ButtonComponentPayload, ButtonComponent as ButtonComponentPayload,
Component as ComponentPayload,
MessageChildComponent,
ModalChildComponent,
SelectMenu as SelectMenuPayload, SelectMenu as SelectMenuPayload,
SelectOption as SelectOptionPayload, SelectOption as SelectOptionPayload,
TextInput as TextInputPayload, TextInput as TextInputPayload,
ActionRowChildComponent as ActionRowChildComponentPayload,
) )
from .emoji import Emoji from .types.interactions import (
from .interactions import Interaction ActionRowInteractionData,
from .message import Message ButtonInteractionData,
ComponentInteractionData,
SelectInteractionData,
TextInputInteractionData,
)
ActionRowChildComponentType = Union['Button', 'SelectMenu', 'TextInput'] MessageChildComponentType = Union['Button', 'SelectMenu']
ActionRowChildComponentType = Union[MessageChildComponentType, 'TextInput']
__all__ = ( __all__ = (
@ -99,7 +109,7 @@ class Component:
setattr(self, slot, value) setattr(self, slot, value)
return self return self
def to_dict(self) -> ComponentPayload: def to_dict(self) -> Union[ActionRowInteractionData, ComponentInteractionData]:
raise NotImplementedError raise NotImplementedError
@ -124,13 +134,12 @@ class ActionRow(Component):
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: ComponentPayload, message: Message): def __init__(self, data: ActionRowPayload, message: Message):
self.message = message self.message = message
self.children: List[ActionRowChildComponentType] = [] self.children: List[ActionRowChildComponentType] = []
for component_data in data.get('components', []): for component_data in data.get('components', []):
component = _component_factory(component_data, message) component = _component_factory(component_data, message)
if component is not None: if component is not None:
self.children.append(component) self.children.append(component)
@ -139,12 +148,11 @@ class ActionRow(Component):
""":class:`ComponentType`: The type of component.""" """:class:`ComponentType`: The type of component."""
return ComponentType.action_row return ComponentType.action_row
def to_dict(self) -> ActionRowPayload: def to_dict(self) -> ActionRowInteractionData:
# NOTE: This will have to be changed for the inevitable selects in modals
return { return {
'type': self.type.value, 'type': ComponentType.action_row.value,
'components': [c.to_dict() for c in self.children], # type: ignore 'components': [c.to_dict() for c in self.children],
} } # type: ignore
class Button(Component): class Button(Component):
@ -202,10 +210,10 @@ class Button(Component):
""":class:`ComponentType`: The type of component.""" """:class:`ComponentType`: The type of component."""
return ComponentType.button return ComponentType.button
def to_dict(self) -> dict: def to_dict(self) -> ButtonInteractionData:
return { return {
'component_type': self.type.value, 'component_type': self.type.value,
'custom_id': self.custom_id, 'custom_id': self.custom_id or '',
} }
async def click(self) -> Union[str, Interaction]: async def click(self) -> Union[str, Interaction]:
@ -237,7 +245,7 @@ class Button(Component):
_generate_nonce(), _generate_nonce(),
InteractionType.component, InteractionType.component,
None, None,
message.channel, # type: ignore # acc_channel is always correct here message.channel, # type: ignore # channel is always correct here
self.to_dict(), self.to_dict(),
message=message, message=message,
) )
@ -296,7 +304,7 @@ class SelectMenu(Component):
""":class:`ComponentType`: The type of component.""" """:class:`ComponentType`: The type of component."""
return ComponentType.select return ComponentType.select
def to_dict(self, options: Optional[Tuple[SelectOption]] = None) -> dict: def to_dict(self, options: Optional[Tuple[SelectOption]] = None) -> SelectInteractionData:
return { return {
'component_type': self.type.value, 'component_type': self.type.value,
'custom_id': self.custom_id, 'custom_id': self.custom_id,
@ -524,31 +532,40 @@ class TextInput(Component):
""" """
self.value = value self.value = value
def to_dict(self) -> dict: def to_dict(self) -> TextInputInteractionData:
return { return {
'type': self.type.value, 'type': self.type.value,
'custom_id': self.custom_id, 'custom_id': self.custom_id,
'value': self.value, 'value': self.value or '',
} }
@overload @overload
def _component_factory( def _component_factory(data: ActionRowPayload, message: Message = ...) -> ActionRow:
data: ActionRowChildComponentPayload, message: Message = ... ...
) -> Optional[ActionRowChildComponentType]:
@overload
def _component_factory(data: MessageChildComponent, message: Message = ...) -> Optional[MessageChildComponentType]:
...
@overload
def _component_factory(data: ModalChildComponent, message: Message = ...) -> Optional[TextInput]:
...
@overload
def _component_factory(data: ActionRowChildComponent, message: Message = ...) -> Optional[ActionRowChildComponentType]:
... ...
@overload @overload
def _component_factory( def _component_factory(data: ComponentPayload, message: Message = ...) -> Optional[Component]:
data: ComponentPayload, message: Message = ...
) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
... ...
def _component_factory( def _component_factory(data: ComponentPayload, message: Message = MISSING) -> Optional[Component]:
data: ComponentPayload, message: Message = MISSING
) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
if data['type'] == 1: if data['type'] == 1:
return ActionRow(data, message) return ActionRow(data, message)
elif data['type'] == 2: elif data['type'] == 2:

19
discord/file.py

@ -24,12 +24,13 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from base64 import b64encode
from hashlib import md5
import io import io
import os import os
from base64 import b64encode
from hashlib import md5
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
import yarl import yarl
from typing import Any, Dict, Optional, Tuple, Union, TYPE_CHECKING
from .utils import MISSING, cached_slot_property from .utils import MISSING, cached_slot_property
@ -37,7 +38,11 @@ if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self
from .state import ConnectionState from .state import ConnectionState
from .types.message import CloudAttachment as CloudAttachmentPayload, UploadedAttachment as UploadedAttachmentPayload from .types.message import (
CloudAttachment as CloudAttachmentPayload,
PartialAttachment as PartialAttachmentPayload,
UploadedAttachment as UploadedAttachmentPayload,
)
__all__ = ( __all__ = (
'File', 'File',
@ -76,8 +81,8 @@ class _FileBase:
def filename(self, value: str) -> None: def filename(self, value: str) -> None:
self._filename, self.spoiler = _strip_spoiler(value) self._filename, self.spoiler = _strip_spoiler(value)
def to_dict(self, index: int) -> Dict[str, Any]: def to_dict(self, index: int) -> PartialAttachmentPayload:
payload = { payload: PartialAttachmentPayload = {
'id': str(index), 'id': str(index),
'filename': self.filename, 'filename': self.filename,
} }
@ -264,7 +269,7 @@ class CloudFile(_FileBase):
url = yarl.URL(self.url) url = yarl.URL(self.url)
return url.query['upload_id'] return url.query['upload_id']
def to_dict(self, index: int) -> Dict[str, Any]: def to_dict(self, index: int) -> PartialAttachmentPayload:
payload = super().to_dict(index) payload = super().to_dict(index)
payload['uploaded_filename'] = self.upload_filename payload['uploaded_filename'] = self.upload_filename
return payload return payload

58
discord/http.py

@ -96,6 +96,7 @@ if TYPE_CHECKING:
guild, guild,
hub, hub,
integration, integration,
interactions,
invite, invite,
library, library,
member, member,
@ -1179,7 +1180,6 @@ class HTTPClient:
flags: Optional[int] = None, flags: Optional[int] = None,
last_viewed: Optional[int] = None, last_viewed: Optional[int] = None,
) -> None: ) -> None:
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id)
payload = {} payload = {}
if manual: if manual:
payload['manual'] = True payload['manual'] = True
@ -1192,7 +1192,10 @@ class HTTPClient:
if last_viewed is not None: if last_viewed is not None:
payload['last_viewed'] = last_viewed payload['last_viewed'] = last_viewed
data: read_state.AcknowledgementToken = await self.request(r, json=payload) data: read_state.AcknowledgementToken = await self.request(
Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id),
json=payload,
)
self.ack_token = data.get('token') if data else None self.ack_token = data.get('token') if data else None
def ack_guild_feature( def ack_guild_feature(
@ -1699,8 +1702,8 @@ class HTTPClient:
params: MultipartParameters, params: MultipartParameters,
reason: Optional[str] = None, reason: Optional[str] = None,
) -> Response[threads.ForumThread]: ) -> Response[threads.ForumThread]:
query = {'use_nested_fields': 1}
r = Route('POST', '/channels/{channel_id}/threads', channel_id=channel_id) r = Route('POST', '/channels/{channel_id}/threads', channel_id=channel_id)
query = {'use_nested_fields': 'true'}
if params.files: if params.files:
return self.request(r, files=params.files, form=params.multipart, params=query, reason=reason) return self.request(r, files=params.files, form=params.multipart, params=query, reason=reason)
else: else:
@ -1925,8 +1928,8 @@ class HTTPClient:
payload = {'level': mfa_level} payload = {'level': mfa_level}
return self.request(Route('POST', '/guilds/{guild_id}/mfa', guild_id=guild_id), json=payload, reason=reason) return self.request(Route('POST', '/guilds/{guild_id}/mfa', guild_id=guild_id), json=payload, reason=reason)
def edit_guild_settings(self, guild_id: Snowflake, fields): # TODO: type def edit_guild_settings(self, guild_id: Snowflake, payload: Dict[str, Any]) -> Response[user.UserGuildSettings]:
return self.request(Route('PATCH', '/users/@me/guilds/{guild_id}/settings', guild_id=guild_id), json=fields) return self.request(Route('PATCH', '/users/@me/guilds/{guild_id}/settings', guild_id=guild_id), json=payload)
def get_template(self, code: str) -> Response[template.Template]: def get_template(self, code: str) -> Response[template.Template]:
return self.request(Route('GET', '/guilds/templates/{code}', code=code)) return self.request(Route('GET', '/guilds/templates/{code}', code=code))
@ -2041,15 +2044,16 @@ class HTTPClient:
return self.request(Route('GET', '/sticker-packs'), params=params) return self.request(Route('GET', '/sticker-packs'), params=params)
def get_sticker_pack(self, pack_id: Snowflake): def get_sticker_pack(self, pack_id: Snowflake) -> Response[sticker.StickerPack]:
return self.request(Route('GET', '/sticker-packs/{pack_id}', pack_id=pack_id)) return self.request(Route('GET', '/sticker-packs/{pack_id}', pack_id=pack_id))
def get_all_guild_stickers(self, guild_id: Snowflake) -> Response[List[sticker.GuildSticker]]: def get_all_guild_stickers(self, guild_id: Snowflake) -> Response[List[sticker.GuildSticker]]:
return self.request(Route('GET', '/guilds/{guild_id}/stickers', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/stickers', guild_id=guild_id))
def get_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake) -> Response[sticker.GuildSticker]: def get_guild_sticker(self, guild_id: Snowflake, sticker_id: Snowflake) -> Response[sticker.GuildSticker]:
r = Route('GET', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id) return self.request(
return self.request(r) Route('GET', '/guilds/{guild_id}/stickers/{sticker_id}', guild_id=guild_id, sticker_id=sticker_id)
)
def create_guild_sticker( def create_guild_sticker(
self, guild_id: Snowflake, payload: Dict[str, Any], file: File, reason: Optional[str] self, guild_id: Snowflake, payload: Dict[str, Any], file: File, reason: Optional[str]
@ -2285,7 +2289,7 @@ class HTTPClient:
channel_id: Snowflake = MISSING, channel_id: Snowflake = MISSING,
channel_type: ChannelType = MISSING, channel_type: ChannelType = MISSING,
message: Optional[Message] = None, message: Optional[Message] = None,
) -> Response[invite.PartialInvite]: ) -> Response[invite.AcceptedInvite]:
if message: # Invite Button Embed if message: # Invite Button Embed
props = ContextProperties.from_invite_button_embed( props = ContextProperties.from_invite_button_embed(
guild_id=getattr(message.guild, 'id', None), guild_id=getattr(message.guild, 'id', None),
@ -2523,7 +2527,7 @@ class HTTPClient:
) -> Response[member.MemberWithUser]: ) -> Response[member.MemberWithUser]:
return self.edit_member(guild_id, user_id, channel_id=channel_id, reason=reason) return self.edit_member(guild_id, user_id, channel_id=channel_id, reason=reason)
def get_ringability(self, channel_id: Snowflake): def get_ringability(self, channel_id: Snowflake) -> Response[channel.CallEligibility]:
return self.request(Route('GET', '/channels/{channel_id}/call', channel_id=channel_id)) return self.request(Route('GET', '/channels/{channel_id}/call', channel_id=channel_id))
def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]: def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]:
@ -2534,7 +2538,7 @@ class HTTPClient:
payload = {'recipients': recipients} payload = {'recipients': recipients}
return self.request(Route('POST', '/channels/{channel_id}/call/stop-ringing', channel_id=channel_id), json=payload) return self.request(Route('POST', '/channels/{channel_id}/call/stop-ringing', channel_id=channel_id), json=payload)
def change_call_voice_region(self, channel_id: int, voice_region: str): # TODO: return type def change_call_voice_region(self, channel_id: int, voice_region: str) -> Response[None]:
payload = {'region': voice_region} payload = {'region': voice_region}
return self.request(Route('PATCH', '/channels/{channel_id}/call', channel_id=channel_id), json=payload) return self.request(Route('PATCH', '/channels/{channel_id}/call', channel_id=channel_id), json=payload)
@ -2864,7 +2868,9 @@ class HTTPClient:
else: else:
props = ContextProperties.empty() props = ContextProperties.empty()
return self.request(Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id), context_properties=props, json=payload) return self.request(
Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id), context_properties=props, json=payload
)
def send_friend_request(self, username: str, discriminator: Snowflake) -> Response[None]: def send_friend_request(self, username: str, discriminator: Snowflake) -> Response[None]:
payload = {'username': username, 'discriminator': int(discriminator) or None} payload = {'username': username, 'discriminator': int(discriminator) or None}
@ -3165,7 +3171,7 @@ class HTTPClient:
super_properties_to_track=True, super_properties_to_track=True,
) )
def delete_app_whitelist(self, app_id: Snowflake, user_id: Snowflake): def delete_app_whitelist(self, app_id: Snowflake, user_id: Snowflake) -> Response[None]:
return self.request( return self.request(
Route('DELETE', '/oauth2/applications/{app_id}/allowlist/{user_id}', app_id=app_id, user_id=user_id), Route('DELETE', '/oauth2/applications/{app_id}/allowlist/{user_id}', app_id=app_id, user_id=user_id),
super_properties_to_track=True, super_properties_to_track=True,
@ -3223,7 +3229,7 @@ class HTTPClient:
super_properties_to_track=True, super_properties_to_track=True,
) )
def create_team(self, name: str): def create_team(self, name: str) -> Response[team.Team]:
payload = {'name': name} payload = {'name': name}
return self.request(Route('POST', '/teams'), json=payload, super_properties_to_track=True) return self.request(Route('POST', '/teams'), json=payload, super_properties_to_track=True)
@ -3251,13 +3257,15 @@ class HTTPClient:
def get_team_members(self, team_id: Snowflake) -> Response[List[team.TeamMember]]: def get_team_members(self, team_id: Snowflake) -> Response[List[team.TeamMember]]:
return self.request(Route('GET', '/teams/{team_id}/members', team_id=team_id), super_properties_to_track=True) return self.request(Route('GET', '/teams/{team_id}/members', team_id=team_id), super_properties_to_track=True)
def invite_team_member(self, team_id: Snowflake, username: str, discriminator: Snowflake): def invite_team_member(
self, team_id: Snowflake, username: str, discriminator: Optional[Snowflake] = None
) -> Response[team.TeamMember]:
payload = {'username': username, 'discriminator': str(discriminator) or None} payload = {'username': username, 'discriminator': str(discriminator) or None}
return self.request( return self.request(
Route('POST', '/teams/{team_id}/members', team_id=team_id), json=payload, super_properties_to_track=True Route('POST', '/teams/{team_id}/members', team_id=team_id), json=payload, super_properties_to_track=True
) )
def remove_team_member(self, team_id: Snowflake, user_id: Snowflake): def remove_team_member(self, team_id: Snowflake, user_id: Snowflake) -> Response[None]:
return self.request( return self.request(
Route('DELETE', '/teams/{team_id}/members/{user_id}', team_id=team_id, user_id=user_id), Route('DELETE', '/teams/{team_id}/members/{user_id}', team_id=team_id, user_id=user_id),
super_properties_to_track=True, super_properties_to_track=True,
@ -4380,27 +4388,27 @@ class HTTPClient:
return self.request(Route('PATCH', '/users/@me/settings-proto/{type}', type=type), json=payload) return self.request(Route('PATCH', '/users/@me/settings-proto/{type}', type=type), json=payload)
def get_settings(self): # TODO: return type def get_settings(self):
return self.request(Route('GET', '/users/@me/settings')) return self.request(Route('GET', '/users/@me/settings'))
def edit_settings(self, **payload): # TODO: return type, is this cheating? def edit_settings(self, **payload):
return self.request(Route('PATCH', '/users/@me/settings'), json=payload) return self.request(Route('PATCH', '/users/@me/settings'), json=payload)
def get_tracking(self): # TODO: return type def get_tracking(self) -> Response[user.ConsentSettings]:
return self.request(Route('GET', '/users/@me/consent')) return self.request(Route('GET', '/users/@me/consent'))
def edit_tracking(self, payload): def edit_tracking(self, payload) -> Response[user.ConsentSettings]:
return self.request(Route('POST', '/users/@me/consent'), json=payload) return self.request(Route('POST', '/users/@me/consent'), json=payload)
def get_email_settings(self): def get_email_settings(self) -> Response[user.EmailSettings]:
return self.request(Route('GET', '/users/@me/email-settings')) return self.request(Route('GET', '/users/@me/email-settings'))
def edit_email_settings(self, **payload): def edit_email_settings(self, **payload) -> Response[user.EmailSettings]:
return self.request(Route('PATCH', '/users/@me/email-settings'), json={'settings': payload}) return self.request(Route('PATCH', '/users/@me/email-settings'), json={'settings': payload})
def mobile_report( # Report v1 def mobile_report( # Report v1
self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str
): # TODO: return type ) -> Response[user.Report]:
payload = {'guild_id': guild_id, 'channel_id': channel_id, 'message_id': message_id, 'reason': reason} payload = {'guild_id': guild_id, 'channel_id': channel_id, 'message_id': message_id, 'reason': reason}
return self.request(Route('POST', '/report'), json=payload) return self.request(Route('POST', '/report'), json=payload)
@ -4418,7 +4426,7 @@ class HTTPClient:
command_ids: Optional[List[Snowflake]] = None, command_ids: Optional[List[Snowflake]] = None,
application_id: Optional[Snowflake] = None, application_id: Optional[Snowflake] = None,
include_applications: Optional[bool] = None, include_applications: Optional[bool] = None,
): ) -> Response[command.ApplicationCommandSearch]:
params: Dict[str, Any] = { params: Dict[str, Any] = {
'type': type, 'type': type,
} }
@ -4442,7 +4450,7 @@ class HTTPClient:
def interact( def interact(
self, self,
type: InteractionType, type: InteractionType,
data: dict, data: interactions.InteractionData,
channel: MessageableChannel, channel: MessageableChannel,
message: Optional[Message] = None, message: Optional[Message] = None,
*, *,

3
discord/interactions.py

@ -39,6 +39,7 @@ if TYPE_CHECKING:
from .modal import Modal from .modal import Modal
from .state import ConnectionState from .state import ConnectionState
from .threads import Thread from .threads import Thread
from .types.interactions import InteractionData
from .types.snowflake import Snowflake from .types.snowflake import Snowflake
from .types.user import User as UserPayload from .types.user import User as UserPayload
from .user import BaseUser, ClientUser from .user import BaseUser, ClientUser
@ -193,7 +194,7 @@ async def _wrapped_interaction(
type: InteractionType, type: InteractionType,
name: Optional[str], name: Optional[str],
channel: MessageableChannel, channel: MessageableChannel,
data: dict, data: InteractionData,
**kwargs, **kwargs,
) -> Interaction: ) -> Interaction:
state._interaction_cache[nonce] = (type.value, name, channel) state._interaction_cache[nonce] = (type.value, name, channel)

6
discord/member.py

@ -60,7 +60,7 @@ if TYPE_CHECKING:
from .guild import Guild from .guild import Guild
from .profile import MemberProfile from .profile import MemberProfile
from .types.activity import ( from .types.activity import (
PartialPresenceUpdate, BasePresenceUpdate,
) )
from .types.member import ( from .types.member import (
MemberWithUser as MemberWithUserPayload, MemberWithUser as MemberWithUserPayload,
@ -389,7 +389,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
self._user = member._user self._user = member._user
return self return self
def _update(self, data: GuildMemberUpdateEvent) -> Optional[Member]: def _update(self, data: Union[GuildMemberUpdateEvent, MemberWithUserPayload]) -> Optional[Member]:
old = Member._copy(self) old = Member._copy(self)
# Some changes are optional # Some changes are optional
@ -416,7 +416,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
return old return old
def _presence_update( def _presence_update(
self, data: PartialPresenceUpdate, user: Union[PartialUserPayload, Tuple[()]] self, data: BasePresenceUpdate, user: Union[PartialUserPayload, Tuple[()]]
) -> Optional[Tuple[User, User]]: ) -> Optional[Tuple[User, User]]:
self._presence = self._state.create_presence(data) self._presence = self._state.create_presence(data)
return self._user._update_self(user) return self._user._update_self(user)

9
discord/message.py

@ -87,7 +87,7 @@ if TYPE_CHECKING:
from .types.interactions import MessageInteraction as MessageInteractionPayload from .types.interactions import MessageInteraction as MessageInteractionPayload
from .types.components import Component as ComponentPayload from .types.components import MessageActionRow as ComponentPayload
from .types.threads import ThreadArchiveDuration from .types.threads import ThreadArchiveDuration
from .types.member import ( from .types.member import (
Member as MemberPayload, Member as MemberPayload,
@ -98,7 +98,7 @@ if TYPE_CHECKING:
from .types.gateway import MessageReactionRemoveEvent, MessageUpdateEvent from .types.gateway import MessageReactionRemoveEvent, MessageUpdateEvent
from .abc import Snowflake from .abc import Snowflake
from .abc import GuildChannel, MessageableChannel from .abc import GuildChannel, MessageableChannel
from .components import ActionRow, ActionRowChildComponentType from .components import ActionRow
from .file import _FileBase from .file import _FileBase
from .state import ConnectionState from .state import ConnectionState
from .mentions import AllowedMentions from .mentions import AllowedMentions
@ -107,7 +107,6 @@ if TYPE_CHECKING:
from .role import Role from .role import Role
EmojiInputType = Union[Emoji, PartialEmoji, str] EmojiInputType = Union[Emoji, PartialEmoji, str]
MessageComponentType = Union[ActionRow, ActionRowChildComponentType]
__all__ = ( __all__ = (
@ -1573,7 +1572,7 @@ class Message(PartialMessage, Hashable):
mentions: List[Union[User, Member]] mentions: List[Union[User, Member]]
author: Union[User, Member] author: Union[User, Member]
role_mentions: List[Role] role_mentions: List[Role]
components: List[MessageComponentType] components: List[ActionRow]
def __init__( def __init__(
self, self,
@ -1865,10 +1864,8 @@ class Message(PartialMessage, Hashable):
def _handle_components(self, data: List[ComponentPayload]) -> None: def _handle_components(self, data: List[ComponentPayload]) -> None:
self.components = [] self.components = []
for component_data in data: for component_data in data:
component = _component_factory(component_data, self) component = _component_factory(component_data, self)
if component is not None: if component is not None:
self.components.append(component) self.components.append(component)

11
discord/modal.py

@ -35,6 +35,7 @@ if TYPE_CHECKING:
from .application import IntegrationApplication from .application import IntegrationApplication
from .components import ActionRow from .components import ActionRow
from .interactions import Interaction from .interactions import Interaction
from .types.interactions import Modal as ModalPayload, ModalSubmitInteractionData
# fmt: off # fmt: off
__all__ = ( __all__ = (
@ -69,7 +70,7 @@ class Modal(Hashable):
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`
The modal's ID. This is the same as the interaction ID. The interaction ID.
nonce: Optional[Union[:class:`int`, :class:`str`]] nonce: Optional[Union[:class:`int`, :class:`str`]]
The modal's nonce. May not be present. The modal's nonce. May not be present.
title: :class:`str` title: :class:`str`
@ -84,24 +85,24 @@ class Modal(Hashable):
__slots__ = ('_state', 'interaction', 'id', 'nonce', 'title', 'custom_id', 'components', 'application') __slots__ = ('_state', 'interaction', 'id', 'nonce', 'title', 'custom_id', 'components', 'application')
def __init__(self, *, data: dict, interaction: Interaction): def __init__(self, *, data: ModalPayload, interaction: Interaction):
self._state = interaction._state self._state = interaction._state
self.interaction = interaction self.interaction = interaction
self.id = int(data['id']) self.id = int(data['id'])
self.nonce: Optional[Union[int, str]] = data.get('nonce') self.nonce: Optional[Union[int, str]] = data.get('nonce')
self.title: str = data.get('title', '') self.title: str = data.get('title', '')
self.custom_id: str = data.get('custom_id', '') self.custom_id: str = data.get('custom_id', '')
self.components: List[ActionRow] = [_component_factory(d) for d in data.get('components', [])] # type: ignore # Will always be rows here self.components: List[ActionRow] = [_component_factory(d) for d in data.get('components', [])]
self.application: IntegrationApplication = interaction._state.create_integration_application(data['application']) self.application: IntegrationApplication = interaction._state.create_integration_application(data['application'])
def __str__(self) -> str: def __str__(self) -> str:
return self.title return self.title
def to_dict(self) -> dict: def to_dict(self) -> ModalSubmitInteractionData:
return { return {
'id': str(self.id), 'id': str(self.id),
'custom_id': self.custom_id, 'custom_id': self.custom_id,
'components': [c.to_dict() for c in self.components], 'components': [c.to_dict() for c in self.components], # type: ignore
} }
async def submit(self): async def submit(self):

81
discord/settings.py

@ -25,13 +25,13 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import base64 import base64
from datetime import datetime, timezone
import struct
import logging import logging
import struct
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Collection, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, overload from typing import TYPE_CHECKING, Any, Collection, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, overload
from google.protobuf.json_format import MessageToDict, ParseDict
from discord_protos import PreloadedUserSettings # , FrecencyUserSettings from discord_protos import PreloadedUserSettings # , FrecencyUserSettings
from google.protobuf.json_format import MessageToDict, ParseDict
from .activity import CustomActivity from .activity import CustomActivity
from .colour import Colour from .colour import Colour
@ -41,8 +41,8 @@ from .enums import (
InboxTab, InboxTab,
Locale, Locale,
NotificationLevel, NotificationLevel,
Status,
SpoilerRenderOptions, SpoilerRenderOptions,
Status,
StickerAnimationOptions, StickerAnimationOptions,
StickerPickerSection, StickerPickerSection,
Theme, Theme,
@ -51,7 +51,7 @@ from .enums import (
) )
from .flags import FriendDiscoveryFlags, FriendSourceFlags, HubProgressFlags, OnboardingProgressFlags from .flags import FriendDiscoveryFlags, FriendSourceFlags, HubProgressFlags, OnboardingProgressFlags
from .object import Object from .object import Object
from .utils import MISSING, _get_as_snowflake, _ocast, parse_time, parse_timestamp, utcnow, find from .utils import MISSING, _get_as_snowflake, _ocast, find, parse_time, parse_timestamp, utcnow
if TYPE_CHECKING: if TYPE_CHECKING:
from google.protobuf.message import Message from google.protobuf.message import Message
@ -61,6 +61,14 @@ if TYPE_CHECKING:
from .channel import DMChannel, GroupChannel from .channel import DMChannel, GroupChannel
from .guild import Guild from .guild import Guild
from .state import ConnectionState from .state import ConnectionState
from .types.user import (
ConsentSettings as ConsentSettingsPayload,
EmailSettings as EmailSettingsPayload,
PartialConsentSettings as PartialConsentSettingsPayload,
UserGuildSettings as UserGuildSettingsPayload,
ChannelOverride as ChannelOverridePayload,
MuteConfig as MuteConfigPayload,
)
from .user import ClientUser, User from .user import ClientUser, User
PrivateChannel = Union[DMChannel, GroupChannel] PrivateChannel = Union[DMChannel, GroupChannel]
@ -1954,8 +1962,8 @@ class MuteConfig:
When the mute will expire. When the mute will expire.
""" """
def __init__(self, muted: bool, config: Dict[str, str]) -> None: def __init__(self, muted: bool, config: Optional[MuteConfigPayload] = None) -> None:
until = parse_time(config.get('end_time')) until = parse_time(config.get('end_time') if config else None)
if until is not None: if until is not None:
if until <= utcnow(): if until <= utcnow():
muted = False muted = False
@ -2002,7 +2010,7 @@ class ChannelSettings:
muted: MuteConfig muted: MuteConfig
collapsed: bool collapsed: bool
def __init__(self, guild_id: Optional[int] = None, *, data: Dict[str, Any], state: ConnectionState) -> None: def __init__(self, guild_id: Optional[int] = None, *, data: ChannelOverridePayload, state: ConnectionState) -> None:
self._guild_id = guild_id self._guild_id = guild_id
self._state = state self._state = state
self._update(data) self._update(data)
@ -2010,14 +2018,14 @@ class ChannelSettings:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<ChannelSettings channel={self.channel} level={self.level} muted={self.muted} collapsed={self.collapsed}>' return f'<ChannelSettings channel={self.channel} level={self.level} muted={self.muted} collapsed={self.collapsed}>'
def _update(self, data: Dict[str, Any]) -> None: def _update(self, data: ChannelOverridePayload) -> None:
# We consider everything optional because this class can be constructed with no data # We consider everything optional because this class can be constructed with no data
# to represent the default settings # to represent the default settings
self._channel_id = int(data['channel_id']) self._channel_id = int(data['channel_id'])
self.collapsed = data.get('collapsed', False) self.collapsed = data.get('collapsed', False)
self.level = try_enum(NotificationLevel, data.get('message_notifications', 3)) self.level = try_enum(NotificationLevel, data.get('message_notifications', 3))
self.muted = MuteConfig(data.get('muted', False), data.get('mute_config') or {}) self.muted = MuteConfig(data.get('muted', False), data.get('mute_config'))
@property @property
def channel(self) -> Union[GuildChannel, PrivateChannel]: def channel(self) -> Union[GuildChannel, PrivateChannel]:
@ -2103,7 +2111,7 @@ class ChannelSettings:
override = find(lambda x: x.get('channel_id') == str(channel_id), data['channel_overrides']) or { override = find(lambda x: x.get('channel_id') == str(channel_id), data['channel_overrides']) or {
'channel_id': channel_id 'channel_id': channel_id
} }
return ChannelSettings(guild_id, data=override, state=state) return ChannelSettings(guild_id, data=override, state=state) # type: ignore
class GuildSettings: class GuildSettings:
@ -2146,14 +2154,15 @@ class GuildSettings:
notify_highlights: HighlightLevel notify_highlights: HighlightLevel
version: int version: int
def __init__(self, *, data: Dict[str, Any], state: ConnectionState) -> None: def __init__(self, *, data: UserGuildSettingsPayload, state: ConnectionState) -> None:
self._state = state self._state = state
self.version = -1 # Overriden by real data
self._update(data) self._update(data)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<GuildSettings guild={self.guild!r} level={self.level} muted={self.muted} suppress_everyone={self.suppress_everyone} suppress_roles={self.suppress_roles}>' return f'<GuildSettings guild={self.guild!r} level={self.level} muted={self.muted} suppress_everyone={self.suppress_everyone} suppress_roles={self.suppress_roles}>'
def _update(self, data: Dict[str, Any]) -> None: def _update(self, data: UserGuildSettingsPayload) -> None:
# We consider everything optional because this class can be constructed with no data # We consider everything optional because this class can be constructed with no data
# to represent the default settings # to represent the default settings
self._guild_id = guild_id = _get_as_snowflake(data, 'guild_id') self._guild_id = guild_id = _get_as_snowflake(data, 'guild_id')
@ -2164,9 +2173,9 @@ class GuildSettings:
self.mobile_push = data.get('mobile_push', True) self.mobile_push = data.get('mobile_push', True)
self.mute_scheduled_events = data.get('mute_scheduled_events', False) self.mute_scheduled_events = data.get('mute_scheduled_events', False)
self.notify_highlights = try_enum(HighlightLevel, data.get('notify_highlights', 0)) self.notify_highlights = try_enum(HighlightLevel, data.get('notify_highlights', 0))
self.version = data.get('version', -1) # Overriden by real data self.version = data.get('version', self.version)
self.muted = MuteConfig(data.get('muted', False), data.get('mute_config') or {}) self.muted = MuteConfig(data.get('muted', False), data.get('mute_config'))
self._channel_overrides = overrides = {} self._channel_overrides = overrides = {}
state = self._state state = self._state
for override in data.get('channel_overrides', []): for override in data.get('channel_overrides', []):
@ -2244,9 +2253,7 @@ class GuildSettings:
payload['muted'] = False payload['muted'] = False
else: else:
payload['muted'] = True payload['muted'] = True
if muted_until is True: if muted_until is not True:
payload['mute_config'] = {'selected_time_window': -1, 'end_time': None}
else:
if muted_until.tzinfo is None: if muted_until.tzinfo is None:
raise TypeError( raise TypeError(
'muted_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' 'muted_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.'
@ -2257,25 +2264,18 @@ class GuildSettings:
'end_time': muted_until.isoformat(), 'end_time': muted_until.isoformat(),
} }
payload['mute_config'] = mute_config payload['mute_config'] = mute_config
if level is not MISSING: if level is not MISSING:
payload['message_notifications'] = level.value payload['message_notifications'] = level.value
if suppress_everyone is not MISSING: if suppress_everyone is not MISSING:
payload['suppress_everyone'] = suppress_everyone payload['suppress_everyone'] = suppress_everyone
if suppress_roles is not MISSING: if suppress_roles is not MISSING:
payload['suppress_roles'] = suppress_roles payload['suppress_roles'] = suppress_roles
if mobile_push is not MISSING: if mobile_push is not MISSING:
payload['mobile_push'] = mobile_push payload['mobile_push'] = mobile_push
if hide_muted_channels is not MISSING: if hide_muted_channels is not MISSING:
payload['hide_muted_channels'] = hide_muted_channels payload['hide_muted_channels'] = hide_muted_channels
if mute_scheduled_events is not MISSING: if mute_scheduled_events is not MISSING:
payload['mute_scheduled_events'] = mute_scheduled_events payload['mute_scheduled_events'] = mute_scheduled_events
if notify_highlights is not MISSING: if notify_highlights is not MISSING:
payload['notify_highlights'] = notify_highlights.value payload['notify_highlights'] = notify_highlights.value
@ -2304,7 +2304,9 @@ class TrackingSettings:
__slots__ = ('_state', 'personalization', 'usage_statistics') __slots__ = ('_state', 'personalization', 'usage_statistics')
def __init__(self, *, data: Dict[str, Dict[str, bool]], state: ConnectionState) -> None: def __init__(
self, *, data: Union[PartialConsentSettingsPayload, ConsentSettingsPayload], state: ConnectionState
) -> None:
self._state = state self._state = state
self._update(data) self._update(data)
@ -2312,9 +2314,9 @@ class TrackingSettings:
return f'<TrackingSettings personalization={self.personalization} usage_statistics={self.usage_statistics}>' return f'<TrackingSettings personalization={self.personalization} usage_statistics={self.usage_statistics}>'
def __bool__(self) -> bool: def __bool__(self) -> bool:
return any({self.personalization, self.usage_statistics}) return any((self.personalization, self.usage_statistics))
def _update(self, data: Dict[str, Dict[str, bool]]): def _update(self, data: Union[PartialConsentSettingsPayload, ConsentSettingsPayload]):
self.personalization = data.get('personalization', {}).get('consented', False) self.personalization = data.get('personalization', {}).get('consented', False)
self.usage_statistics = data.get('usage_statistics', {}).get('consented', False) self.usage_statistics = data.get('usage_statistics', {}).get('consented', False)
@ -2387,22 +2389,22 @@ class EmailSettings:
'family_center_digest', 'family_center_digest',
) )
def __init__(self, *, data: dict, state: ConnectionState): def __init__(self, *, data: EmailSettingsPayload, state: ConnectionState):
self._state = state self._state = state
self._update(data) self._update(data)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<EmailSettings initialized={self.initialized}>' return f'<EmailSettings initialized={self.initialized}>'
def _update(self, data: dict): def _update(self, data: EmailSettingsPayload):
self.initialized = data.get('initialized', False) self.initialized: bool = data.get('initialized', False)
categories = data.get('categories', {}) categories = data.get('categories', {})
self.communication = categories.get('communication', False) self.communication: bool = categories.get('communication', False)
self.social = categories.get('social', False) self.social: bool = categories.get('social', False)
self.recommendations_and_events = categories.get('recommendations_and_events', False) self.recommendations_and_events: bool = categories.get('recommendations_and_events', False)
self.tips = categories.get('tips', False) self.tips: bool = categories.get('tips', False)
self.updates_and_announcements = categories.get('updates_and_announcements', False) self.updates_and_announcements: bool = categories.get('updates_and_announcements', False)
self.family_center_digest = categories.get('family_center_digest', False) self.family_center_digest: bool = categories.get('family_center_digest', False)
@overload @overload
async def edit(self) -> None: async def edit(self) -> None:
@ -2412,6 +2414,7 @@ class EmailSettings:
async def edit( async def edit(
self, self,
*, *,
initialized: bool = MISSING,
communication: bool = MISSING, communication: bool = MISSING,
social: bool = MISSING, social: bool = MISSING,
recommendations_and_events: bool = MISSING, recommendations_and_events: bool = MISSING,
@ -2449,8 +2452,8 @@ class EmailSettings:
# It seems that initialized is settable, but it doesn't do anything # It seems that initialized is settable, but it doesn't do anything
# So we support just in case but leave it undocumented # So we support just in case but leave it undocumented
initialized = kwargs.pop('initialized', None) initialized = kwargs.pop('initialized', MISSING)
if initialized is not None: if initialized is not MISSING:
payload['initialized'] = initialized payload['initialized'] = initialized
if kwargs: if kwargs:
payload['categories'] = kwargs payload['categories'] = kwargs

90
discord/state.py

@ -128,7 +128,7 @@ if TYPE_CHECKING:
PartialMessage as PartialMessagePayload, PartialMessage as PartialMessagePayload,
) )
from .types import gateway as gw from .types import gateway as gw
from .types.voice import GuildVoiceState from .types.voice import VoiceState as VoiceStatePayload
from .types.activity import ClientStatus as ClientStatusPayload from .types.activity import ClientStatus as ClientStatusPayload
T = TypeVar('T') T = TypeVar('T')
@ -496,7 +496,7 @@ class ClientStatus:
class Presence: class Presence:
__slots__ = ('client_status', 'activities', 'last_modified') __slots__ = ('client_status', 'activities', 'last_modified')
def __init__(self, data: gw.PresenceUpdateEvent, state: ConnectionState, /) -> None: def __init__(self, data: gw.BasePresenceUpdate, state: ConnectionState, /) -> None:
self.client_status: ClientStatus = ClientStatus(data['status'], data.get('client_status')) self.client_status: ClientStatus = ClientStatus(data['status'], data.get('client_status'))
self.activities: Tuple[ActivityTypes, ...] = tuple(create_activity(d, state) for d in data['activities']) self.activities: Tuple[ActivityTypes, ...] = tuple(create_activity(d, state) for d in data['activities'])
self.last_modified: Optional[datetime.datetime] = utils.parse_timestamp(data.get('last_modified')) self.last_modified: Optional[datetime.datetime] = utils.parse_timestamp(data.get('last_modified'))
@ -520,7 +520,7 @@ class Presence:
return True return True
return self.client_status != other.client_status or self.activities != other.activities return self.client_status != other.client_status or self.activities != other.activities
def _update(self, data: gw.PresenceUpdateEvent, state: ConnectionState, /) -> None: def _update(self, data: gw.BasePresenceUpdate, state: ConnectionState, /) -> None:
self.client_status._update(data['status'], data.get('client_status')) self.client_status._update(data['status'], data.get('client_status'))
self.activities = tuple(create_activity(d, state) for d in data['activities']) self.activities = tuple(create_activity(d, state) for d in data['activities'])
self.last_modified = utils.parse_timestamp(data.get('last_modified')) or utils.utcnow() self.last_modified = utils.parse_timestamp(data.get('last_modified')) or utils.utcnow()
@ -653,7 +653,6 @@ class ConnectionState:
self.user: Optional[ClientUser] = None self.user: Optional[ClientUser] = None
self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary() self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary()
self.settings: Optional[UserSettings] = None self.settings: Optional[UserSettings] = None
self.guild_settings: Dict[Optional[int], GuildSettings] = {}
self.consents: Optional[TrackingSettings] = None self.consents: Optional[TrackingSettings] = None
self.connections: Dict[str, Connection] = {} self.connections: Dict[str, Connection] = {}
self.pending_payments: Dict[int, Payment] = {} self.pending_payments: Dict[int, Payment] = {}
@ -674,6 +673,9 @@ class ConnectionState:
self._read_states: Dict[int, Dict[int, ReadState]] = {} self._read_states: Dict[int, Dict[int, ReadState]] = {}
self.read_state_version: int = 0 self.read_state_version: int = 0
self.guild_settings: Dict[Optional[int], GuildSettings] = {}
self.guild_settings_version: int = 0
self._calls: Dict[int, Call] = {} self._calls: Dict[int, Call] = {}
self._call_message_cache: Dict[int, Message] = {} # Hopefully this won't be a memory leak self._call_message_cache: Dict[int, Message] = {} # Hopefully this won't be a memory leak
self._voice_clients: Dict[int, VoiceProtocol] = {} self._voice_clients: Dict[int, VoiceProtocol] = {}
@ -756,7 +758,7 @@ class ConnectionState:
return list(self._voice_clients.values()) return list(self._voice_clients.values())
def _update_voice_state( def _update_voice_state(
self, data: GuildVoiceState, channel_id: Optional[int] self, data: VoiceStatePayload, channel_id: Optional[int]
) -> Tuple[Optional[User], VoiceState, VoiceState]: ) -> Tuple[Optional[User], VoiceState, VoiceState]:
user_id = int(data['user_id']) user_id = int(data['user_id'])
user = self.get_user(user_id) user = self.get_user(user_id)
@ -1079,6 +1081,14 @@ class ConnectionState:
self.store_read_state(item) self.store_read_state(item)
self.read_state_version = read_states.get('version', 0) self.read_state_version = read_states.get('version', 0)
# Guild settings parsing
guild_settings = data.get('user_guild_settings', {})
self.guild_settings = {
utils._get_as_snowflake(entry, 'guild_id'): GuildSettings(data=entry, state=self)
for entry in guild_settings.get('entries', [])
}
self.guild_settings_version = guild_settings.get('version', 0)
# Experiments # Experiments
self.experiments = {exp[0]: UserExperiment(state=self, data=exp) for exp in data.get('experiments', [])} self.experiments = {exp[0]: UserExperiment(state=self, data=exp) for exp in data.get('experiments', [])}
self.guild_experiments = {exp[0]: GuildExperiment(state=self, data=exp) for exp in data.get('guild_experiments', [])} self.guild_experiments = {exp[0]: GuildExperiment(state=self, data=exp) for exp in data.get('guild_experiments', [])}
@ -1087,10 +1097,6 @@ class ConnectionState:
self.analytics_token = data.get('analytics_token') self.analytics_token = data.get('analytics_token')
self.preferred_rtc_regions = data.get('geo_ordered_rtc_regions', ['us-central']) self.preferred_rtc_regions = data.get('geo_ordered_rtc_regions', ['us-central'])
self.settings = UserSettings(self, data.get('user_settings_proto', '')) self.settings = UserSettings(self, data.get('user_settings_proto', ''))
self.guild_settings = {
utils._get_as_snowflake(entry, 'guild_id'): GuildSettings(data=entry, state=self)
for entry in data.get('user_guild_settings', {}).get('entries', [])
}
self.consents = TrackingSettings(data=data.get('consents', {}), state=self) self.consents = TrackingSettings(data=data.get('consents', {}), state=self)
self.country_code = data.get('country_code', 'US') self.country_code = data.get('country_code', 'US')
self.api_code_version = data.get('api_code_version', 1) self.api_code_version = data.get('api_code_version', 1)
@ -1479,7 +1485,7 @@ class ConnectionState:
else: else:
_log.warning('Unknown user settings proto type: %s', type.value) _log.warning('Unknown user settings proto type: %s', type.value)
def parse_user_guild_settings_update(self, data) -> None: def parse_user_guild_settings_update(self, data: gw.UserGuildSettingsEvent) -> None:
guild_id = utils._get_as_snowflake(data, 'guild_id') guild_id = utils._get_as_snowflake(data, 'guild_id')
settings = self.guild_settings.get(guild_id) settings = self.guild_settings.get(guild_id)
@ -1489,6 +1495,7 @@ class ConnectionState:
else: else:
old_settings = None old_settings = None
settings = GuildSettings(data=data, state=self) settings = GuildSettings(data=data, state=self)
self.guild_settings_version = data.get('version', self.guild_settings_version)
self.dispatch('guild_settings_update', old_settings, settings) self.dispatch('guild_settings_update', old_settings, settings)
def parse_user_required_action_update(self, data: gw.RequiredActionEvent) -> None: def parse_user_required_action_update(self, data: gw.RequiredActionEvent) -> None:
@ -1779,14 +1786,22 @@ class ConnectionState:
else: else:
self.dispatch('guild_channel_pins_ack', channel, last_pin) self.dispatch('guild_channel_pins_ack', channel, last_pin)
def parse_channel_recipient_add(self, data) -> None: def parse_channel_recipient_add(self, data: gw.ChannelRecipientEvent) -> None:
channel = self._get_private_channel(int(data['channel_id'])) channel = self._get_private_channel(int(data['channel_id']))
if channel is None:
_log.debug('CHANNEL_RECIPIENT_ADD referencing an unknown channel ID: %s. Discarding.', data['channel_id'])
return
user = self.store_user(data['user']) user = self.store_user(data['user'])
channel.recipients.append(user) # type: ignore channel.recipients.append(user) # type: ignore
self.dispatch('group_join', channel, user) self.dispatch('group_join', channel, user)
def parse_channel_recipient_remove(self, data) -> None: def parse_channel_recipient_remove(self, data: gw.ChannelRecipientEvent) -> None:
channel = self._get_private_channel(int(data['channel_id'])) channel = self._get_private_channel(int(data['channel_id']))
if channel is None:
_log.debug('CHANNEL_RECIPIENT_REMOVE referencing an unknown channel ID: %s. Discarding.', data['channel_id'])
return
user = self.store_user(data['user']) user = self.store_user(data['user'])
try: try:
channel.recipients.remove(user) # type: ignore channel.recipients.remove(user) # type: ignore
@ -2011,7 +2026,7 @@ class ConnectionState:
'I noticed you triggered a `GUILD_SYNC`.\nIf you want to share your secrets, please feel free to open an issue.' 'I noticed you triggered a `GUILD_SYNC`.\nIf you want to share your secrets, please feel free to open an issue.'
) )
def parse_guild_member_list_update(self, data) -> None: def parse_guild_member_list_update(self, data: gw.GuildMemberListUpdateEvent) -> None:
self.dispatch('raw_member_list_update', data) self.dispatch('raw_member_list_update', data)
guild = self._get_guild(int(data['guild_id'])) guild = self._get_guild(int(data['guild_id']))
if guild is None: if guild is None:
@ -2042,7 +2057,6 @@ class ConnectionState:
ops = data['ops'] ops = data['ops']
for opdata in ops: for opdata in ops:
op = opdata['op']
# The OPs are as follows: # The OPs are as follows:
# SYNC: Provides member/presence data for a 100 member range of the member list # SYNC: Provides member/presence data for a 100 member range of the member list
# UPDATE: Dispatched when a member is updated and stays in the same range # UPDATE: Dispatched when a member is updated and stays in the same range
@ -2050,7 +2064,7 @@ class ConnectionState:
# DELETE: Dispatched when a member is removed from a range # DELETE: Dispatched when a member is removed from a range
# INVALIDATE: Sent when you're unsubscribed from a range # INVALIDATE: Sent when you're unsubscribed from a range
if op == 'SYNC': if opdata['op'] == 'SYNC':
for item in opdata['items']: for item in opdata['items']:
if 'group' in item: # Hoisted role if 'group' in item: # Hoisted role
guild._member_list.append(None) if should_parse else None # Insert blank so indexes don't fuck up guild._member_list.append(None) if should_parse else None # Insert blank so indexes don't fuck up
@ -2064,7 +2078,7 @@ class ConnectionState:
members.append(member) members.append(member)
guild._member_list.append(member) if should_parse else None guild._member_list.append(member) if should_parse else None
elif op == 'INSERT': elif opdata['op'] == 'INSERT':
index = opdata['index'] index = opdata['index']
item = opdata['item'] item = opdata['item']
if 'group' in item: # Hoisted role if 'group' in item: # Hoisted role
@ -2111,7 +2125,7 @@ class ConnectionState:
guild._member_list.insert(index, member) if should_parse else None guild._member_list.insert(index, member) if should_parse else None
elif op == 'UPDATE' and should_parse: elif opdata['op'] == 'UPDATE' and should_parse:
item = opdata['item'] item = opdata['item']
if 'group' in item: # Hoisted role if 'group' in item: # Hoisted role
continue continue
@ -2159,7 +2173,7 @@ class ConnectionState:
guild._member_list.insert(opdata['index'], member) # Race condition? guild._member_list.insert(opdata['index'], member) # Race condition?
elif op == 'DELETE' and should_parse: elif opdata['op'] == 'DELETE' and should_parse:
index = opdata['index'] index = opdata['index']
try: try:
item = guild._member_list.pop(index) item = guild._member_list.pop(index)
@ -2213,7 +2227,6 @@ class ConnectionState:
before_emojis = guild.emojis before_emojis = guild.emojis
for emoji in before_emojis: for emoji in before_emojis:
self._emojis.pop(emoji.id, None) self._emojis.pop(emoji.id, None)
# guild won't be None here
guild.emojis = tuple(map(lambda d: self.store_emoji(guild, d), data['emojis'])) guild.emojis = tuple(map(lambda d: self.store_emoji(guild, d), data['emojis']))
self.dispatch('guild_emojis_update', guild, before_emojis, guild.emojis) self.dispatch('guild_emojis_update', guild, before_emojis, guild.emojis)
@ -2226,7 +2239,6 @@ class ConnectionState:
before_stickers = guild.stickers before_stickers = guild.stickers
for emoji in before_stickers: for emoji in before_stickers:
self._stickers.pop(emoji.id, None) self._stickers.pop(emoji.id, None)
guild.stickers = tuple(map(lambda d: self.store_sticker(guild, d), data['stickers'])) guild.stickers = tuple(map(lambda d: self.store_sticker(guild, d), data['stickers']))
self.dispatch('guild_stickers_update', guild, before_stickers, guild.stickers) self.dispatch('guild_stickers_update', guild, before_stickers, guild.stickers)
@ -2243,7 +2255,6 @@ class ConnectionState:
data=data, data=data,
guild=guild, guild=guild,
) )
self.dispatch('audit_log_entry_create', entry) self.dispatch('audit_log_entry_create', entry)
def parse_auto_moderation_rule_create(self, data: AutoModerationRule) -> None: def parse_auto_moderation_rule_create(self, data: AutoModerationRule) -> None:
@ -2753,17 +2764,26 @@ class ConnectionState:
else: else:
_log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id']) _log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
def parse_call_create(self, data) -> None: def parse_call_create(self, data: gw.CallCreateEvent) -> None:
channel = self._get_private_channel(int(data['channel_id'])) channel_id = int(data['channel_id'])
channel = self._get_private_channel(channel_id)
if channel is None: if channel is None:
_log.debug('CALL_CREATE referencing unknown channel ID: %s. Discarding.', data['channel_id']) _log.debug('CALL_CREATE referencing unknown channel ID: %s. Discarding.', data['channel_id'])
return return
call = self._calls.get(channel_id)
if call is not None:
# Should only happen for unavailable calls
old_call = copy.copy(call)
call._update(data)
self.dispatch('call_update', old_call, call)
message = self._get_message(int(data['message_id'])) message = self._get_message(int(data['message_id']))
call = channel._add_call(data=data, state=self, message=message, channel=channel) call = channel._add_call(data=data, state=self, message=message, channel=channel)
self._calls[channel.id] = call self._calls[channel.id] = call
self.dispatch('call_create', call) self.dispatch('call_create', call)
def parse_call_update(self, data) -> None: def parse_call_update(self, data: gw.CallUpdateEvent) -> None:
call = self._calls.get(int(data['channel_id'])) call = self._calls.get(int(data['channel_id']))
if call is None: if call is None:
_log.debug('CALL_UPDATE referencing unknown call (channel ID: %s). Discarding.', data['channel_id']) _log.debug('CALL_UPDATE referencing unknown call (channel ID: %s). Discarding.', data['channel_id'])
@ -2772,9 +2792,15 @@ class ConnectionState:
call._update(data) call._update(data)
self.dispatch('call_update', old_call, call) self.dispatch('call_update', old_call, call)
def parse_call_delete(self, data) -> None: def parse_call_delete(self, data: gw.CallDeleteEvent) -> None:
call = self._calls.pop(int(data['channel_id']), None) call = self._calls.pop(int(data['channel_id']), None)
if call is not None: if call is not None:
if data.get('unavailable'):
old_call = copy.copy(call)
call.unavailable = True
self.dispatch('call_update', old_call, call)
return
call._delete() call._delete()
self._call_message_cache.pop(call._message_id, None) self._call_message_cache.pop(call._message_id, None)
self.dispatch('call_delete', call) self.dispatch('call_delete', call)
@ -2884,7 +2910,7 @@ class ConnectionState:
self.dispatch('friend_suggestion_remove', user) self.dispatch('friend_suggestion_remove', user)
self.dispatch('raw_friend_suggestion_remove', user_id) self.dispatch('raw_friend_suggestion_remove', user_id)
def parse_interaction_create(self, data) -> None: def parse_interaction_create(self, data: gw.InteractionEvent) -> None:
if 'nonce' not in data: # Sometimes interactions seem to be missing the nonce if 'nonce' not in data: # Sometimes interactions seem to be missing the nonce
return return
@ -2893,7 +2919,7 @@ class ConnectionState:
self._interactions[i.id] = i self._interactions[i.id] = i
self.dispatch('interaction', i) self.dispatch('interaction', i)
def parse_interaction_success(self, data) -> None: def parse_interaction_success(self, data: gw.InteractionEvent) -> None:
id = int(data['id']) id = int(data['id'])
i = self._interactions.get(id, None) i = self._interactions.get(id, None)
if i is None: if i is None:
@ -2903,7 +2929,7 @@ class ConnectionState:
i.successful = True i.successful = True
self.dispatch('interaction_finish', i) self.dispatch('interaction_finish', i)
def parse_interaction_failed(self, data) -> None: def parse_interaction_failed(self, data: gw.InteractionEvent) -> None:
id = int(data['id']) id = int(data['id'])
i = self._interactions.pop(id, None) i = self._interactions.pop(id, None)
if i is None: if i is None:
@ -2913,7 +2939,7 @@ class ConnectionState:
i.successful = False i.successful = False
self.dispatch('interaction_finish', i) self.dispatch('interaction_finish', i)
def parse_interaction_modal_create(self, data) -> None: def parse_interaction_modal_create(self, data: gw.InteractionModalCreateEvent) -> None:
id = int(data['id']) id = int(data['id'])
interaction = self._interactions.pop(id, None) interaction = self._interactions.pop(id, None)
if interaction is not None: if interaction is not None:
@ -3000,10 +3026,10 @@ class ConnectionState:
return IntegrationApplication(state=self, data=data) return IntegrationApplication(state=self, data=data)
def default_guild_settings(self, guild_id: Optional[int]) -> GuildSettings: def default_guild_settings(self, guild_id: Optional[int]) -> GuildSettings:
return GuildSettings(data={'guild_id': guild_id}, state=self) return GuildSettings(data={'guild_id': guild_id}, state=self) # type: ignore
def default_channel_settings(self, guild_id: Optional[int], channel_id: int) -> ChannelSettings: def default_channel_settings(self, guild_id: Optional[int], channel_id: int) -> ChannelSettings:
return ChannelSettings(guild_id, data={'channel_id': channel_id}, state=self) return ChannelSettings(guild_id, data={'channel_id': channel_id}, state=self) # type: ignore
def create_implicit_relationship(self, user: User) -> Relationship: def create_implicit_relationship(self, user: User) -> Relationship:
relationship = self._relationships.get(user.id) relationship = self._relationships.get(user.id)
@ -3027,7 +3053,7 @@ class ConnectionState:
def client_presence(self) -> FakeClientPresence: def client_presence(self) -> FakeClientPresence:
return FakeClientPresence(self) return FakeClientPresence(self)
def create_presence(self, data: gw.PresenceUpdateEvent) -> Presence: def create_presence(self, data: gw.BasePresenceUpdate) -> Presence:
return Presence(data, self) return Presence(data, self)
def create_offline_presence(self) -> Presence: def create_offline_presence(self) -> Presence:

7
discord/types/activity.py

@ -33,14 +33,17 @@ from .snowflake import Snowflake
StatusType = Literal['idle', 'dnd', 'online', 'offline'] StatusType = Literal['idle', 'dnd', 'online', 'offline']
class PartialPresenceUpdate(TypedDict): class BasePresenceUpdate(TypedDict):
user: PartialUser user: PartialUser
guild_id: Optional[Snowflake]
status: StatusType status: StatusType
activities: List[Activity] activities: List[Activity]
client_status: ClientStatus client_status: ClientStatus
class PartialPresenceUpdate(BasePresenceUpdate):
guild_id: NotRequired[Snowflake]
class ClientStatus(TypedDict, total=False): class ClientStatus(TypedDict, total=False):
desktop: StatusType desktop: StatusType
mobile: StatusType mobile: StatusType

4
discord/types/channel.py

@ -205,3 +205,7 @@ class InviteStageInstance(TypedDict):
participant_count: int participant_count: int
speaker_count: int speaker_count: int
topic: str topic: str
class CallEligibility(TypedDict):
ringable: bool

40
discord/types/command.py

@ -24,9 +24,10 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import List, Literal, TypedDict, Union from typing import List, Literal, Optional, TypedDict, Union
from typing_extensions import NotRequired, Required from typing_extensions import NotRequired, Required
from .application import IntegrationApplication
from .channel import ChannelType from .channel import ChannelType
from .snowflake import Snowflake from .snowflake import Snowflake
@ -109,6 +110,8 @@ class _NumberApplicationCommandOption(_BaseValueApplicationCommandOption, total=
autocomplete: bool autocomplete: bool
SubCommand = Union[_SubCommandCommandOption, _SubCommandGroupCommandOption]
_ValueApplicationCommandOption = Union[ _ValueApplicationCommandOption = Union[
_StringApplicationCommandOption, _StringApplicationCommandOption,
_IntegerApplicationCommandOption, _IntegerApplicationCommandOption,
@ -137,13 +140,10 @@ class _BaseApplicationCommand(TypedDict):
version: Snowflake version: Snowflake
class _ChatInputApplicationCommand(_BaseApplicationCommand, total=False): class _ChatInputApplicationCommand(_BaseApplicationCommand):
description: Required[str] description: str
type: Literal[1] type: Literal[1]
options: Union[ options: NotRequired[List[ApplicationCommandOption]]
List[_ValueApplicationCommandOption],
List[Union[_SubCommandCommandOption, _SubCommandGroupCommandOption]],
]
class _BaseContextMenuApplicationCommand(_BaseApplicationCommand): class _BaseContextMenuApplicationCommand(_BaseApplicationCommand):
@ -177,13 +177,23 @@ class _GuildMessageApplicationCommand(_MessageApplicationCommand):
guild_id: Snowflake guild_id: Snowflake
ChatInputCommand = Union[
_ChatInputApplicationCommand,
_GuildChatInputApplicationCommand,
]
UserCommand = Union[
_UserApplicationCommand,
_GuildUserApplicationCommand,
]
MessageCommand = Union[
_MessageApplicationCommand,
_GuildMessageApplicationCommand,
]
GuildApplicationCommand = Union[ GuildApplicationCommand = Union[
_GuildChatInputApplicationCommand, _GuildChatInputApplicationCommand,
_GuildUserApplicationCommand, _GuildUserApplicationCommand,
_GuildMessageApplicationCommand, _GuildMessageApplicationCommand,
] ]
ApplicationCommand = Union[ ApplicationCommand = Union[
GlobalApplicationCommand, GlobalApplicationCommand,
GuildApplicationCommand, GuildApplicationCommand,
@ -204,3 +214,15 @@ class GuildApplicationCommandPermissions(TypedDict):
application_id: Snowflake application_id: Snowflake
guild_id: Snowflake guild_id: Snowflake
permissions: List[ApplicationCommandPermissions] permissions: List[ApplicationCommandPermissions]
class ApplicationCommandCursor(TypedDict):
next: Optional[str]
previous: Optional[str]
repaired: Optional[str]
class ApplicationCommandSearch(TypedDict):
application_commands: List[ApplicationCommand]
applications: Optional[List[IntegrationApplication]]
cursor: ApplicationCommandCursor

16
discord/types/components.py

@ -34,9 +34,17 @@ ButtonStyle = Literal[1, 2, 3, 4, 5]
TextStyle = Literal[1, 2] TextStyle = Literal[1, 2]
class ActionRow(TypedDict): class MessageActionRow(TypedDict):
type: Literal[1] type: Literal[1]
components: List[ActionRowChildComponent] components: List[MessageChildComponent]
class ModalActionRow(TypedDict):
type: Literal[1]
components: List[ModalChildComponent]
ActionRow = Union[MessageActionRow, ModalActionRow]
class ButtonComponent(TypedDict): class ButtonComponent(TypedDict):
@ -79,5 +87,7 @@ class TextInput(TypedDict):
max_length: NotRequired[int] max_length: NotRequired[int]
ActionRowChildComponent = Union[ButtonComponent, SelectMenu, TextInput] MessageChildComponent = Union[ButtonComponent, SelectMenu]
ModalChildComponent = TextInput
ActionRowChildComponent = Union[MessageChildComponent, ModalChildComponent]
Component = Union[ActionRow, ActionRowChildComponent] Component = Union[ActionRow, ActionRowChildComponent]

153
discord/types/gateway.py

@ -24,11 +24,10 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import List, Literal, Optional, TypedDict, Union from typing import Generic, List, Literal, Optional, TypedDict, TypeVar, Union
from typing_extensions import NotRequired, Required from typing_extensions import NotRequired, Required
from .activity import Activity, ClientStatus, PartialPresenceUpdate, StatusType from .activity import Activity, BasePresenceUpdate, PartialPresenceUpdate, StatusType
from .application import BaseAchievement from .application import BaseAchievement
from .audit_log import AuditLogEntry from .audit_log import AuditLogEntry
from .automod import AutoModerationAction, AutoModerationRuleTriggerType from .automod import AutoModerationAction, AutoModerationRuleTriggerType
@ -38,10 +37,10 @@ from .entitlements import Entitlement, GatewayGift
from .experiment import GuildExperiment, UserExperiment from .experiment import GuildExperiment, UserExperiment
from .guild import ApplicationCommandCounts, Guild, SupplementalGuild, UnavailableGuild from .guild import ApplicationCommandCounts, Guild, SupplementalGuild, UnavailableGuild
from .integration import BaseIntegration, IntegrationApplication from .integration import BaseIntegration, IntegrationApplication
from .interactions import Interaction from .interactions import Modal
from .invite import _InviteTargetType from .invite import _InviteTargetType
from .library import LibraryApplication from .library import LibraryApplication
from .member import MemberWithUser from .member import MemberWithPresence, MemberWithUser
from .message import Message from .message import Message
from .payments import Payment from .payments import Payment
from .read_state import ReadState, ReadStateType from .read_state import ReadState, ReadStateType
@ -51,15 +50,23 @@ from .snowflake import Snowflake
from .sticker import GuildSticker from .sticker import GuildSticker
from .subscriptions import PremiumGuildSubscriptionSlot from .subscriptions import PremiumGuildSubscriptionSlot
from .threads import Thread, ThreadMember from .threads import Thread, ThreadMember
from .user import Connection, FriendSuggestion, PartialUser, ProtoSettingsType, Relationship, RelationshipType, User from .user import (
from .voice import GuildVoiceState Connection,
FriendSuggestion,
PartialConsentSettings,
class UserPresenceUpdateEvent(TypedDict): PartialUser,
user: PartialUser ProtoSettingsType,
status: StatusType Relationship,
activities: List[Activity] RelationshipType,
client_status: ClientStatus User,
UserGuildSettings,
)
from .voice import GuildVoiceState, VoiceState
T = TypeVar('T')
class UserPresenceUpdateEvent(BasePresenceUpdate):
last_modified: int last_modified: int
@ -86,6 +93,7 @@ class ReadyEvent(ResumedEvent):
auth_session_id_hash: str auth_session_id_hash: str
auth_token: NotRequired[str] auth_token: NotRequired[str]
connected_accounts: List[Connection] connected_accounts: List[Connection]
consents: PartialConsentSettings
country_code: str country_code: str
experiments: List[UserExperiment] experiments: List[UserExperiment]
friend_suggestion_count: int friend_suggestion_count: int
@ -95,17 +103,17 @@ class ReadyEvent(ResumedEvent):
merged_members: List[List[MemberWithUser]] merged_members: List[List[MemberWithUser]]
pending_payments: NotRequired[List[Payment]] pending_payments: NotRequired[List[Payment]]
private_channels: List[Union[DMChannel, GroupDMChannel]] private_channels: List[Union[DMChannel, GroupDMChannel]]
read_state: VersionedReadState read_state: Versioned[ReadState]
relationships: List[Relationship] relationships: List[Relationship]
resume_gateway_url: str resume_gateway_url: str
required_action: NotRequired[str] required_action: NotRequired[str]
sessions: List[Session] sessions: List[Session]
session_id: str session_id: str
session_type: str session_type: Literal['normal']
shard: NotRequired[ShardInfo] shard: NotRequired[ShardInfo]
tutorial: Optional[Tutorial] tutorial: Optional[Tutorial]
user: User user: User
user_guild_settings: dict user_guild_settings: Versioned[UserGuildSettings]
user_settings_proto: NotRequired[str] user_settings_proto: NotRequired[str]
users: List[PartialUser] users: List[PartialUser]
v: int v: int
@ -138,8 +146,8 @@ class ReadySupplementalEvent(TypedDict):
disclose: List[str] disclose: List[str]
class VersionedReadState(TypedDict): class Versioned(TypedDict, Generic[T]):
entries: List[ReadState] entries: List[T]
version: int version: int
partial: bool partial: bool
@ -205,9 +213,6 @@ class MessageReactionRemoveEmojiEvent(TypedDict):
guild_id: NotRequired[Snowflake] guild_id: NotRequired[Snowflake]
InteractionCreateEvent = Interaction
UserUpdateEvent = User UserUpdateEvent = User
@ -240,6 +245,11 @@ class _ChannelEvent(TypedDict):
ChannelCreateEvent = ChannelUpdateEvent = ChannelDeleteEvent = _ChannelEvent ChannelCreateEvent = ChannelUpdateEvent = ChannelDeleteEvent = _ChannelEvent
class ChannelRecipientEvent(TypedDict):
channel_id: Snowflake
user: PartialUser
class ChannelPinsUpdateEvent(TypedDict): class ChannelPinsUpdateEvent(TypedDict):
channel_id: Snowflake channel_id: Snowflake
guild_id: NotRequired[Snowflake] guild_id: NotRequired[Snowflake]
@ -561,3 +571,102 @@ class GuildApplicationCommandIndexUpdateEvent(TypedDict):
class UserNoteUpdateEvent(TypedDict): class UserNoteUpdateEvent(TypedDict):
id: Snowflake id: Snowflake
note: str note: str
UserGuildSettingsEvent = UserGuildSettings
class InteractionEvent(TypedDict):
id: Snowflake
nonce: NotRequired[Snowflake]
InteractionModalCreateEvent = Modal
class CallCreateEvent(TypedDict):
channel_id: Snowflake
message_id: Snowflake
embedded_activities: List[dict]
region: str
ringing: List[Snowflake]
voice_states: List[VoiceState]
unavailable: NotRequired[bool]
class CallUpdateEvent(TypedDict):
channel_id: Snowflake
guild_id: Optional[Snowflake] # ???
message_id: Snowflake
region: str
ringing: List[Snowflake]
class CallDeleteEvent(TypedDict):
channel_id: Snowflake
unavailable: NotRequired[bool]
class _GuildMemberListGroup(TypedDict):
id: Union[Snowflake, Literal['online', 'offline']]
class GuildMemberListGroup(_GuildMemberListGroup):
count: int
class _GuildMemberListGroupItem(TypedDict):
group: _GuildMemberListGroup
class _GuildMemberListMemberItem(TypedDict):
member: MemberWithPresence
GuildMemberListItem = Union[_GuildMemberListGroupItem, _GuildMemberListMemberItem]
class GuildMemberListSyncOP(TypedDict):
op: Literal['SYNC']
range: tuple[int, int]
items: List[GuildMemberListItem]
class GuildMemberListUpdateOP(TypedDict):
op: Literal['UPDATE']
index: int
item: _GuildMemberListMemberItem
class GuildMemberListInsertOP(TypedDict):
op: Literal['INSERT']
index: int
item: _GuildMemberListMemberItem
class GuildMemberListDeleteOP(TypedDict):
op: Literal['DELETE']
index: int
class GuildMemberListInvalidateOP(TypedDict):
op: Literal['INVALIDATE']
range: tuple[int, int]
GuildMemberListOP = Union[
GuildMemberListSyncOP,
GuildMemberListUpdateOP,
GuildMemberListInsertOP,
GuildMemberListDeleteOP,
GuildMemberListInvalidateOP,
]
class GuildMemberListUpdateEvent(TypedDict):
id: Union[Snowflake, Literal['everyone']]
guild_id: Snowflake
member_count: int
online_count: int
groups: List[GuildMemberListGroup]
ops: List[GuildMemberListOP]

121
discord/types/interactions.py

@ -24,49 +24,20 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Dict, List, Literal, TypedDict, Union from typing import List, Literal, TypedDict, Union
from typing_extensions import NotRequired from typing_extensions import NotRequired
from .channel import ChannelTypeWithoutThread, ThreadMetadata from .application import IntegrationApplication
from .threads import ThreadType from .command import ApplicationCommand
from .components import ModalActionRow
from .member import Member from .member import Member
from .message import Attachment from .message import PartialAttachment
from .role import Role
from .snowflake import Snowflake from .snowflake import Snowflake
from .user import User from .user import User
if TYPE_CHECKING:
from .message import Message
InteractionType = Literal[1, 2, 3, 4, 5] InteractionType = Literal[1, 2, 3, 4, 5]
class _BasePartialChannel(TypedDict):
id: Snowflake
name: str
permissions: str
class PartialChannel(_BasePartialChannel):
type: ChannelTypeWithoutThread
class PartialThread(_BasePartialChannel):
type: ThreadType
thread_metadata: ThreadMetadata
parent_id: Snowflake
class ResolvedData(TypedDict, total=False):
users: Dict[str, User]
members: Dict[str, Member]
roles: Dict[str, Role]
channels: Dict[str, Union[PartialChannel, PartialThread]]
messages: Dict[str, Message]
attachments: Dict[str, Attachment]
class _BaseApplicationCommandInteractionDataOption(TypedDict): class _BaseApplicationCommandInteractionDataOption(TypedDict):
name: str name: str
@ -123,31 +94,33 @@ ApplicationCommandInteractionDataOption = Union[
class _BaseApplicationCommandInteractionData(TypedDict): class _BaseApplicationCommandInteractionData(TypedDict):
id: Snowflake id: Snowflake
name: str name: str
resolved: NotRequired[ResolvedData] version: Snowflake
guild_id: NotRequired[Snowflake] guild_id: NotRequired[Snowflake]
application_command: ApplicationCommand
attachments: List[PartialAttachment]
options: List[ApplicationCommandInteractionDataOption]
class ChatInputApplicationCommandInteractionData(_BaseApplicationCommandInteractionData, total=False): class ChatInputCommandInteractionData(_BaseApplicationCommandInteractionData, total=False):
type: Literal[1] type: Literal[1]
options: List[ApplicationCommandInteractionDataOption]
class _BaseNonChatInputApplicationCommandInteractionData(_BaseApplicationCommandInteractionData): class _BaseNonChatInputApplicationCommandInteractionData(_BaseApplicationCommandInteractionData):
target_id: Snowflake target_id: Snowflake
class UserApplicationCommandInteractionData(_BaseNonChatInputApplicationCommandInteractionData): class UserCommandInteractionData(_BaseNonChatInputApplicationCommandInteractionData):
type: Literal[2] type: Literal[2]
class MessageApplicationCommandInteractionData(_BaseNonChatInputApplicationCommandInteractionData): class MessageCommandInteractionData(_BaseNonChatInputApplicationCommandInteractionData):
type: Literal[3] type: Literal[3]
ApplicationCommandInteractionData = Union[ ApplicationCommandInteractionData = Union[
ChatInputApplicationCommandInteractionData, ChatInputCommandInteractionData,
UserApplicationCommandInteractionData, UserCommandInteractionData,
MessageApplicationCommandInteractionData, MessageCommandInteractionData,
] ]
@ -155,40 +128,45 @@ class _BaseMessageComponentInteractionData(TypedDict):
custom_id: str custom_id: str
class ButtonMessageComponentInteractionData(_BaseMessageComponentInteractionData): class ButtonInteractionData(_BaseMessageComponentInteractionData):
component_type: Literal[2] component_type: Literal[2]
class SelectMessageComponentInteractionData(_BaseMessageComponentInteractionData): class SelectInteractionData(_BaseMessageComponentInteractionData):
component_type: Literal[3] component_type: Literal[3]
values: List[str] values: List[str]
MessageComponentInteractionData = Union[ButtonMessageComponentInteractionData, SelectMessageComponentInteractionData] MessageComponentInteractionData = Union[ButtonInteractionData, SelectInteractionData]
class ModalSubmitTextInputInteractionData(TypedDict): class TextInputInteractionData(TypedDict):
type: Literal[4] type: Literal[4]
custom_id: str custom_id: str
value: str value: str
ModalSubmitComponentItemInteractionData = ModalSubmitTextInputInteractionData ModalSubmitComponentInteractionData = TextInputInteractionData
class ModalSubmitActionRowInteractionData(TypedDict): class MessageActionRowData(TypedDict):
type: Literal[1] type: Literal[1]
components: List[ModalSubmitComponentItemInteractionData] components: List[MessageComponentInteractionData]
ModalSubmitComponentInteractionData = Union[ModalSubmitActionRowInteractionData, ModalSubmitComponentItemInteractionData] class ModalSubmitActionRowData(TypedDict):
type: Literal[1]
components: List[ModalSubmitComponentInteractionData]
class ModalSubmitInteractionData(TypedDict): class ModalSubmitInteractionData(TypedDict):
id: Snowflake
custom_id: str custom_id: str
components: List[ModalSubmitComponentInteractionData] components: List[ModalSubmitComponentInteractionData]
ActionRowInteractionData = Union[MessageActionRowData, ModalSubmitActionRowData]
ComponentInteractionData = Union[MessageComponentInteractionData, ModalSubmitComponentInteractionData]
InteractionData = Union[ InteractionData = Union[
ApplicationCommandInteractionData, ApplicationCommandInteractionData,
MessageComponentInteractionData, MessageComponentInteractionData,
@ -196,42 +174,19 @@ InteractionData = Union[
] ]
class _BaseInteraction(TypedDict):
id: Snowflake
application_id: Snowflake
token: str
version: Literal[1]
guild_id: NotRequired[Snowflake]
channel_id: NotRequired[Snowflake]
locale: NotRequired[str]
guild_locale: NotRequired[str]
class PingInteraction(_BaseInteraction):
type: Literal[1]
class ApplicationCommandInteraction(_BaseInteraction):
type: Literal[2, 4]
data: ApplicationCommandInteractionData
class MessageComponentInteraction(_BaseInteraction):
type: Literal[3]
data: MessageComponentInteractionData
class ModalSubmitInteraction(_BaseInteraction):
type: Literal[5]
data: ModalSubmitInteractionData
Interaction = Union[PingInteraction, ApplicationCommandInteraction, MessageComponentInteraction, ModalSubmitInteraction]
class MessageInteraction(TypedDict): class MessageInteraction(TypedDict):
id: Snowflake id: Snowflake
type: InteractionType type: InteractionType
name: str name: str
user: User user: User
member: NotRequired[Member] member: NotRequired[Member]
class Modal(TypedDict):
id: int
nonce: NotRequired[Snowflake]
channel_id: Snowflake
title: str
custom_id: str
application: IntegrationApplication
components: List[ModalActionRow]

6
discord/types/invite.py

@ -78,8 +78,10 @@ class InviteWithMetadata(PartialInvite, _InviteMetadata):
... ...
Invite = Union[PartialInvite, InviteWithCounts, InviteWithMetadata] class AcceptedInvite(InviteWithCounts):
new_member: bool
show_verification_form: bool
Invite = Union[PartialInvite, InviteWithCounts, InviteWithMetadata, AcceptedInvite]
GatewayInvite = Union[InviteCreateEvent, InviteDeleteEvent] GatewayInvite = Union[InviteCreateEvent, InviteDeleteEvent]

6
discord/types/member.py

@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE.
""" """
from typing import Optional, TypedDict from typing import Optional, TypedDict
from .activity import BasePresenceUpdate
from .snowflake import SnowflakeList from .snowflake import SnowflakeList
from .user import PartialUser from .user import PartialUser
@ -60,6 +62,10 @@ class MemberWithUser(_OptionalMemberWithUser):
user: PartialUser user: PartialUser
class MemberWithPresence(MemberWithUser):
presence: BasePresenceUpdate
class PrivateMember(MemberWithUser): class PrivateMember(MemberWithUser):
bio: str bio: str
banner: Optional[str] banner: Optional[str]

11
discord/types/message.py

@ -33,7 +33,7 @@ from .user import User
from .emoji import PartialEmoji from .emoji import PartialEmoji
from .embed import Embed from .embed import Embed
from .channel import ChannelType from .channel import ChannelType
from .components import Component from .components import MessageActionRow
from .interactions import MessageInteraction from .interactions import MessageInteraction
from .application import BaseApplication from .application import BaseApplication
from .sticker import StickerItem from .sticker import StickerItem
@ -134,7 +134,7 @@ class Message(PartialMessage):
sticker_items: NotRequired[List[StickerItem]] sticker_items: NotRequired[List[StickerItem]]
referenced_message: NotRequired[Optional[Message]] referenced_message: NotRequired[Optional[Message]]
interaction: NotRequired[MessageInteraction] interaction: NotRequired[MessageInteraction]
components: NotRequired[List[Component]] components: NotRequired[List[MessageActionRow]]
position: NotRequired[int] position: NotRequired[int]
call: NotRequired[Call] call: NotRequired[Call]
role_subscription_data: NotRequired[RoleSubscriptionData] role_subscription_data: NotRequired[RoleSubscriptionData]
@ -190,6 +190,13 @@ MessageSearchSortType = Literal['timestamp', 'relevance']
MessageSearchSortOrder = Literal['desc', 'asc'] MessageSearchSortOrder = Literal['desc', 'asc']
class PartialAttachment(TypedDict):
id: NotRequired[Snowflake]
filename: str
description: NotRequired[str]
uploaded_filename: NotRequired[str]
class UploadedAttachment(TypedDict): class UploadedAttachment(TypedDict):
id: NotRequired[Snowflake] id: NotRequired[Snowflake]
filename: str filename: str

65
discord/types/user.py

@ -136,11 +136,70 @@ class Relationship(TypedDict):
since: NotRequired[str] since: NotRequired[str]
ProtoSettingsType = Literal[1, 2, 3]
class ProtoSettings(TypedDict): class ProtoSettings(TypedDict):
settings: str settings: str
ProtoSettingsType = Literal[1, 2, 3] class _ConsentSettings(TypedDict):
consented: bool
class PartialConsentSettings(TypedDict):
personalization: _ConsentSettings
class ConsentSettings(PartialConsentSettings):
usage_statistics: _ConsentSettings
class _EmailSettingsCategories(TypedDict):
communication: bool
social: bool
recommendations_and_events: bool
tips: bool
updates_and_announcements: bool
family_center_digest: bool
class EmailSettings(TypedDict):
initialized: bool
categories: _EmailSettingsCategories
MessageNotificationLevel = Literal[0, 1, 2, 3]
HighlightLevel = Literal[0, 1, 2]
class MuteConfig(TypedDict):
end_time: Optional[str]
selected_time_window: Optional[int]
class ChannelOverride(TypedDict):
channel_id: Snowflake
collapsed: bool
message_notifications: MessageNotificationLevel
muted: bool
mute_config: Optional[MuteConfig]
class UserGuildSettings(TypedDict):
guild_id: Optional[Snowflake]
channel_overrides: List[ChannelOverride]
flags: int
message_notifications: MessageNotificationLevel
notify_highlights: HighlightLevel
hide_muted_channels: bool
mobile_push: bool
muted: bool
mute_config: Optional[MuteConfig]
mute_scheduled_events: bool
suppress_everyone: bool
suppress_roles: bool
version: int
class UserAffinity(TypedDict): class UserAffinity(TypedDict):
@ -177,3 +236,7 @@ class FriendSuggestionReason(TypedDict):
class FriendSuggestion(TypedDict): class FriendSuggestion(TypedDict):
suggested_user: PartialUser suggested_user: PartialUser
reasons: List[FriendSuggestionReason] reasons: List[FriendSuggestionReason]
class Report(TypedDict):
report_id: Snowflake

Loading…
Cancel
Save