Repository: Pycord-Development/pycord

Issue:
MessageCommand raises an error when used on Bot/System messages in DM's
### Summary

When calling a MessageCommand (context menu) in a DM with the bot, and the message was either sent by the bot or is a system message, an error is raised & the interaction fails.

### Reproduction Steps

- Create a global MessageCommand
- Go to your DM's with the bot
- Invoke the command on one of the bot's messages

### Minimal Reproducible Code

```python
# (Example for a cog to be added to the bot)


from discord.ext import commands
from discord.commands import message_command
class Example(commands.Cog):
    def __init__(self, client):
        self.client = client

    @message_command(name="Example")
    async def _example_mc(self, ctx, message):
        pass
```


### Expected Results

The command is invoked on the message on which it was selected, and the interaction succeeds*. This is the case for both your own messages, and the bot's messages.

*assuming the content of the command is valid, the interaction should succeed. In my reproducible example it will still fail because there is no response, but the content was left out because it's not relevant here.

### Actual Results

Exception raised when used on System messages or messages sent by the bot:
```
The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/stijn/Documents/didier/venv/lib/python3.9/site-packages/discord/client.py", line 352, in _run_event
    await coro(*args, **kwargs)
  File "/home/stijn/Documents/didier/cogs/events.py", line 146, in on_application_command_error
    raise err
  File "/home/stijn/Documents/didier/venv/lib/python3.9/site-packages/discord/bot.py", line 520, in process_application_commands
    await ctx.command.invoke(ctx)
  File "/home/stijn/Documents/didier/venv/lib/python3.9/site-packages/discord/commands/core.py", line 306, in invoke
    await injected(ctx)
  File "/home/stijn/Documents/didier/venv/lib/python3.9/site-packages/discord/commands/core.py", line 116, in wrapped
    raise ApplicationCommandInvokeError(exc) from exc
discord.commands.errors.ApplicationCommandInvokeError: Application Command raised an exception: HTTPException: 400 Bad Request (error code: 50007): Cannot send messages to this user
```

### Intents

default + members

### System Information

- Python v3.9.5-final
- py-cord v2.0.0-beta
    - py-cord pkg_resources: v2.0.0b1
- aiohttp v3.7.4.post0
- system info: Linux 4.15.0-58-generic #64-Ubuntu SMP Tue Aug 6 11:12:41 UTC 2019

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

Important note: I am aware you can't DM bots (or the "author" of system messages). **I'm not trying to**. This is something done internally **upon command invocation**, so **before** my own function starts executing. For that reason, the body of the command doesn't matter.

It looks like the lib tries to create a DM with the author of the message for some reason. The relevant lines are (at the creation of this issue):

https://github.com/Pycord-Development/pycord/blob/a553809b0f013676745c2cd8b16fcd7f77dbe827/discord/commands/core.py#L1353-L1359

# Proposed solution

When the channel is `None`, check if the message was sent by the bot/system. If yes, the channel should be constructed from the person that invoked the command (which can be found in the `ctx` parameter) instead of `message["author"]["id"]` (which doesn't exist). If not, follow the current procedure.

A user can only invoke a DM-message in their own DM's, for obvious reasons, so this shouldn't make any difference. The target should be the DM channel between the user and the bot, so constructing it from the user that invoked it puts us in the right spot.

**Note** that this might have side effects that I'm unaware of, as I'm unfamiliar with the internals of the library, so a better solution might be to explicitly check if the interaction was invoked in a DM instead of assuming that it always is when `channel` is `None`.


Discussion:

Resolving patch:
diff --git a/discord/commands/core.py b/discord/commands/core.py
--- a/discord/commands/core.py
+++ b/discord/commands/core.py
@@ -52,7 +52,7 @@
 from ..role import Role
 from ..object import Object
 from ..channel import _guild_channel_factory
-from ..enums import ChannelType, SlashCommandOptionType
+from ..enums import ChannelType, MessageType, SlashCommandOptionType, try_enum
 from ..errors import (
     ClientException,
     ValidationError,
@@ -1418,7 +1418,18 @@ async def _invoke(self, ctx: ApplicationContext):
             message = v
         channel = ctx.interaction._state.get_channel(int(message["channel_id"]))
         if channel is None:
-            data = await ctx.interaction._state.http.start_private_message(int(message["author"]["id"]))
+            author_id = int(message["author"]["id"])
+            self_or_system_message: bool = (
+                ctx.bot.user.id == author_id
+                or try_enum(MessageType, message["type"]) not in (
+                    MessageType.default,
+                    MessageType.reply,
+                    MessageType.application_command,
+                    MessageType.thread_starter_message,
+                )
+            )
+            user_id = ctx.author.id if self_or_system_message else author_id
+            data = await ctx.interaction._state.http.start_private_message(user_id)
             channel = ctx.interaction._state.add_dm_channel(data)
 
         target = Message(state=ctx.interaction._state, channel=channel, data=message)


Test patch: