Skip to content

Commit 5baa513

Browse files
authored
forwarding
1 parent ceaffaf commit 5baa513

File tree

3 files changed

+175
-16
lines changed

3 files changed

+175
-16
lines changed

discord/abc.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,8 +1493,8 @@ async def send(
14931493
.. versionadded:: 1.4
14941494
14951495
reference: Union[:class:`~discord.Message`, :class:`~discord.MessageReference`, :class:`~discord.PartialMessage`]
1496-
A reference to the :class:`~discord.Message` to which you are replying, this can be created using
1497-
:meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. You can control
1496+
A reference to the :class:`~discord.Message` you are replying to or forwarding, this can be created using
1497+
:meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. When replying, you can control
14981498
whether this mentions the author of the referenced message using the
14991499
:attr:`~discord.AllowedMentions.replied_user` attribute of ``allowed_mentions`` or by
15001500
setting ``mention_author``.
@@ -1588,6 +1588,8 @@ async def send(
15881588
if reference is not None:
15891589
try:
15901590
reference = reference.to_message_reference_dict()
1591+
if not isinstance(reference, MessageReference):
1592+
utils.warn_deprecated(f"Passing {type(reference).__name__} to reference", "MessageReference", "2.7", "3.0")
15911593
except AttributeError:
15921594
raise InvalidArgument(
15931595
"reference parameter must be Message, MessageReference, or"

discord/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
"EntitlementOwnerType",
7777
"IntegrationType",
7878
"InteractionContextType",
79+
"PollLayoutType",
80+
"MessageReferenceType",
7981
)
8082

8183

discord/message.py

Lines changed: 169 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
from .types.message import MessageApplication as MessageApplicationPayload
8484
from .types.message import MessageCall as MessageCallPayload
8585
from .types.message import MessageReference as MessageReferencePayload
86+
from .types.message import ForwardedMessage as ForwardedMessagePayload
87+
from .types.message import MessageSnapshot as MessageSnapshotPayload
8688
from .types.message import Reaction as ReactionPayload
8789
from .types.poll import Poll as PollPayload
8890
from .types.snowflake import SnowflakeList
@@ -101,6 +103,7 @@
101103
"MessageReference",
102104
"MessageCall",
103105
"DeletedReferencedMessage",
106+
"ForwardedMessage"
104107
)
105108

106109

@@ -477,8 +480,8 @@ class MessageReference:
477480
478481
Attributes
479482
----------
480-
type: Optional[:class:`MessageReferenceType`]
481-
The type of message reference. If this is not provided, assume default behavior.
483+
type: Optional[:class:`~discord.MessageReferenceType`]
484+
The type of message reference. If this is not provided, assume default behavior (reply).
482485
483486
.. versionadded:: 2.7
484487
@@ -523,11 +526,11 @@ def __init__(
523526
channel_id: int,
524527
guild_id: int | None = None,
525528
fail_if_not_exists: bool = True,
526-
type: MessageReferenceType | None = None
529+
type: MessageReferenceType = MessageReferenceType.default
527530
):
528531
self._state: ConnectionState | None = None
529532
self.resolved: Message | DeletedReferencedMessage | None = None
530-
self.type: MessageReferenceType | None = type
533+
self.type: MessageReferenceType = type
531534
self.message_id: int | None = message_id
532535
self.channel_id: int = channel_id
533536
self.guild_id: int | None = guild_id
@@ -538,9 +541,9 @@ def with_state(
538541
cls: type[MR], state: ConnectionState, data: MessageReferencePayload
539542
) -> MR:
540543
self = cls.__new__(cls)
541-
self.type = try_enum(MessageReferenceType, data.get("type"))
544+
self.type = try_enum(MessageReferenceType, data.get("type")) or MessageReferenceType.default
542545
self.message_id = utils._get_as_snowflake(data, "message_id")
543-
self.channel_id = int(data.pop("channel_id"))
546+
self.channel_id = utils._get_as_snowflake(data, "channel_id")
544547
self.guild_id = utils._get_as_snowflake(data, "guild_id")
545548
self.fail_if_not_exists = data.get("fail_if_not_exists", True)
546549
self._state = state
@@ -549,7 +552,7 @@ def with_state(
549552

550553
@classmethod
551554
def from_message(
552-
cls: type[MR], message: Message, *, fail_if_not_exists: bool = True, type: MessageReferenceType = None
555+
cls: type[MR], message: Message, *, fail_if_not_exists: bool = True, type: MessageReferenceType = MessageReferenceType.default
553556
) -> MR:
554557
"""Creates a :class:`MessageReference` from an existing :class:`~discord.Message`.
555558
@@ -565,8 +568,8 @@ def from_message(
565568
566569
.. versionadded:: 1.7
567570
568-
type: Optional[:class:`MessageReferenceType`]
569-
The type of reference to create. Defaults to reply.
571+
type: Optional[:class:`~discord.MessageReferenceType`]
572+
The type of reference to create. Defaults to :attr:`MessageReferenceType.default` (reply).
570573
571574
.. versionadded:: 2.7
572575
@@ -602,14 +605,16 @@ def jump_url(self) -> str:
602605
def __repr__(self) -> str:
603606
return (
604607
f"<MessageReference message_id={self.message_id!r}"
605-
f" channel_id={self.channel_id!r} guild_id={self.guild_id!r}>"
608+
f" channel_id={self.channel_id!r} guild_id={self.guild_id!r}"
609+
f" type={self.type!r}>"
606610
)
607611

608612
def to_dict(self) -> MessageReferencePayload:
609613
result: MessageReferencePayload = (
610614
{"message_id": self.message_id} if self.message_id is not None else {}
611615
)
612616
result["channel_id"] = self.channel_id
617+
result["type"] = self.type and self.type.value
613618
if self.guild_id is not None:
614619
result["guild_id"] = self.guild_id
615620
if self.fail_if_not_exists is not None:
@@ -647,6 +652,106 @@ def ended_at(self) -> datetime.datetime | None:
647652
return self._ended_timestamp
648653

649654

655+
class ForwardedMessage:
656+
"""Represents the snapshotted contents from a forwarded message. Forwarded messages are immutable; any updates to the original message won't be reflected.
657+
658+
.. versionadded:: 2.7
659+
660+
Attributes
661+
----------
662+
type: :class:`MessageType`
663+
The type of message. In most cases this should not be checked, but it is helpful
664+
in cases where it might be a system message for :attr:`system_content`.
665+
content: :class:`str`
666+
The contents of the original message.
667+
embeds: List[:class:`Embed`]
668+
A list of embeds the original message had.
669+
attachments: List[:class:`Attachment`]
670+
A list of attachments given to the original message.
671+
flags: :class:`MessageFlags`
672+
Extra features of the message.
673+
mentions: List[Union[:class:`abc.User`, :class:`Object`]]
674+
A list of :class:`Member` that were mentioned.
675+
role_mentions: List[Union[:class:`Role`, :class:`Object`]]
676+
A list of :class:`Role` that were mentioned.
677+
stickers: List[:class:`StickerItem`]
678+
A list of sticker items given to the original message.
679+
components: List[:class:`Component`]
680+
A list of components in the original message.
681+
"""
682+
683+
__slots__ = (
684+
"message_id",
685+
"channel_id",
686+
"guild_id",
687+
"fail_if_not_exists",
688+
"resolved",
689+
"type",
690+
"_state",
691+
)
692+
693+
def __init__(
694+
self,
695+
*,
696+
state: ConnectionState,
697+
reference: MessageReference,
698+
data: ForwardedMessagePayload,
699+
):
700+
self._state: ConnectionState = state
701+
self.id: int = reference.message_id
702+
self.channel = state.get_channel(reference.channel_id) or (reference.channel_id and Object(reference.channel_id))
703+
self.guild = state._get_guild(reference.guild_id) or (reference.guild_id and Object(reference.guild_id))
704+
self.content: str = data["content"]
705+
self.embeds: list[Embed] = [Embed.from_dict(a) for a in data["embeds"]]
706+
self.attachments: list[Attachment] = [
707+
Attachment(data=a, state=state) for a in data["attachments"]
708+
]
709+
self.flags: MessageFlags = MessageFlags._from_value(data.get("flags", 0))
710+
self.stickers: list[StickerItem] = [
711+
StickerItem(data=d, state=state) for d in data.get("sticker_items", [])
712+
]
713+
self.components: list[Component] = [
714+
_component_factory(d) for d in data.get("components", [])
715+
]
716+
self._edited_timestamp: datetime.datetime | None = utils.parse_time(
717+
data["edited_timestamp"]
718+
)
719+
720+
@property
721+
def created_at(self) -> datetime.datetime:
722+
"""The original message's creation time in UTC."""
723+
return utils.snowflake_time(self.id)
724+
725+
@property
726+
def edited_at(self) -> datetime.datetime | None:
727+
"""An aware UTC datetime object containing the
728+
edited time of the original message.
729+
"""
730+
return self._edited_timestamp
731+
732+
733+
class MessageSnapshot:
734+
"""Represents a message snapshot.
735+
736+
.. versionadded:: 2.7
737+
738+
Attributes
739+
----------
740+
message: :class:`ForwardedMessage`
741+
The forwarded message, which includes a minimal subset of fields from the original message.
742+
"""
743+
744+
def __init__(
745+
self,
746+
*,
747+
state: ConnectionState,
748+
reference: MessageReference,
749+
data: MessageSnapshotPayload,
750+
):
751+
self._state: ConnectionState = state
752+
self.message: ForwardedMessage = ForwardedMessage(state=state, reference=reference, data=data)
753+
754+
650755
def flatten_handlers(cls):
651756
prefix = len("_handle_")
652757
handlers = [
@@ -799,6 +904,10 @@ class Message(Hashable):
799904
The call information associated with this message, if applicable.
800905
801906
.. versionadded:: 2.6
907+
snapshots: Optional[List[:class:`MessageSnapshots`]]
908+
The snapshots attached to this message, if applicable.
909+
910+
.. versionadded:: 2.7
802911
"""
803912

804913
__slots__ = (
@@ -837,6 +946,7 @@ class Message(Hashable):
837946
"thread",
838947
"_poll",
839948
"call",
949+
"snapshots",
840950
)
841951

842952
if TYPE_CHECKING:
@@ -916,6 +1026,14 @@ def __init__(
9161026
# the channel will be the correct type here
9171027
ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore
9181028

1029+
self.snapshots: list[MessageSnapshot]
1030+
try:
1031+
self.snapshots = [MessageSnapshot(
1032+
state=state, reference=self.reference, data=ms,
1033+
) for ms in data["message_snapshots"]]
1034+
except KeyError:
1035+
self.snapshots = []
1036+
9191037
from .interactions import InteractionMetadata, MessageInteraction
9201038

9211039
self._interaction: MessageInteraction | None
@@ -1939,7 +2057,38 @@ async def reply(self, content: str | None = None, **kwargs) -> Message:
19392057
you specified both ``file`` and ``files``.
19402058
"""
19412059

1942-
return await self.channel.send(content, reference=self, **kwargs)
2060+
return await self.channel.send(content, reference=self.to_reference(), **kwargs)
2061+
2062+
async def forward(self, channel: MessageableChannel | PartialMessageableChannel, **kwargs) -> Message:
2063+
"""|coro|
2064+
2065+
A shortcut method to :meth:`.abc.Messageable.send` to forward the
2066+
:class:`.Message` to a channel.
2067+
2068+
.. versionadded:: 2.7
2069+
2070+
Parameters
2071+
----------
2072+
channel: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, :class:`str`]
2073+
The emoji to react with.
2074+
2075+
Returns
2076+
-------
2077+
:class:`.Message`
2078+
The message that was sent.
2079+
2080+
Raises
2081+
------
2082+
~discord.HTTPException
2083+
Sending the message failed.
2084+
~discord.Forbidden
2085+
You do not have the proper permissions to send the message.
2086+
~discord.InvalidArgument
2087+
The ``files`` list is not of the appropriate size, or
2088+
you specified both ``file`` and ``files``.
2089+
"""
2090+
2091+
return await channel.send(reference=self.to_reference(type=MessageReferenceType.forward))
19432092

19442093
async def end_poll(self) -> Message:
19452094
"""|coro|
@@ -1969,7 +2118,7 @@ async def end_poll(self) -> Message:
19692118

19702119
return message
19712120

1972-
def to_reference(self, *, fail_if_not_exists: bool = True) -> MessageReference:
2121+
def to_reference(self, *, fail_if_not_exists: bool = True, type: MessageReferenceType = None) -> MessageReference:
19732122
"""Creates a :class:`~discord.MessageReference` from the current message.
19742123
19752124
.. versionadded:: 1.6
@@ -1982,20 +2131,26 @@ def to_reference(self, *, fail_if_not_exists: bool = True) -> MessageReference:
19822131
19832132
.. versionadded:: 1.7
19842133
2134+
type: Optional[:class:`~discord.MessageReferenceType`]
2135+
The type of message reference. Defaults to a reply.
2136+
2137+
.. versionadded:: 2.7
2138+
19852139
Returns
19862140
-------
19872141
:class:`~discord.MessageReference`
19882142
The reference to this message.
19892143
"""
19902144

19912145
return MessageReference.from_message(
1992-
self, fail_if_not_exists=fail_if_not_exists
2146+
self, fail_if_not_exists=fail_if_not_exists, type=type
19932147
)
19942148

1995-
def to_message_reference_dict(self) -> MessageReferencePayload:
2149+
def to_message_reference_dict(self, type: MessageReferenceType = None) -> MessageReferencePayload:
19962150
data: MessageReferencePayload = {
19972151
"message_id": self.id,
19982152
"channel_id": self.channel.id,
2153+
"type": type and type.value,
19992154
}
20002155

20012156
if self.guild is not None:

0 commit comments

Comments
 (0)