Repository: Pycord-Development/pycord

Issue:
Message Interaction Object missing from discord.Message object
### Summary

Add interaction object to message object

### What is the feature request for?

The core library

### The Problem

The message object the library returns is currently missing the message interaction object. Not sure about all the cases this is available but this allows you to see on messages sent by bots which user triggered the response and what application command was used to trigger it.

To reproduce, create an `on_message` event, catch a bot message that gets triggered by an application command and inspect the message object -> no interaction.

I tried this in `on_message`, `on_message_edit` and also tried manually fetching the message again with `channel.fetch_message`, all of the returned message objects do not have the interaction object attached.

If I get the same message with `raw_message = await bot.http.get_message(channel.id, message.id)`, the interaction is included in the raw JSON.

See also API docs: https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object.

### The Ideal Solution

Add the interaction data to the message object whenever available.

### The Current Solution

My current workaround is to use `raw_message = await bot.http.get_message(channel.id, message.id)` and then parse myself. However, this is an unnecessary API call.

### Additional Context

_No response_


Discussion:

Resolving patch:
diff --git a/discord/interactions.py b/discord/interactions.py
--- a/discord/interactions.py
+++ b/discord/interactions.py
@@ -27,17 +27,15 @@
 
 from __future__ import annotations
 from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
-import asyncio
 
 from . import utils
 from .enums import try_enum, InteractionType, InteractionResponseType
 from .errors import InteractionResponded, ClientException, InvalidArgument
-from .channel import PartialMessageable, ChannelType
+from .channel import ChannelType
 from .file import File
 from .user import User
 from .member import Member
 from .message import Message, Attachment
-from .mentions import AllowedMentions
 from .object import Object
 from .permissions import Permissions
 from .webhook.async_ import async_context, Webhook, handle_message_parameters
@@ -46,12 +44,14 @@
     'Interaction',
     'InteractionMessage',
     'InteractionResponse',
+    'MessageInteraction',
 )
 
 if TYPE_CHECKING:
     from .types.interactions import (
         Interaction as InteractionPayload,
         InteractionData,
+        MessageInteraction as MessageInteractionPayload
     )
     from .guild import Guild
     from .state import ConnectionState
@@ -422,7 +422,6 @@ def is_done(self) -> bool:
         An interaction can only be responded to once.
         """
         return self._responded
-        
 
     async def defer(self, *, ephemeral: bool = False) -> None:
         """|coro|
@@ -899,3 +898,46 @@ async def delete(self, *, delay: Optional[float] = None) -> None:
             Deleting the message failed.
         """
         await self._state._interaction.delete_original_message(delay=delay)
+
+
+class MessageInteraction:
+    """Represents a Discord message interaction.
+
+    This is sent on the message object when the message is a response
+    to an interaction without an existing message e.g. application command.
+
+    .. versionadded:: 2.0
+
+    .. note::
+        Responses to message components do not include this property.
+
+    Attributes
+    -----------
+    id: :class:`int`
+        The interaction's ID.
+    type: :class:`InteractionType`
+        The interaction type.
+    name: :class:`str`
+        The name of the invoked application command.
+    user: :class:`User`
+        The user that sent the interaction.
+    data: :class:`dict`
+        The raw interaction data.
+    """
+
+    __slots__: Tuple[str, ...] = (
+        'id',
+        'type',
+        'name',
+        'user',
+        'data',
+        '_state'
+    )
+
+    def __init__(self, *, data: MessageInteractionPayload, state: ConnectionState):
+        self._state = state
+        self.data = data
+        self.id: int = int(data['id'])
+        self.type: InteractionType = data['type']
+        self.name: str = data['name']
+        self.user: User = self._state.store_user(data['user'])
diff --git a/discord/message.py b/discord/message.py
--- a/discord/message.py
+++ b/discord/message.py
@@ -29,7 +29,7 @@
 import re
 import io
 from os import PathLike
-from typing import Dict, TYPE_CHECKING, Union, List, Optional, Any, Callable, Tuple, ClassVar, Optional, overload, TypeVar, Type, Sequence
+from typing import Dict, TYPE_CHECKING, Union, List, Any, Callable, Tuple, ClassVar, Optional, overload, TypeVar, Type, Sequence
 
 from . import utils
 from .reaction import Reaction
@@ -611,6 +611,8 @@ class Message(Hashable):
         .. versionadded:: 2.0
     guild: Optional[:class:`Guild`]
         The guild that the message belongs to, if applicable.
+    interaction: Optional[:class:`MessageInteraction`]
+        The interaction associated with the message, if applicable.
     """
 
     __slots__ = (
@@ -644,6 +646,7 @@ class Message(Hashable):
         'stickers',
         'components',
         'guild',
+        'interaction',
     )
 
     if TYPE_CHECKING:
@@ -711,6 +714,13 @@ def __init__(
                     # the channel will be the correct type here
                     ref.resolved = self.__class__(channel=chan, data=resolved, state=state)  # type: ignore
 
+        from .interactions import MessageInteraction
+        self.interaction: Optional[MessageInteraction]
+        try:
+            self.interaction = MessageInteraction(data=data['interaction'], state=state)
+        except KeyError:
+            self.interaction = None
+
         for handler in ('author', 'member', 'mentions', 'mention_roles'):
             try:
                 getattr(self, f'_handle_{handler}')(data[handler])
diff --git a/discord/types/interactions.py b/discord/types/interactions.py
--- a/discord/types/interactions.py
+++ b/discord/types/interactions.py
@@ -225,9 +225,6 @@ class MessageInteraction(TypedDict):
     user: User
 
 
-
-
-
 class _EditApplicationCommandOptional(TypedDict, total=False):
     description: str
     options: Optional[List[ApplicationCommandOption]]


Test patch: