mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-18 15:06:07 +00:00
Add support for message forwarding
Co-authored-by: Red Magnos <redmagnos@gmail.com> Co-authored-by: MCausc78 <mcausc78@gmail.com> Co-authored-by: owocado <24418520+owocado@users.noreply.github.com> Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com>
This commit is contained in:
parent
b11f19a397
commit
99a7093c34
@ -220,6 +220,12 @@ class ChannelType(Enum):
|
||||
return self.name
|
||||
|
||||
|
||||
class MessageReferenceType(Enum):
|
||||
default = 0
|
||||
reply = 0
|
||||
forward = 1
|
||||
|
||||
|
||||
class MessageType(Enum):
|
||||
default = 0
|
||||
recipient_add = 1
|
||||
|
@ -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:
|
||||
|
@ -102,7 +102,11 @@ class MessageApplication(TypedDict):
|
||||
cover_image: NotRequired[str]
|
||||
|
||||
|
||||
MessageReferenceType = Literal[0, 1]
|
||||
|
||||
|
||||
class MessageReference(TypedDict, total=False):
|
||||
type: MessageReferenceType
|
||||
message_id: Snowflake
|
||||
channel_id: Required[Snowflake]
|
||||
guild_id: Snowflake
|
||||
@ -173,6 +177,20 @@ MessageType = Literal[
|
||||
]
|
||||
|
||||
|
||||
class MessageSnapshot(TypedDict):
|
||||
type: MessageType
|
||||
content: str
|
||||
embeds: List[Embed]
|
||||
attachments: List[Attachment]
|
||||
timestamp: str
|
||||
edited_timestamp: Optional[str]
|
||||
flags: NotRequired[int]
|
||||
mentions: List[UserWithMember]
|
||||
mention_roles: SnowflakeList
|
||||
stickers_items: NotRequired[List[StickerItem]]
|
||||
components: NotRequired[List[Component]]
|
||||
|
||||
|
||||
class Message(PartialMessage):
|
||||
id: Snowflake
|
||||
author: User
|
||||
|
26
docs/api.rst
26
docs/api.rst
@ -3810,6 +3810,24 @@ of :class:`enum.Enum`.
|
||||
The subscription is inactive and not being charged.
|
||||
|
||||
|
||||
.. class:: MessageReferenceType
|
||||
|
||||
Represents the type of a message reference.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
.. attribute:: reply
|
||||
|
||||
A message reply.
|
||||
|
||||
.. attribute:: forward
|
||||
|
||||
A forwarded message.
|
||||
|
||||
.. attribute:: default
|
||||
|
||||
An alias for :attr:`.reply`.
|
||||
|
||||
.. _discord-api-audit-logs:
|
||||
|
||||
Audit Log Data
|
||||
@ -5353,6 +5371,14 @@ PollAnswer
|
||||
|
||||
.. _discord_api_data:
|
||||
|
||||
MessageSnapshot
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: MessageSnapshot
|
||||
|
||||
.. autoclass:: MessageSnapshot
|
||||
:members:
|
||||
|
||||
Data Classes
|
||||
--------------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user