We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Race condition in wait_for_deletion between a manual delete and reacting
Sentry Issue: [BOT-86](https://sentry.io/organizations/python-discord/issues/1861537034/?referrer=github_integration)

The message may be deleted before `wait_for_deletion` has a chance to add a reaction.

```
NotFound: 404 Not Found (error code: 10008): Unknown Message
  File "bot/utils/messages.py", line 38, in wait_for_deletion
    await message.add_reaction(emoji)
  File "discord/message.py", line 953, in add_reaction
    await self._state.http.add_reaction(self.channel.id, self.id, emoji)
  File "discord/http.py", line 243, in request
    raise NotFound(r, data)

Task exception was never retrieved
future: <Task finished name='Task-333258' coro=<wait_for_deletion() done, defined at /bot/bot/utils/messages.py:19> exception=NotFound('404 Not Found (error code: 10008): Unknown Message')>
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bot/utils/messages.py`
Content:
```
1 import asyncio
2 import contextlib
3 import logging
4 import random
5 import re
6 from io import BytesIO
7 from typing import List, Optional, Sequence, Union
8 
9 import discord
10 from discord.errors import HTTPException
11 from discord.ext.commands import Context
12 
13 from bot.constants import Emojis, NEGATIVE_REPLIES
14 
15 log = logging.getLogger(__name__)
16 
17 
18 async def wait_for_deletion(
19     message: discord.Message,
20     user_ids: Sequence[discord.abc.Snowflake],
21     client: discord.Client,
22     deletion_emojis: Sequence[str] = (Emojis.trashcan,),
23     timeout: float = 60 * 5,
24     attach_emojis: bool = True,
25 ) -> None:
26     """
27     Wait for up to `timeout` seconds for a reaction by any of the specified `user_ids` to delete the message.
28 
29     An `attach_emojis` bool may be specified to determine whether to attach the given
30     `deletion_emojis` to the message in the given `context`.
31     """
32     if message.guild is None:
33         raise ValueError("Message must be sent on a guild")
34 
35     if attach_emojis:
36         for emoji in deletion_emojis:
37             await message.add_reaction(emoji)
38 
39     def check(reaction: discord.Reaction, user: discord.Member) -> bool:
40         """Check that the deletion emoji is reacted by the appropriate user."""
41         return (
42             reaction.message.id == message.id
43             and str(reaction.emoji) in deletion_emojis
44             and user.id in user_ids
45         )
46 
47     with contextlib.suppress(asyncio.TimeoutError):
48         await client.wait_for('reaction_add', check=check, timeout=timeout)
49         await message.delete()
50 
51 
52 async def send_attachments(
53     message: discord.Message,
54     destination: Union[discord.TextChannel, discord.Webhook],
55     link_large: bool = True
56 ) -> List[str]:
57     """
58     Re-upload the message's attachments to the destination and return a list of their new URLs.
59 
60     Each attachment is sent as a separate message to more easily comply with the request/file size
61     limit. If link_large is True, attachments which are too large are instead grouped into a single
62     embed which links to them.
63     """
64     large = []
65     urls = []
66     for attachment in message.attachments:
67         failure_msg = (
68             f"Failed to re-upload attachment {attachment.filename} from message {message.id}"
69         )
70 
71         try:
72             # Allow 512 bytes of leeway for the rest of the request.
73             # This should avoid most files that are too large,
74             # but some may get through hence the try-catch.
75             if attachment.size <= destination.guild.filesize_limit - 512:
76                 with BytesIO() as file:
77                     await attachment.save(file, use_cached=True)
78                     attachment_file = discord.File(file, filename=attachment.filename)
79 
80                     if isinstance(destination, discord.TextChannel):
81                         msg = await destination.send(file=attachment_file)
82                         urls.append(msg.attachments[0].url)
83                     else:
84                         await destination.send(
85                             file=attachment_file,
86                             username=sub_clyde(message.author.display_name),
87                             avatar_url=message.author.avatar_url
88                         )
89             elif link_large:
90                 large.append(attachment)
91             else:
92                 log.info(f"{failure_msg} because it's too large.")
93         except HTTPException as e:
94             if link_large and e.status == 413:
95                 large.append(attachment)
96             else:
97                 log.warning(f"{failure_msg} with status {e.status}.", exc_info=e)
98 
99     if link_large and large:
100         desc = "\n".join(f"[{attachment.filename}]({attachment.url})" for attachment in large)
101         embed = discord.Embed(description=desc)
102         embed.set_footer(text="Attachments exceed upload size limit.")
103 
104         if isinstance(destination, discord.TextChannel):
105             await destination.send(embed=embed)
106         else:
107             await destination.send(
108                 embed=embed,
109                 username=sub_clyde(message.author.display_name),
110                 avatar_url=message.author.avatar_url
111             )
112 
113     return urls
114 
115 
116 def sub_clyde(username: Optional[str]) -> Optional[str]:
117     """
118     Replace "e"/"E" in any "clyde" in `username` with a Cyrillic "е"/"E" and return the new string.
119 
120     Discord disallows "clyde" anywhere in the username for webhooks. It will return a 400.
121     Return None only if `username` is None.
122     """
123     def replace_e(match: re.Match) -> str:
124         char = "е" if match[2] == "e" else "Е"
125         return match[1] + char
126 
127     if username:
128         return re.sub(r"(clyd)(e)", replace_e, username, flags=re.I)
129     else:
130         return username  # Empty string or None
131 
132 
133 async def send_denial(ctx: Context, reason: str) -> None:
134     """Send an embed denying the user with the given reason."""
135     embed = discord.Embed()
136     embed.colour = discord.Colour.red()
137     embed.title = random.choice(NEGATIVE_REPLIES)
138     embed.description = reason
139 
140     await ctx.send(embed=embed)
141 
142 
143 def format_user(user: discord.abc.User) -> str:
144     """Return a string for `user` which has their mention and ID."""
145     return f"{user.mention} (`{user.id}`)"
146
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bot/utils/messages.py b/bot/utils/messages.py
--- a/bot/utils/messages.py
+++ b/bot/utils/messages.py
@@ -34,7 +34,11 @@
 
     if attach_emojis:
         for emoji in deletion_emojis:
-            await message.add_reaction(emoji)
+            try:
+                await message.add_reaction(emoji)
+            except discord.NotFound:
+                log.trace(f"Aborting wait_for_deletion: message {message.id} deleted prematurely.")
+                return
 
     def check(reaction: discord.Reaction, user: discord.Member) -> bool:
         """Check that the deletion emoji is reacted by the appropriate user."""

{"golden_diff": "diff --git a/bot/utils/messages.py b/bot/utils/messages.py\n--- a/bot/utils/messages.py\n+++ b/bot/utils/messages.py\n@@ -34,7 +34,11 @@\n \n     if attach_emojis:\n         for emoji in deletion_emojis:\n-            await message.add_reaction(emoji)\n+            try:\n+                await message.add_reaction(emoji)\n+            except discord.NotFound:\n+                log.trace(f\"Aborting wait_for_deletion: message {message.id} deleted prematurely.\")\n+                return\n \n     def check(reaction: discord.Reaction, user: discord.Member) -> bool:\n         \"\"\"Check that the deletion emoji is reacted by the appropriate user.\"\"\"\n", "issue": "Race condition in wait_for_deletion between a manual delete and reacting\nSentry Issue: [BOT-86](https://sentry.io/organizations/python-discord/issues/1861537034/?referrer=github_integration)\r\n\r\nThe message may be deleted before `wait_for_deletion` has a chance to add a reaction.\r\n\r\n```\r\nNotFound: 404 Not Found (error code: 10008): Unknown Message\r\n  File \"bot/utils/messages.py\", line 38, in wait_for_deletion\r\n    await message.add_reaction(emoji)\r\n  File \"discord/message.py\", line 953, in add_reaction\r\n    await self._state.http.add_reaction(self.channel.id, self.id, emoji)\r\n  File \"discord/http.py\", line 243, in request\r\n    raise NotFound(r, data)\r\n\r\nTask exception was never retrieved\r\nfuture: <Task finished name='Task-333258' coro=<wait_for_deletion() done, defined at /bot/bot/utils/messages.py:19> exception=NotFound('404 Not Found (error code: 10008): Unknown Message')>\r\n```\n", "before_files": [{"content": "import asyncio\nimport contextlib\nimport logging\nimport random\nimport re\nfrom io import BytesIO\nfrom typing import List, Optional, Sequence, Union\n\nimport discord\nfrom discord.errors import HTTPException\nfrom discord.ext.commands import Context\n\nfrom bot.constants import Emojis, NEGATIVE_REPLIES\n\nlog = logging.getLogger(__name__)\n\n\nasync def wait_for_deletion(\n    message: discord.Message,\n    user_ids: Sequence[discord.abc.Snowflake],\n    client: discord.Client,\n    deletion_emojis: Sequence[str] = (Emojis.trashcan,),\n    timeout: float = 60 * 5,\n    attach_emojis: bool = True,\n) -> None:\n    \"\"\"\n    Wait for up to `timeout` seconds for a reaction by any of the specified `user_ids` to delete the message.\n\n    An `attach_emojis` bool may be specified to determine whether to attach the given\n    `deletion_emojis` to the message in the given `context`.\n    \"\"\"\n    if message.guild is None:\n        raise ValueError(\"Message must be sent on a guild\")\n\n    if attach_emojis:\n        for emoji in deletion_emojis:\n            await message.add_reaction(emoji)\n\n    def check(reaction: discord.Reaction, user: discord.Member) -> bool:\n        \"\"\"Check that the deletion emoji is reacted by the appropriate user.\"\"\"\n        return (\n            reaction.message.id == message.id\n            and str(reaction.emoji) in deletion_emojis\n            and user.id in user_ids\n        )\n\n    with contextlib.suppress(asyncio.TimeoutError):\n        await client.wait_for('reaction_add', check=check, timeout=timeout)\n        await message.delete()\n\n\nasync def send_attachments(\n    message: discord.Message,\n    destination: Union[discord.TextChannel, discord.Webhook],\n    link_large: bool = True\n) -> List[str]:\n    \"\"\"\n    Re-upload the message's attachments to the destination and return a list of their new URLs.\n\n    Each attachment is sent as a separate message to more easily comply with the request/file size\n    limit. If link_large is True, attachments which are too large are instead grouped into a single\n    embed which links to them.\n    \"\"\"\n    large = []\n    urls = []\n    for attachment in message.attachments:\n        failure_msg = (\n            f\"Failed to re-upload attachment {attachment.filename} from message {message.id}\"\n        )\n\n        try:\n            # Allow 512 bytes of leeway for the rest of the request.\n            # This should avoid most files that are too large,\n            # but some may get through hence the try-catch.\n            if attachment.size <= destination.guild.filesize_limit - 512:\n                with BytesIO() as file:\n                    await attachment.save(file, use_cached=True)\n                    attachment_file = discord.File(file, filename=attachment.filename)\n\n                    if isinstance(destination, discord.TextChannel):\n                        msg = await destination.send(file=attachment_file)\n                        urls.append(msg.attachments[0].url)\n                    else:\n                        await destination.send(\n                            file=attachment_file,\n                            username=sub_clyde(message.author.display_name),\n                            avatar_url=message.author.avatar_url\n                        )\n            elif link_large:\n                large.append(attachment)\n            else:\n                log.info(f\"{failure_msg} because it's too large.\")\n        except HTTPException as e:\n            if link_large and e.status == 413:\n                large.append(attachment)\n            else:\n                log.warning(f\"{failure_msg} with status {e.status}.\", exc_info=e)\n\n    if link_large and large:\n        desc = \"\\n\".join(f\"[{attachment.filename}]({attachment.url})\" for attachment in large)\n        embed = discord.Embed(description=desc)\n        embed.set_footer(text=\"Attachments exceed upload size limit.\")\n\n        if isinstance(destination, discord.TextChannel):\n            await destination.send(embed=embed)\n        else:\n            await destination.send(\n                embed=embed,\n                username=sub_clyde(message.author.display_name),\n                avatar_url=message.author.avatar_url\n            )\n\n    return urls\n\n\ndef sub_clyde(username: Optional[str]) -> Optional[str]:\n    \"\"\"\n    Replace \"e\"/\"E\" in any \"clyde\" in `username` with a Cyrillic \"\u0435\"/\"E\" and return the new string.\n\n    Discord disallows \"clyde\" anywhere in the username for webhooks. It will return a 400.\n    Return None only if `username` is None.\n    \"\"\"\n    def replace_e(match: re.Match) -> str:\n        char = \"\u0435\" if match[2] == \"e\" else \"\u0415\"\n        return match[1] + char\n\n    if username:\n        return re.sub(r\"(clyd)(e)\", replace_e, username, flags=re.I)\n    else:\n        return username  # Empty string or None\n\n\nasync def send_denial(ctx: Context, reason: str) -> None:\n    \"\"\"Send an embed denying the user with the given reason.\"\"\"\n    embed = discord.Embed()\n    embed.colour = discord.Colour.red()\n    embed.title = random.choice(NEGATIVE_REPLIES)\n    embed.description = reason\n\n    await ctx.send(embed=embed)\n\n\ndef format_user(user: discord.abc.User) -> str:\n    \"\"\"Return a string for `user` which has their mention and ID.\"\"\"\n    return f\"{user.mention} (`{user.id}`)\"\n", "path": "bot/utils/messages.py"}], "after_files": [{"content": "import asyncio\nimport contextlib\nimport logging\nimport random\nimport re\nfrom io import BytesIO\nfrom typing import List, Optional, Sequence, Union\n\nimport discord\nfrom discord.errors import HTTPException\nfrom discord.ext.commands import Context\n\nfrom bot.constants import Emojis, NEGATIVE_REPLIES\n\nlog = logging.getLogger(__name__)\n\n\nasync def wait_for_deletion(\n    message: discord.Message,\n    user_ids: Sequence[discord.abc.Snowflake],\n    client: discord.Client,\n    deletion_emojis: Sequence[str] = (Emojis.trashcan,),\n    timeout: float = 60 * 5,\n    attach_emojis: bool = True,\n) -> None:\n    \"\"\"\n    Wait for up to `timeout` seconds for a reaction by any of the specified `user_ids` to delete the message.\n\n    An `attach_emojis` bool may be specified to determine whether to attach the given\n    `deletion_emojis` to the message in the given `context`.\n    \"\"\"\n    if message.guild is None:\n        raise ValueError(\"Message must be sent on a guild\")\n\n    if attach_emojis:\n        for emoji in deletion_emojis:\n            try:\n                await message.add_reaction(emoji)\n            except discord.NotFound:\n                log.trace(f\"Aborting wait_for_deletion: message {message.id} deleted prematurely.\")\n                return\n\n    def check(reaction: discord.Reaction, user: discord.Member) -> bool:\n        \"\"\"Check that the deletion emoji is reacted by the appropriate user.\"\"\"\n        return (\n            reaction.message.id == message.id\n            and str(reaction.emoji) in deletion_emojis\n            and user.id in user_ids\n        )\n\n    with contextlib.suppress(asyncio.TimeoutError):\n        await client.wait_for('reaction_add', check=check, timeout=timeout)\n        await message.delete()\n\n\nasync def send_attachments(\n    message: discord.Message,\n    destination: Union[discord.TextChannel, discord.Webhook],\n    link_large: bool = True\n) -> List[str]:\n    \"\"\"\n    Re-upload the message's attachments to the destination and return a list of their new URLs.\n\n    Each attachment is sent as a separate message to more easily comply with the request/file size\n    limit. If link_large is True, attachments which are too large are instead grouped into a single\n    embed which links to them.\n    \"\"\"\n    large = []\n    urls = []\n    for attachment in message.attachments:\n        failure_msg = (\n            f\"Failed to re-upload attachment {attachment.filename} from message {message.id}\"\n        )\n\n        try:\n            # Allow 512 bytes of leeway for the rest of the request.\n            # This should avoid most files that are too large,\n            # but some may get through hence the try-catch.\n            if attachment.size <= destination.guild.filesize_limit - 512:\n                with BytesIO() as file:\n                    await attachment.save(file, use_cached=True)\n                    attachment_file = discord.File(file, filename=attachment.filename)\n\n                    if isinstance(destination, discord.TextChannel):\n                        msg = await destination.send(file=attachment_file)\n                        urls.append(msg.attachments[0].url)\n                    else:\n                        await destination.send(\n                            file=attachment_file,\n                            username=sub_clyde(message.author.display_name),\n                            avatar_url=message.author.avatar_url\n                        )\n            elif link_large:\n                large.append(attachment)\n            else:\n                log.info(f\"{failure_msg} because it's too large.\")\n        except HTTPException as e:\n            if link_large and e.status == 413:\n                large.append(attachment)\n            else:\n                log.warning(f\"{failure_msg} with status {e.status}.\", exc_info=e)\n\n    if link_large and large:\n        desc = \"\\n\".join(f\"[{attachment.filename}]({attachment.url})\" for attachment in large)\n        embed = discord.Embed(description=desc)\n        embed.set_footer(text=\"Attachments exceed upload size limit.\")\n\n        if isinstance(destination, discord.TextChannel):\n            await destination.send(embed=embed)\n        else:\n            await destination.send(\n                embed=embed,\n                username=sub_clyde(message.author.display_name),\n                avatar_url=message.author.avatar_url\n            )\n\n    return urls\n\n\ndef sub_clyde(username: Optional[str]) -> Optional[str]:\n    \"\"\"\n    Replace \"e\"/\"E\" in any \"clyde\" in `username` with a Cyrillic \"\u0435\"/\"E\" and return the new string.\n\n    Discord disallows \"clyde\" anywhere in the username for webhooks. It will return a 400.\n    Return None only if `username` is None.\n    \"\"\"\n    def replace_e(match: re.Match) -> str:\n        char = \"\u0435\" if match[2] == \"e\" else \"\u0415\"\n        return match[1] + char\n\n    if username:\n        return re.sub(r\"(clyd)(e)\", replace_e, username, flags=re.I)\n    else:\n        return username  # Empty string or None\n\n\nasync def send_denial(ctx: Context, reason: str) -> None:\n    \"\"\"Send an embed denying the user with the given reason.\"\"\"\n    embed = discord.Embed()\n    embed.colour = discord.Colour.red()\n    embed.title = random.choice(NEGATIVE_REPLIES)\n    embed.description = reason\n\n    await ctx.send(embed=embed)\n\n\ndef format_user(user: discord.abc.User) -> str:\n    \"\"\"Return a string for `user` which has their mention and ID.\"\"\"\n    return f\"{user.mention} (`{user.id}`)\"\n", "path": "bot/utils/messages.py"}]}