Repository: python-discord/bot

Issue:
ID not displayed in dm_relay when attachment sent
Currently the dm-relay message username will not include the user ID if only an attachment is sent, so it looks like this:
![image](https://user-images.githubusercontent.com/22353562/91665752-da5f8d80-eaef-11ea-9e91-c710ed977056.png)
Whereas it should look like this:
![image](https://user-images.githubusercontent.com/22353562/91665710-91a7d480-eaef-11ea-9b0e-9a52672647f1.png)

This is because it uses [`bot.utils.messages.send_attachments`](https://github.com/python-discord/bot/blob/1bd5c80e7b2aa90162639b86960c66f6a218022f/bot/utils/messages.py#L53), which by default just sends the message with the ID. Relevant line in `dm_relay` cog [here](https://github.com/python-discord/bot/blob/1bd5c80e7b2aa90162639b86960c66f6a218022f/bot/cogs/dm_relay.py#L93)

Simple solution here would be to add a `username` keyword argument to `send_attachments`. I would be happy to make this change if this all sounds good.

415 error for attachment re-upload
Sentry issue: [BOT-6V](https://sentry.io/organizations/python-discord/issues/1780462718/?referrer=github_integration)

Strange issue that has happened 6 times in which a attachment fails to be uploaded due to a 415 (unsupported media type) error.

My suspicion is that it's not due to a misconfigured `Content-Type` header as one would expect but rather Discord's API using that error to reject certain files. I don't know how `Content-Type` could possibly be wrong.

This has happened for both `.py` and `.txt` files so far.


Discussion:
How about supporting arbitrary kwargs to pass on to `send()`? An explicit `username` kwarg is kinda odd since it will only be useful if a webhook is given as the destination. It's not too bad though.
Sure, supporting arbitrary kwargs sounds like a good idea to me.
The log reads:
```
Failed to re-upload attachment message.txt from message <message ID> with status 415.
```

The relevant file is:
https://github.com/python-discord/bot/blob/687f2c4c5875e9de2abeeb122eeee529816ee204/bot/utils/messages.py#L100
Reproduced this locally with setting up Big Brother watch and sending text files.
The problem is with discord.py's handling of the `use_cached=True` kwarg here
https://github.com/python-discord/bot/blob/687f2c4c5875e9de2abeeb122eeee529816ee204/bot/utils/messages.py#L80
For getting cached, it uses the `proxy_url` of the attachment which in case of text (non media) files seems to be invalid as they are not processed on the media server to be shown to the user.

How should this be handled? Only use the cache for accepted image formats?
> How should this be handled? Only use the cache for accepted image formats?

Try to use the cache. If it fails with a 415, then try again without a cache. We can't check for accepted formats since it's based on whether the file itself is valid rather than on its extension. For example, this error was triggered by someone uploading an empty text file with an "mp3" extension. In any case, I imagine it'd be hard to maintain an exhaustive list of supported formats.

Resolving patch:
diff --git a/bot/exts/moderation/dm_relay.py b/bot/exts/moderation/dm_relay.py
--- a/bot/exts/moderation/dm_relay.py
+++ b/bot/exts/moderation/dm_relay.py
@@ -90,7 +90,11 @@ async def on_message(self, message: discord.Message) -> None:
         # Handle any attachments
         if message.attachments:
             try:
-                await send_attachments(message, self.webhook)
+                await send_attachments(
+                    message,
+                    self.webhook,
+                    username=f"{message.author.display_name} ({message.author.id})"
+                )
             except (discord.errors.Forbidden, discord.errors.NotFound):
                 e = discord.Embed(
                     description=":x: **This message contained an attachment, but it could not be retrieved**",
diff --git a/bot/utils/messages.py b/bot/utils/messages.py
--- a/bot/utils/messages.py
+++ b/bot/utils/messages.py
@@ -56,15 +56,24 @@ def check(reaction: discord.Reaction, user: discord.Member) -> bool:
 async def send_attachments(
     message: discord.Message,
     destination: Union[discord.TextChannel, discord.Webhook],
-    link_large: bool = True
+    link_large: bool = True,
+    use_cached: bool = False,
+    **kwargs
 ) -> List[str]:
     """
     Re-upload the message's attachments to the destination and return a list of their new URLs.
 
     Each attachment is sent as a separate message to more easily comply with the request/file size
     limit. If link_large is True, attachments which are too large are instead grouped into a single
-    embed which links to them.
+    embed which links to them. Extra kwargs will be passed to send() when sending the attachment.
     """
+    webhook_send_kwargs = {
+        'username': message.author.display_name,
+        'avatar_url': message.author.avatar_url,
+    }
+    webhook_send_kwargs.update(kwargs)
+    webhook_send_kwargs['username'] = sub_clyde(webhook_send_kwargs['username'])
+
     large = []
     urls = []
     for attachment in message.attachments:
@@ -78,18 +87,14 @@ async def send_attachments(
             # but some may get through hence the try-catch.
             if attachment.size <= destination.guild.filesize_limit - 512:
                 with BytesIO() as file:
-                    await attachment.save(file, use_cached=True)
+                    await attachment.save(file, use_cached=use_cached)
                     attachment_file = discord.File(file, filename=attachment.filename)
 
                     if isinstance(destination, discord.TextChannel):
-                        msg = await destination.send(file=attachment_file)
+                        msg = await destination.send(file=attachment_file, **kwargs)
                         urls.append(msg.attachments[0].url)
                     else:
-                        await destination.send(
-                            file=attachment_file,
-                            username=sub_clyde(message.author.display_name),
-                            avatar_url=message.author.avatar_url
-                        )
+                        await destination.send(file=attachment_file, **webhook_send_kwargs)
             elif link_large:
                 large.append(attachment)
             else:
@@ -106,13 +111,9 @@ async def send_attachments(
         embed.set_footer(text="Attachments exceed upload size limit.")
 
         if isinstance(destination, discord.TextChannel):
-            await destination.send(embed=embed)
+            await destination.send(embed=embed, **kwargs)
         else:
-            await destination.send(
-                embed=embed,
-                username=sub_clyde(message.author.display_name),
-                avatar_url=message.author.avatar_url
-            )
+            await destination.send(embed=embed, **webhook_send_kwargs)
 
     return urls
 


Test patch: