Repository: Rapptz/discord.py

Issue:
Add a method to access the "message_reference" object on messages
### The Problem

Currently when a message is pinned, you have no way of knowing what message was pinned. Discord sends a [message_reference](https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure) object on the "User pinned a message to this channel" messages and also for crossposted messages either denoting the message that was pinned or the original message that was crossposted.

### The Ideal Solution

Ideally, messages could have a "reference" attribute that would allow you to access the data provided in the message_reference object, either allowing access to the id itself or the object linked to that id if possible (e.g. if the bot is in the guild and has access to the channel or if the pinned/original message is cached)

### The Current Solution

There is no good solution to this at the moment.

### Summary

Add a way to access a message's reference object :) 


Discussion:
FYI, `on_message_edit` triggers on message pin/unpin according to docs
> FYI, `on_message_edit` triggers on message pin/unpin according to docs

oh I forgot about that but it'd still be nice to have
[`Message.type`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Message.type) is [`pins_add`](https://discordpy.readthedocs.io/en/latest/api.html#discord.MessageType.pins_add) when a message saying that a message was pinned to a channel so you can check for it in `on_message`. Also, like @apple502j mentioned, `on_message_edit` also triggers on pins updates. Both of these events give you message object which has all the fields that the `message_reference` provides (`message_id`, `channel_id` and `guild_id`)
> [`Message.type`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Message.type) is [`pins_add`](https://discordpy.readthedocs.io/en/latest/api.html#discord.MessageType.pins_add) when a message saying that a message was pinned to a channel so you can check for it in `on_message`. Also, like @apple502j mentioned, `on_message_edit` also triggers on pins updates. Both of these events give you message object which has all the fields that the `message_reference` provides (`message_id`, `channel_id` and `guild_id`)

This isn't what's being asked though. He is asking for the `message_reference` information to be provided on a `Message` object just like is [documented here](https://discord.com/developers/docs/resources/channel#message-object-message-structure). This is a perfectly valid ask to me

Resolving patch:
diff --git a/discord/__init__.py b/discord/__init__.py
--- a/discord/__init__.py
+++ b/discord/__init__.py
@@ -33,7 +33,7 @@
 from .flags import *
 from .relationship import Relationship
 from .member import Member, VoiceState
-from .message import Message, Attachment
+from .message import Message, MessageReference, Attachment
 from .asset import Asset
 from .errors import *
 from .calls import CallMessage, GroupCall
diff --git a/discord/message.py b/discord/message.py
--- a/discord/message.py
+++ b/discord/message.py
@@ -208,6 +208,37 @@ async def to_file(self, *, use_cached=False, spoiler=False):
         data = await self.read(use_cached=use_cached)
         return File(io.BytesIO(data), filename=self.filename, spoiler=spoiler)
 
+class MessageReference:
+    """Represents a reference to a :class:`Message`.
+
+    .. versionadded:: 1.5
+
+    Attributes
+    -----------
+    message_id: Optional[:class:`int`]
+        The id of the message referenced.
+    channel_id: :class:`int`
+        The channel id of the message referenced.
+    guild_id: Optional[:class:`int`]
+        The guild id of the message referenced.
+    """
+
+    __slots__ = ('message_id', 'channel_id', 'guild_id', '_state')
+
+    def __init__(self, state, **kwargs):
+        self.message_id = utils._get_as_snowflake(kwargs, 'message_id')
+        self.channel_id = int(kwargs.pop('channel_id'))
+        self.guild_id = utils._get_as_snowflake(kwargs, 'guild_id')
+        self._state = state
+
+    @property
+    def cached_message(self):
+        """Optional[:class:`Message`]: The cached message, if found in the internal message cache."""
+        return self._state._get_message(self.message_id)
+
+    def __repr__(self):
+        return '<MessageReference message_id={0.message_id!r} channel_id={0.channel_id!r} guild_id={0.guild_id!r}>'.format(self)
+
 def flatten_handlers(cls):
     prefix = len('_handle_')
     cls._HANDLERS = {
@@ -251,6 +282,13 @@ class Message:
     call: Optional[:class:`CallMessage`]
         The call that the message refers to. This is only applicable to messages of type
         :attr:`MessageType.call`.
+    reference: Optional[:class:`MessageReference`]
+        The message that this message references. This is only applicable to messages of
+        type :attr:`MessageType.pins_add` or crossposted messages created by a
+        followed channel integration.
+
+        .. versionadded:: 1.5
+
     mention_everyone: :class:`bool`
         Specifies if the message mentions everyone.
 
@@ -316,7 +354,7 @@ class Message:
                  '_cs_channel_mentions', '_cs_raw_mentions', 'attachments',
                  '_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned',
                  'role_mentions', '_cs_raw_role_mentions', 'type', 'call', 'flags',
-                 '_cs_system_content', '_cs_guild', '_state', 'reactions',
+                 '_cs_system_content', '_cs_guild', '_state', 'reactions', 'reference', 
                  'application', 'activity')
 
     def __init__(self, *, state, channel, data):
@@ -338,6 +376,9 @@ def __init__(self, *, state, channel, data):
         self.content = data['content']
         self.nonce = data.get('nonce')
 
+        ref = data.get('message_reference')
+        self.reference = MessageReference(state, **ref) if ref is not None else None
+
         for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'flags'):
             try:
                 getattr(self, '_handle_%s' % handler)(data[handler])


Test patch: