|
|
@ -32,6 +32,7 @@ from os import PathLike |
|
|
|
from typing import ( |
|
|
|
Dict, |
|
|
|
TYPE_CHECKING, |
|
|
|
Literal, |
|
|
|
Sequence, |
|
|
|
Union, |
|
|
|
List, |
|
|
@ -49,7 +50,7 @@ from .asset import Asset |
|
|
|
from .reaction import Reaction |
|
|
|
from .emoji import Emoji |
|
|
|
from .partial_emoji import PartialEmoji |
|
|
|
from .enums import InteractionType, MessageType, ChannelType, try_enum |
|
|
|
from .enums import InteractionType, MessageReferenceType, MessageType, ChannelType, try_enum |
|
|
|
from .errors import HTTPException |
|
|
|
from .components import _component_factory |
|
|
|
from .embeds import Embed |
|
|
@ -72,6 +73,7 @@ if TYPE_CHECKING: |
|
|
|
Message as MessagePayload, |
|
|
|
Attachment as AttachmentPayload, |
|
|
|
MessageReference as MessageReferencePayload, |
|
|
|
MessageSnapshot as MessageSnapshotPayload, |
|
|
|
MessageApplication as MessageApplicationPayload, |
|
|
|
MessageActivity as MessageActivityPayload, |
|
|
|
RoleSubscriptionData as RoleSubscriptionDataPayload, |
|
|
@ -111,6 +113,7 @@ __all__ = ( |
|
|
|
'PartialMessage', |
|
|
|
'MessageInteraction', |
|
|
|
'MessageReference', |
|
|
|
'MessageSnapshot', |
|
|
|
'DeletedReferencedMessage', |
|
|
|
'MessageApplication', |
|
|
|
'RoleSubscriptionInfo', |
|
|
@ -464,6 +467,133 @@ class DeletedReferencedMessage: |
|
|
|
return self._parent.guild_id |
|
|
|
|
|
|
|
|
|
|
|
class MessageSnapshot: |
|
|
|
"""Represents a message snapshot attached to a forwarded message. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
|
|
|
|
Attributes |
|
|
|
----------- |
|
|
|
type: :class:`MessageType` |
|
|
|
The type of the forwarded message. |
|
|
|
content: :class:`str` |
|
|
|
The actual contents of the forwarded message. |
|
|
|
embeds: List[:class:`Embed`] |
|
|
|
A list of embeds the forwarded message has. |
|
|
|
attachments: List[:class:`Attachment`] |
|
|
|
A list of attachments given to the forwarded message. |
|
|
|
created_at: :class:`datetime.datetime` |
|
|
|
The forwarded message's time of creation. |
|
|
|
flags: :class:`MessageFlags` |
|
|
|
Extra features of the the message snapshot. |
|
|
|
stickers: List[:class:`StickerItem`] |
|
|
|
A list of sticker items given to the message. |
|
|
|
components: List[Union[:class:`ActionRow`, :class:`Button`, :class:`SelectMenu`]] |
|
|
|
A list of components in the message. |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ( |
|
|
|
'_cs_raw_channel_mentions', |
|
|
|
'_cs_cached_message', |
|
|
|
'_cs_raw_mentions', |
|
|
|
'_cs_raw_role_mentions', |
|
|
|
'_edited_timestamp', |
|
|
|
'attachments', |
|
|
|
'content', |
|
|
|
'embeds', |
|
|
|
'flags', |
|
|
|
'created_at', |
|
|
|
'type', |
|
|
|
'stickers', |
|
|
|
'components', |
|
|
|
'_state', |
|
|
|
) |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def _from_value( |
|
|
|
cls, |
|
|
|
state: ConnectionState, |
|
|
|
message_snapshots: Optional[List[Dict[Literal['message'], MessageSnapshotPayload]]], |
|
|
|
) -> List[Self]: |
|
|
|
if not message_snapshots: |
|
|
|
return [] |
|
|
|
|
|
|
|
return [cls(state, snapshot['message']) for snapshot in message_snapshots] |
|
|
|
|
|
|
|
def __init__(self, state: ConnectionState, data: MessageSnapshotPayload): |
|
|
|
self.type: MessageType = try_enum(MessageType, data['type']) |
|
|
|
self.content: str = data['content'] |
|
|
|
self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']] |
|
|
|
self.attachments: List[Attachment] = [Attachment(data=a, state=state) for a in data['attachments']] |
|
|
|
self.created_at: datetime.datetime = utils.parse_time(data['timestamp']) |
|
|
|
self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data['edited_timestamp']) |
|
|
|
self.flags: MessageFlags = MessageFlags._from_value(data.get('flags', 0)) |
|
|
|
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('stickers_items', [])] |
|
|
|
|
|
|
|
self.components: List[MessageComponentType] = [] |
|
|
|
for component_data in data.get('components', []): |
|
|
|
component = _component_factory(component_data) |
|
|
|
if component is not None: |
|
|
|
self.components.append(component) |
|
|
|
|
|
|
|
self._state: ConnectionState = state |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
name = self.__class__.__name__ |
|
|
|
return f'<{name} type={self.type!r} created_at={self.created_at!r} flags={self.flags!r}>' |
|
|
|
|
|
|
|
@utils.cached_slot_property('_cs_raw_mentions') |
|
|
|
def raw_mentions(self) -> List[int]: |
|
|
|
"""List[:class:`int`]: A property that returns an array of user IDs matched with |
|
|
|
the syntax of ``<@user_id>`` in the message content. |
|
|
|
|
|
|
|
This allows you to receive the user IDs of mentioned users |
|
|
|
even in a private message context. |
|
|
|
""" |
|
|
|
return [int(x) for x in re.findall(r'<@!?([0-9]{15,20})>', self.content)] |
|
|
|
|
|
|
|
@utils.cached_slot_property('_cs_raw_channel_mentions') |
|
|
|
def raw_channel_mentions(self) -> List[int]: |
|
|
|
"""List[:class:`int`]: A property that returns an array of channel IDs matched with |
|
|
|
the syntax of ``<#channel_id>`` in the message content. |
|
|
|
""" |
|
|
|
return [int(x) for x in re.findall(r'<#([0-9]{15,20})>', self.content)] |
|
|
|
|
|
|
|
@utils.cached_slot_property('_cs_raw_role_mentions') |
|
|
|
def raw_role_mentions(self) -> List[int]: |
|
|
|
"""List[:class:`int`]: A property that returns an array of role IDs matched with |
|
|
|
the syntax of ``<@&role_id>`` in the message content. |
|
|
|
""" |
|
|
|
return [int(x) for x in re.findall(r'<@&([0-9]{15,20})>', self.content)] |
|
|
|
|
|
|
|
@utils.cached_slot_property('_cs_cached_message') |
|
|
|
def cached_message(self) -> Optional[Message]: |
|
|
|
"""Optional[:class:`Message`]: Returns the cached message this snapshot points to, if any.""" |
|
|
|
state = self._state |
|
|
|
return ( |
|
|
|
utils.find( |
|
|
|
lambda m: ( |
|
|
|
m.created_at == self.created_at |
|
|
|
and m.edited_at == self.edited_at |
|
|
|
and m.content == self.content |
|
|
|
and m.embeds == self.embeds |
|
|
|
and m.components == self.components |
|
|
|
and m.stickers == self.stickers |
|
|
|
and m.attachments == self.attachments |
|
|
|
and m.flags == self.flags |
|
|
|
), |
|
|
|
reversed(state._messages), |
|
|
|
) |
|
|
|
if state._messages |
|
|
|
else None |
|
|
|
) |
|
|
|
|
|
|
|
@property |
|
|
|
def edited_at(self) -> Optional[datetime.datetime]: |
|
|
|
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the forwarded message.""" |
|
|
|
return self._edited_timestamp |
|
|
|
|
|
|
|
|
|
|
|
class MessageReference: |
|
|
|
"""Represents a reference to a :class:`~discord.Message`. |
|
|
|
|
|
|
@ -474,6 +604,10 @@ class MessageReference: |
|
|
|
|
|
|
|
Attributes |
|
|
|
----------- |
|
|
|
type: :class:`MessageReferenceType` |
|
|
|
The type of message reference. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
message_id: Optional[:class:`int`] |
|
|
|
The id of the message referenced. |
|
|
|
channel_id: :class:`int` |
|
|
@ -498,10 +632,19 @@ class MessageReference: |
|
|
|
.. versionadded:: 1.6 |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'fail_if_not_exists', 'resolved', '_state') |
|
|
|
__slots__ = ('type', 'message_id', 'channel_id', 'guild_id', 'fail_if_not_exists', 'resolved', '_state') |
|
|
|
|
|
|
|
def __init__(self, *, message_id: int, channel_id: int, guild_id: Optional[int] = None, fail_if_not_exists: bool = True): |
|
|
|
def __init__( |
|
|
|
self, |
|
|
|
*, |
|
|
|
message_id: int, |
|
|
|
channel_id: int, |
|
|
|
guild_id: Optional[int] = None, |
|
|
|
fail_if_not_exists: bool = True, |
|
|
|
type: MessageReferenceType = MessageReferenceType.reply, |
|
|
|
): |
|
|
|
self._state: Optional[ConnectionState] = None |
|
|
|
self.type: MessageReferenceType = type |
|
|
|
self.resolved: Optional[Union[Message, DeletedReferencedMessage]] = None |
|
|
|
self.message_id: Optional[int] = message_id |
|
|
|
self.channel_id: int = channel_id |
|
|
@ -511,6 +654,7 @@ class MessageReference: |
|
|
|
@classmethod |
|
|
|
def with_state(cls, state: ConnectionState, data: MessageReferencePayload) -> Self: |
|
|
|
self = cls.__new__(cls) |
|
|
|
self.type = try_enum(MessageReferenceType, data.get('type', 0)) |
|
|
|
self.message_id = utils._get_as_snowflake(data, 'message_id') |
|
|
|
self.channel_id = int(data['channel_id']) |
|
|
|
self.guild_id = utils._get_as_snowflake(data, 'guild_id') |
|
|
@ -520,7 +664,13 @@ class MessageReference: |
|
|
|
return self |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def from_message(cls, message: PartialMessage, *, fail_if_not_exists: bool = True) -> Self: |
|
|
|
def from_message( |
|
|
|
cls, |
|
|
|
message: PartialMessage, |
|
|
|
*, |
|
|
|
fail_if_not_exists: bool = True, |
|
|
|
type: MessageReferenceType = MessageReferenceType.reply, |
|
|
|
) -> Self: |
|
|
|
"""Creates a :class:`MessageReference` from an existing :class:`~discord.Message`. |
|
|
|
|
|
|
|
.. versionadded:: 1.6 |
|
|
@ -534,6 +684,10 @@ class MessageReference: |
|
|
|
if the message no longer exists or Discord could not fetch the message. |
|
|
|
|
|
|
|
.. versionadded:: 1.7 |
|
|
|
type: :class:`~discord.MessageReferenceType` |
|
|
|
The type of message reference this is. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
|
|
|
|
Returns |
|
|
|
------- |
|
|
@ -545,6 +699,7 @@ class MessageReference: |
|
|
|
channel_id=message.channel.id, |
|
|
|
guild_id=getattr(message.guild, 'id', None), |
|
|
|
fail_if_not_exists=fail_if_not_exists, |
|
|
|
type=type, |
|
|
|
) |
|
|
|
self._state = message._state |
|
|
|
return self |
|
|
@ -567,7 +722,9 @@ class MessageReference: |
|
|
|
return f'<MessageReference message_id={self.message_id!r} channel_id={self.channel_id!r} guild_id={self.guild_id!r}>' |
|
|
|
|
|
|
|
def to_dict(self) -> MessageReferencePayload: |
|
|
|
result: Dict[str, Any] = {'message_id': self.message_id} if self.message_id is not None else {} |
|
|
|
result: Dict[str, Any] = ( |
|
|
|
{'type': self.type.value, 'message_id': self.message_id} if self.message_id is not None else {} |
|
|
|
) |
|
|
|
result['channel_id'] = self.channel_id |
|
|
|
if self.guild_id is not None: |
|
|
|
result['guild_id'] = self.guild_id |
|
|
@ -1699,7 +1856,12 @@ class PartialMessage(Hashable): |
|
|
|
|
|
|
|
return Message(state=self._state, channel=self.channel, data=data) |
|
|
|
|
|
|
|
def to_reference(self, *, fail_if_not_exists: bool = True) -> MessageReference: |
|
|
|
def to_reference( |
|
|
|
self, |
|
|
|
*, |
|
|
|
fail_if_not_exists: bool = True, |
|
|
|
type: MessageReferenceType = MessageReferenceType.reply, |
|
|
|
) -> MessageReference: |
|
|
|
"""Creates a :class:`~discord.MessageReference` from the current message. |
|
|
|
|
|
|
|
.. versionadded:: 1.6 |
|
|
@ -1711,6 +1873,10 @@ class PartialMessage(Hashable): |
|
|
|
if the message no longer exists or Discord could not fetch the message. |
|
|
|
|
|
|
|
.. versionadded:: 1.7 |
|
|
|
type: :class:`MessageReferenceType` |
|
|
|
The type of message reference. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
|
|
|
|
Returns |
|
|
|
--------- |
|
|
@ -1718,7 +1884,44 @@ class PartialMessage(Hashable): |
|
|
|
The reference to this message. |
|
|
|
""" |
|
|
|
|
|
|
|
return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists) |
|
|
|
return MessageReference.from_message(self, fail_if_not_exists=fail_if_not_exists, type=type) |
|
|
|
|
|
|
|
async def forward( |
|
|
|
self, |
|
|
|
destination: MessageableChannel, |
|
|
|
*, |
|
|
|
fail_if_not_exists: bool = True, |
|
|
|
) -> Message: |
|
|
|
"""|coro| |
|
|
|
|
|
|
|
Forwards this message to a channel. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
|
|
|
|
Parameters |
|
|
|
---------- |
|
|
|
destination: :class:`~discord.abc.Messageable` |
|
|
|
The channel to forward this message to. |
|
|
|
fail_if_not_exists: :class:`bool` |
|
|
|
Whether replying using the message reference should raise :class:`HTTPException` |
|
|
|
if the message no longer exists or Discord could not fetch the message. |
|
|
|
|
|
|
|
Raises |
|
|
|
------ |
|
|
|
~discord.HTTPException |
|
|
|
Forwarding the message failed. |
|
|
|
|
|
|
|
Returns |
|
|
|
------- |
|
|
|
:class:`.Message` |
|
|
|
The message sent to the channel. |
|
|
|
""" |
|
|
|
reference = self.to_reference( |
|
|
|
fail_if_not_exists=fail_if_not_exists, |
|
|
|
type=MessageReferenceType.forward, |
|
|
|
) |
|
|
|
ret = await destination.send(reference=reference) |
|
|
|
return ret |
|
|
|
|
|
|
|
def to_message_reference_dict(self) -> MessageReferencePayload: |
|
|
|
data: MessageReferencePayload = { |
|
|
@ -1881,6 +2084,10 @@ class Message(PartialMessage, Hashable): |
|
|
|
purchase_notification: Optional[:class:`PurchaseNotification`] |
|
|
|
The data of the purchase notification that prompted this :attr:`MessageType.purchase_notification` message. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
message_snapshots: List[:class:`MessageSnapshot`] |
|
|
|
The message snapshots attached to this message. |
|
|
|
|
|
|
|
.. versionadded:: 2.5 |
|
|
|
""" |
|
|
|
|
|
|
@ -1920,6 +2127,7 @@ class Message(PartialMessage, Hashable): |
|
|
|
'poll', |
|
|
|
'call', |
|
|
|
'purchase_notification', |
|
|
|
'message_snapshots', |
|
|
|
) |
|
|
|
|
|
|
|
if TYPE_CHECKING: |
|
|
@ -1958,6 +2166,7 @@ class Message(PartialMessage, Hashable): |
|
|
|
self.position: Optional[int] = data.get('position') |
|
|
|
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id') |
|
|
|
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])] |
|
|
|
self.message_snapshots: List[MessageSnapshot] = MessageSnapshot._from_value(state, data.get('message_snapshots')) |
|
|
|
|
|
|
|
self.poll: Optional[Poll] = None |
|
|
|
try: |
|
|
|