We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
IndexError when re-evaluating snekbox code
Sentry Issue: [BOT-2C](https://sentry.io/organizations/python-discord/issues/1567544008/?referrer=github_integration)

```
IndexError: list index out of range
  File "discord/ext/commands/core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "bot/cogs/snekbox.py", line 281, in eval_command
    code = await self.continue_eval(ctx, response)
  File "bot/cogs/snekbox.py", line 235, in continue_eval
    code = new_message.content.split(' ', maxsplit=1)[1]
```

Using `get_context` seems to be a more robust option than parsing the message ourselves. Either way, if it can't parse it, it should just assume the entire content is the code.

--- 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/cogs/snekbox.py`
Content:
```
1 import asyncio
2 import contextlib
3 import datetime
4 import logging
5 import re
6 import textwrap
7 from functools import partial
8 from signal import Signals
9 from typing import Optional, Tuple
10 
11 from discord import HTTPException, Message, NotFound, Reaction, User
12 from discord.ext.commands import Cog, Context, command, guild_only
13 
14 from bot.bot import Bot
15 from bot.constants import Channels, Roles, URLs
16 from bot.decorators import in_channel
17 from bot.utils.messages import wait_for_deletion
18 
19 log = logging.getLogger(__name__)
20 
21 ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}")
22 FORMATTED_CODE_REGEX = re.compile(
23     r"^\s*"                                 # any leading whitespace from the beginning of the string
24     r"(?P<delim>(?P<block>```)|``?)"        # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block
25     r"(?(block)(?:(?P<lang>[a-z]+)\n)?)"    # if we're in a block, match optional language (only letters plus newline)
26     r"(?:[ \t]*\n)*"                        # any blank (empty or tabs/spaces only) lines before the code
27     r"(?P<code>.*?)"                        # extract all code inside the markup
28     r"\s*"                                  # any more whitespace before the end of the code markup
29     r"(?P=delim)"                           # match the exact same delimiter from the start again
30     r"\s*$",                                # any trailing whitespace until the end of the string
31     re.DOTALL | re.IGNORECASE               # "." also matches newlines, case insensitive
32 )
33 RAW_CODE_REGEX = re.compile(
34     r"^(?:[ \t]*\n)*"                       # any blank (empty or tabs/spaces only) lines before the code
35     r"(?P<code>.*?)"                        # extract all the rest as code
36     r"\s*$",                                # any trailing whitespace until the end of the string
37     re.DOTALL                               # "." also matches newlines
38 )
39 
40 MAX_PASTE_LEN = 1000
41 EVAL_ROLES = (Roles.helpers, Roles.moderators, Roles.admins, Roles.owners, Roles.python_community, Roles.partners)
42 
43 SIGKILL = 9
44 
45 REEVAL_EMOJI = '\U0001f501'  # :repeat:
46 
47 
48 class Snekbox(Cog):
49     """Safe evaluation of Python code using Snekbox."""
50 
51     def __init__(self, bot: Bot):
52         self.bot = bot
53         self.jobs = {}
54 
55     async def post_eval(self, code: str) -> dict:
56         """Send a POST request to the Snekbox API to evaluate code and return the results."""
57         url = URLs.snekbox_eval_api
58         data = {"input": code}
59         async with self.bot.http_session.post(url, json=data, raise_for_status=True) as resp:
60             return await resp.json()
61 
62     async def upload_output(self, output: str) -> Optional[str]:
63         """Upload the eval output to a paste service and return a URL to it if successful."""
64         log.trace("Uploading full output to paste service...")
65 
66         if len(output) > MAX_PASTE_LEN:
67             log.info("Full output is too long to upload")
68             return "too long to upload"
69 
70         url = URLs.paste_service.format(key="documents")
71         try:
72             async with self.bot.http_session.post(url, data=output, raise_for_status=True) as resp:
73                 data = await resp.json()
74 
75             if "key" in data:
76                 return URLs.paste_service.format(key=data["key"])
77         except Exception:
78             # 400 (Bad Request) means there are too many characters
79             log.exception("Failed to upload full output to paste service!")
80 
81     @staticmethod
82     def prepare_input(code: str) -> str:
83         """Extract code from the Markdown, format it, and insert it into the code template."""
84         match = FORMATTED_CODE_REGEX.fullmatch(code)
85         if match:
86             code, block, lang, delim = match.group("code", "block", "lang", "delim")
87             code = textwrap.dedent(code)
88             if block:
89                 info = (f"'{lang}' highlighted" if lang else "plain") + " code block"
90             else:
91                 info = f"{delim}-enclosed inline code"
92             log.trace(f"Extracted {info} for evaluation:\n{code}")
93         else:
94             code = textwrap.dedent(RAW_CODE_REGEX.fullmatch(code).group("code"))
95             log.trace(
96                 f"Eval message contains unformatted or badly formatted code, "
97                 f"stripping whitespace only:\n{code}"
98             )
99 
100         return code
101 
102     @staticmethod
103     def get_results_message(results: dict) -> Tuple[str, str]:
104         """Return a user-friendly message and error corresponding to the process's return code."""
105         stdout, returncode = results["stdout"], results["returncode"]
106         msg = f"Your eval job has completed with return code {returncode}"
107         error = ""
108 
109         if returncode is None:
110             msg = "Your eval job has failed"
111             error = stdout.strip()
112         elif returncode == 128 + SIGKILL:
113             msg = "Your eval job timed out or ran out of memory"
114         elif returncode == 255:
115             msg = "Your eval job has failed"
116             error = "A fatal NsJail error occurred"
117         else:
118             # Try to append signal's name if one exists
119             try:
120                 name = Signals(returncode - 128).name
121                 msg = f"{msg} ({name})"
122             except ValueError:
123                 pass
124 
125         return msg, error
126 
127     @staticmethod
128     def get_status_emoji(results: dict) -> str:
129         """Return an emoji corresponding to the status code or lack of output in result."""
130         if not results["stdout"].strip():  # No output
131             return ":warning:"
132         elif results["returncode"] == 0:  # No error
133             return ":white_check_mark:"
134         else:  # Exception
135             return ":x:"
136 
137     async def format_output(self, output: str) -> Tuple[str, Optional[str]]:
138         """
139         Format the output and return a tuple of the formatted output and a URL to the full output.
140 
141         Prepend each line with a line number. Truncate if there are over 10 lines or 1000 characters
142         and upload the full output to a paste service.
143         """
144         log.trace("Formatting output...")
145 
146         output = output.rstrip("\n")
147         original_output = output  # To be uploaded to a pasting service if needed
148         paste_link = None
149 
150         if "<@" in output:
151             output = output.replace("<@", "<@\u200B")  # Zero-width space
152 
153         if "<!@" in output:
154             output = output.replace("<!@", "<!@\u200B")  # Zero-width space
155 
156         if ESCAPE_REGEX.findall(output):
157             return "Code block escape attempt detected; will not output result", paste_link
158 
159         truncated = False
160         lines = output.count("\n")
161 
162         if lines > 0:
163             output = [f"{i:03d} | {line}" for i, line in enumerate(output.split('\n'), 1)]
164             output = output[:11]  # Limiting to only 11 lines
165             output = "\n".join(output)
166 
167         if lines > 10:
168             truncated = True
169             if len(output) >= 1000:
170                 output = f"{output[:1000]}\n... (truncated - too long, too many lines)"
171             else:
172                 output = f"{output}\n... (truncated - too many lines)"
173         elif len(output) >= 1000:
174             truncated = True
175             output = f"{output[:1000]}\n... (truncated - too long)"
176 
177         if truncated:
178             paste_link = await self.upload_output(original_output)
179 
180         output = output or "[No output]"
181 
182         return output, paste_link
183 
184     async def send_eval(self, ctx: Context, code: str) -> Message:
185         """
186         Evaluate code, format it, and send the output to the corresponding channel.
187 
188         Return the bot response.
189         """
190         async with ctx.typing():
191             results = await self.post_eval(code)
192             msg, error = self.get_results_message(results)
193 
194             if error:
195                 output, paste_link = error, None
196             else:
197                 output, paste_link = await self.format_output(results["stdout"])
198 
199             icon = self.get_status_emoji(results)
200             msg = f"{ctx.author.mention} {icon} {msg}.\n\n```py\n{output}\n```"
201             if paste_link:
202                 msg = f"{msg}\nFull output: {paste_link}"
203 
204             response = await ctx.send(msg)
205             self.bot.loop.create_task(
206                 wait_for_deletion(response, user_ids=(ctx.author.id,), client=ctx.bot)
207             )
208 
209             log.info(f"{ctx.author}'s job had a return code of {results['returncode']}")
210         return response
211 
212     async def continue_eval(self, ctx: Context, response: Message) -> Optional[str]:
213         """
214         Check if the eval session should continue.
215 
216         Return the new code to evaluate or None if the eval session should be terminated.
217         """
218         _predicate_eval_message_edit = partial(predicate_eval_message_edit, ctx)
219         _predicate_emoji_reaction = partial(predicate_eval_emoji_reaction, ctx)
220 
221         with contextlib.suppress(NotFound):
222             try:
223                 _, new_message = await self.bot.wait_for(
224                     'message_edit',
225                     check=_predicate_eval_message_edit,
226                     timeout=10
227                 )
228                 await ctx.message.add_reaction(REEVAL_EMOJI)
229                 await self.bot.wait_for(
230                     'reaction_add',
231                     check=_predicate_emoji_reaction,
232                     timeout=10
233                 )
234 
235                 code = new_message.content.split(' ', maxsplit=1)[1]
236                 await ctx.message.clear_reactions()
237                 with contextlib.suppress(HTTPException):
238                     await response.delete()
239 
240             except asyncio.TimeoutError:
241                 await ctx.message.clear_reactions()
242                 return None
243 
244             return code
245 
246     @command(name="eval", aliases=("e",))
247     @guild_only()
248     @in_channel(Channels.bot_commands, hidden_channels=(Channels.esoteric,), bypass_roles=EVAL_ROLES)
249     async def eval_command(self, ctx: Context, *, code: str = None) -> None:
250         """
251         Run Python code and get the results.
252 
253         This command supports multiple lines of code, including code wrapped inside a formatted code
254         block. Code can be re-evaluated by editing the original message within 10 seconds and
255         clicking the reaction that subsequently appears.
256 
257         We've done our best to make this sandboxed, but do let us know if you manage to find an
258         issue with it!
259         """
260         if ctx.author.id in self.jobs:
261             await ctx.send(
262                 f"{ctx.author.mention} You've already got a job running - "
263                 "please wait for it to finish!"
264             )
265             return
266 
267         if not code:  # None or empty string
268             await ctx.invoke(self.bot.get_command("help"), "eval")
269             return
270 
271         log.info(f"Received code from {ctx.author} for evaluation:\n{code}")
272 
273         while True:
274             self.jobs[ctx.author.id] = datetime.datetime.now()
275             code = self.prepare_input(code)
276             try:
277                 response = await self.send_eval(ctx, code)
278             finally:
279                 del self.jobs[ctx.author.id]
280 
281             code = await self.continue_eval(ctx, response)
282             if not code:
283                 break
284             log.info(f"Re-evaluating message {ctx.message.id}")
285 
286 
287 def predicate_eval_message_edit(ctx: Context, old_msg: Message, new_msg: Message) -> bool:
288     """Return True if the edited message is the context message and the content was indeed modified."""
289     return new_msg.id == ctx.message.id and old_msg.content != new_msg.content
290 
291 
292 def predicate_eval_emoji_reaction(ctx: Context, reaction: Reaction, user: User) -> bool:
293     """Return True if the reaction REEVAL_EMOJI was added by the context message author on this message."""
294     return reaction.message.id == ctx.message.id and user.id == ctx.author.id and str(reaction) == REEVAL_EMOJI
295 
296 
297 def setup(bot: Bot) -> None:
298     """Load the Snekbox cog."""
299     bot.add_cog(Snekbox(bot))
300
```
--- 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/cogs/snekbox.py b/bot/cogs/snekbox.py
--- a/bot/cogs/snekbox.py
+++ b/bot/cogs/snekbox.py
@@ -232,7 +232,7 @@
                     timeout=10
                 )
 
-                code = new_message.content.split(' ', maxsplit=1)[1]
+                code = await self.get_code(new_message)
                 await ctx.message.clear_reactions()
                 with contextlib.suppress(HTTPException):
                     await response.delete()
@@ -243,6 +243,26 @@
 
             return code
 
+    async def get_code(self, message: Message) -> Optional[str]:
+        """
+        Return the code from `message` to be evaluated.
+
+        If the message is an invocation of the eval command, return the first argument or None if it
+        doesn't exist. Otherwise, return the full content of the message.
+        """
+        log.trace(f"Getting context for message {message.id}.")
+        new_ctx = await self.bot.get_context(message)
+
+        if new_ctx.command is self.eval_command:
+            log.trace(f"Message {message.id} invokes eval command.")
+            split = message.content.split(maxsplit=1)
+            code = split[1] if len(split) > 1 else None
+        else:
+            log.trace(f"Message {message.id} does not invoke eval command.")
+            code = message.content
+
+        return code
+
     @command(name="eval", aliases=("e",))
     @guild_only()
     @in_channel(Channels.bot_commands, hidden_channels=(Channels.esoteric,), bypass_roles=EVAL_ROLES)

{"golden_diff": "diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py\n--- a/bot/cogs/snekbox.py\n+++ b/bot/cogs/snekbox.py\n@@ -232,7 +232,7 @@\n                     timeout=10\n                 )\n \n-                code = new_message.content.split(' ', maxsplit=1)[1]\n+                code = await self.get_code(new_message)\n                 await ctx.message.clear_reactions()\n                 with contextlib.suppress(HTTPException):\n                     await response.delete()\n@@ -243,6 +243,26 @@\n \n             return code\n \n+    async def get_code(self, message: Message) -> Optional[str]:\n+        \"\"\"\n+        Return the code from `message` to be evaluated.\n+\n+        If the message is an invocation of the eval command, return the first argument or None if it\n+        doesn't exist. Otherwise, return the full content of the message.\n+        \"\"\"\n+        log.trace(f\"Getting context for message {message.id}.\")\n+        new_ctx = await self.bot.get_context(message)\n+\n+        if new_ctx.command is self.eval_command:\n+            log.trace(f\"Message {message.id} invokes eval command.\")\n+            split = message.content.split(maxsplit=1)\n+            code = split[1] if len(split) > 1 else None\n+        else:\n+            log.trace(f\"Message {message.id} does not invoke eval command.\")\n+            code = message.content\n+\n+        return code\n+\n     @command(name=\"eval\", aliases=(\"e\",))\n     @guild_only()\n     @in_channel(Channels.bot_commands, hidden_channels=(Channels.esoteric,), bypass_roles=EVAL_ROLES)\n", "issue": "IndexError when re-evaluating snekbox code\nSentry Issue: [BOT-2C](https://sentry.io/organizations/python-discord/issues/1567544008/?referrer=github_integration)\n\n```\nIndexError: list index out of range\n  File \"discord/ext/commands/core.py\", line 83, in wrapped\n    ret = await coro(*args, **kwargs)\n  File \"bot/cogs/snekbox.py\", line 281, in eval_command\n    code = await self.continue_eval(ctx, response)\n  File \"bot/cogs/snekbox.py\", line 235, in continue_eval\n    code = new_message.content.split(' ', maxsplit=1)[1]\n```\n\nUsing `get_context` seems to be a more robust option than parsing the message ourselves. Either way, if it can't parse it, it should just assume the entire content is the code.\n", "before_files": [{"content": "import asyncio\nimport contextlib\nimport datetime\nimport logging\nimport re\nimport textwrap\nfrom functools import partial\nfrom signal import Signals\nfrom typing import Optional, Tuple\n\nfrom discord import HTTPException, Message, NotFound, Reaction, User\nfrom discord.ext.commands import Cog, Context, command, guild_only\n\nfrom bot.bot import Bot\nfrom bot.constants import Channels, Roles, URLs\nfrom bot.decorators import in_channel\nfrom bot.utils.messages import wait_for_deletion\n\nlog = logging.getLogger(__name__)\n\nESCAPE_REGEX = re.compile(\"[`\\u202E\\u200B]{3,}\")\nFORMATTED_CODE_REGEX = re.compile(\n    r\"^\\s*\"                                 # any leading whitespace from the beginning of the string\n    r\"(?P<delim>(?P<block>```)|``?)\"        # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block\n    r\"(?(block)(?:(?P<lang>[a-z]+)\\n)?)\"    # if we're in a block, match optional language (only letters plus newline)\n    r\"(?:[ \\t]*\\n)*\"                        # any blank (empty or tabs/spaces only) lines before the code\n    r\"(?P<code>.*?)\"                        # extract all code inside the markup\n    r\"\\s*\"                                  # any more whitespace before the end of the code markup\n    r\"(?P=delim)\"                           # match the exact same delimiter from the start again\n    r\"\\s*$\",                                # any trailing whitespace until the end of the string\n    re.DOTALL | re.IGNORECASE               # \".\" also matches newlines, case insensitive\n)\nRAW_CODE_REGEX = re.compile(\n    r\"^(?:[ \\t]*\\n)*\"                       # any blank (empty or tabs/spaces only) lines before the code\n    r\"(?P<code>.*?)\"                        # extract all the rest as code\n    r\"\\s*$\",                                # any trailing whitespace until the end of the string\n    re.DOTALL                               # \".\" also matches newlines\n)\n\nMAX_PASTE_LEN = 1000\nEVAL_ROLES = (Roles.helpers, Roles.moderators, Roles.admins, Roles.owners, Roles.python_community, Roles.partners)\n\nSIGKILL = 9\n\nREEVAL_EMOJI = '\\U0001f501'  # :repeat:\n\n\nclass Snekbox(Cog):\n    \"\"\"Safe evaluation of Python code using Snekbox.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.jobs = {}\n\n    async def post_eval(self, code: str) -> dict:\n        \"\"\"Send a POST request to the Snekbox API to evaluate code and return the results.\"\"\"\n        url = URLs.snekbox_eval_api\n        data = {\"input\": code}\n        async with self.bot.http_session.post(url, json=data, raise_for_status=True) as resp:\n            return await resp.json()\n\n    async def upload_output(self, output: str) -> Optional[str]:\n        \"\"\"Upload the eval output to a paste service and return a URL to it if successful.\"\"\"\n        log.trace(\"Uploading full output to paste service...\")\n\n        if len(output) > MAX_PASTE_LEN:\n            log.info(\"Full output is too long to upload\")\n            return \"too long to upload\"\n\n        url = URLs.paste_service.format(key=\"documents\")\n        try:\n            async with self.bot.http_session.post(url, data=output, raise_for_status=True) as resp:\n                data = await resp.json()\n\n            if \"key\" in data:\n                return URLs.paste_service.format(key=data[\"key\"])\n        except Exception:\n            # 400 (Bad Request) means there are too many characters\n            log.exception(\"Failed to upload full output to paste service!\")\n\n    @staticmethod\n    def prepare_input(code: str) -> str:\n        \"\"\"Extract code from the Markdown, format it, and insert it into the code template.\"\"\"\n        match = FORMATTED_CODE_REGEX.fullmatch(code)\n        if match:\n            code, block, lang, delim = match.group(\"code\", \"block\", \"lang\", \"delim\")\n            code = textwrap.dedent(code)\n            if block:\n                info = (f\"'{lang}' highlighted\" if lang else \"plain\") + \" code block\"\n            else:\n                info = f\"{delim}-enclosed inline code\"\n            log.trace(f\"Extracted {info} for evaluation:\\n{code}\")\n        else:\n            code = textwrap.dedent(RAW_CODE_REGEX.fullmatch(code).group(\"code\"))\n            log.trace(\n                f\"Eval message contains unformatted or badly formatted code, \"\n                f\"stripping whitespace only:\\n{code}\"\n            )\n\n        return code\n\n    @staticmethod\n    def get_results_message(results: dict) -> Tuple[str, str]:\n        \"\"\"Return a user-friendly message and error corresponding to the process's return code.\"\"\"\n        stdout, returncode = results[\"stdout\"], results[\"returncode\"]\n        msg = f\"Your eval job has completed with return code {returncode}\"\n        error = \"\"\n\n        if returncode is None:\n            msg = \"Your eval job has failed\"\n            error = stdout.strip()\n        elif returncode == 128 + SIGKILL:\n            msg = \"Your eval job timed out or ran out of memory\"\n        elif returncode == 255:\n            msg = \"Your eval job has failed\"\n            error = \"A fatal NsJail error occurred\"\n        else:\n            # Try to append signal's name if one exists\n            try:\n                name = Signals(returncode - 128).name\n                msg = f\"{msg} ({name})\"\n            except ValueError:\n                pass\n\n        return msg, error\n\n    @staticmethod\n    def get_status_emoji(results: dict) -> str:\n        \"\"\"Return an emoji corresponding to the status code or lack of output in result.\"\"\"\n        if not results[\"stdout\"].strip():  # No output\n            return \":warning:\"\n        elif results[\"returncode\"] == 0:  # No error\n            return \":white_check_mark:\"\n        else:  # Exception\n            return \":x:\"\n\n    async def format_output(self, output: str) -> Tuple[str, Optional[str]]:\n        \"\"\"\n        Format the output and return a tuple of the formatted output and a URL to the full output.\n\n        Prepend each line with a line number. Truncate if there are over 10 lines or 1000 characters\n        and upload the full output to a paste service.\n        \"\"\"\n        log.trace(\"Formatting output...\")\n\n        output = output.rstrip(\"\\n\")\n        original_output = output  # To be uploaded to a pasting service if needed\n        paste_link = None\n\n        if \"<@\" in output:\n            output = output.replace(\"<@\", \"<@\\u200B\")  # Zero-width space\n\n        if \"<!@\" in output:\n            output = output.replace(\"<!@\", \"<!@\\u200B\")  # Zero-width space\n\n        if ESCAPE_REGEX.findall(output):\n            return \"Code block escape attempt detected; will not output result\", paste_link\n\n        truncated = False\n        lines = output.count(\"\\n\")\n\n        if lines > 0:\n            output = [f\"{i:03d} | {line}\" for i, line in enumerate(output.split('\\n'), 1)]\n            output = output[:11]  # Limiting to only 11 lines\n            output = \"\\n\".join(output)\n\n        if lines > 10:\n            truncated = True\n            if len(output) >= 1000:\n                output = f\"{output[:1000]}\\n... (truncated - too long, too many lines)\"\n            else:\n                output = f\"{output}\\n... (truncated - too many lines)\"\n        elif len(output) >= 1000:\n            truncated = True\n            output = f\"{output[:1000]}\\n... (truncated - too long)\"\n\n        if truncated:\n            paste_link = await self.upload_output(original_output)\n\n        output = output or \"[No output]\"\n\n        return output, paste_link\n\n    async def send_eval(self, ctx: Context, code: str) -> Message:\n        \"\"\"\n        Evaluate code, format it, and send the output to the corresponding channel.\n\n        Return the bot response.\n        \"\"\"\n        async with ctx.typing():\n            results = await self.post_eval(code)\n            msg, error = self.get_results_message(results)\n\n            if error:\n                output, paste_link = error, None\n            else:\n                output, paste_link = await self.format_output(results[\"stdout\"])\n\n            icon = self.get_status_emoji(results)\n            msg = f\"{ctx.author.mention} {icon} {msg}.\\n\\n```py\\n{output}\\n```\"\n            if paste_link:\n                msg = f\"{msg}\\nFull output: {paste_link}\"\n\n            response = await ctx.send(msg)\n            self.bot.loop.create_task(\n                wait_for_deletion(response, user_ids=(ctx.author.id,), client=ctx.bot)\n            )\n\n            log.info(f\"{ctx.author}'s job had a return code of {results['returncode']}\")\n        return response\n\n    async def continue_eval(self, ctx: Context, response: Message) -> Optional[str]:\n        \"\"\"\n        Check if the eval session should continue.\n\n        Return the new code to evaluate or None if the eval session should be terminated.\n        \"\"\"\n        _predicate_eval_message_edit = partial(predicate_eval_message_edit, ctx)\n        _predicate_emoji_reaction = partial(predicate_eval_emoji_reaction, ctx)\n\n        with contextlib.suppress(NotFound):\n            try:\n                _, new_message = await self.bot.wait_for(\n                    'message_edit',\n                    check=_predicate_eval_message_edit,\n                    timeout=10\n                )\n                await ctx.message.add_reaction(REEVAL_EMOJI)\n                await self.bot.wait_for(\n                    'reaction_add',\n                    check=_predicate_emoji_reaction,\n                    timeout=10\n                )\n\n                code = new_message.content.split(' ', maxsplit=1)[1]\n                await ctx.message.clear_reactions()\n                with contextlib.suppress(HTTPException):\n                    await response.delete()\n\n            except asyncio.TimeoutError:\n                await ctx.message.clear_reactions()\n                return None\n\n            return code\n\n    @command(name=\"eval\", aliases=(\"e\",))\n    @guild_only()\n    @in_channel(Channels.bot_commands, hidden_channels=(Channels.esoteric,), bypass_roles=EVAL_ROLES)\n    async def eval_command(self, ctx: Context, *, code: str = None) -> None:\n        \"\"\"\n        Run Python code and get the results.\n\n        This command supports multiple lines of code, including code wrapped inside a formatted code\n        block. Code can be re-evaluated by editing the original message within 10 seconds and\n        clicking the reaction that subsequently appears.\n\n        We've done our best to make this sandboxed, but do let us know if you manage to find an\n        issue with it!\n        \"\"\"\n        if ctx.author.id in self.jobs:\n            await ctx.send(\n                f\"{ctx.author.mention} You've already got a job running - \"\n                \"please wait for it to finish!\"\n            )\n            return\n\n        if not code:  # None or empty string\n            await ctx.invoke(self.bot.get_command(\"help\"), \"eval\")\n            return\n\n        log.info(f\"Received code from {ctx.author} for evaluation:\\n{code}\")\n\n        while True:\n            self.jobs[ctx.author.id] = datetime.datetime.now()\n            code = self.prepare_input(code)\n            try:\n                response = await self.send_eval(ctx, code)\n            finally:\n                del self.jobs[ctx.author.id]\n\n            code = await self.continue_eval(ctx, response)\n            if not code:\n                break\n            log.info(f\"Re-evaluating message {ctx.message.id}\")\n\n\ndef predicate_eval_message_edit(ctx: Context, old_msg: Message, new_msg: Message) -> bool:\n    \"\"\"Return True if the edited message is the context message and the content was indeed modified.\"\"\"\n    return new_msg.id == ctx.message.id and old_msg.content != new_msg.content\n\n\ndef predicate_eval_emoji_reaction(ctx: Context, reaction: Reaction, user: User) -> bool:\n    \"\"\"Return True if the reaction REEVAL_EMOJI was added by the context message author on this message.\"\"\"\n    return reaction.message.id == ctx.message.id and user.id == ctx.author.id and str(reaction) == REEVAL_EMOJI\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Snekbox cog.\"\"\"\n    bot.add_cog(Snekbox(bot))\n", "path": "bot/cogs/snekbox.py"}], "after_files": [{"content": "import asyncio\nimport contextlib\nimport datetime\nimport logging\nimport re\nimport textwrap\nfrom functools import partial\nfrom signal import Signals\nfrom typing import Optional, Tuple\n\nfrom discord import HTTPException, Message, NotFound, Reaction, User\nfrom discord.ext.commands import Cog, Context, command, guild_only\n\nfrom bot.bot import Bot\nfrom bot.constants import Channels, Roles, URLs\nfrom bot.decorators import in_channel\nfrom bot.utils.messages import wait_for_deletion\n\nlog = logging.getLogger(__name__)\n\nESCAPE_REGEX = re.compile(\"[`\\u202E\\u200B]{3,}\")\nFORMATTED_CODE_REGEX = re.compile(\n    r\"^\\s*\"                                 # any leading whitespace from the beginning of the string\n    r\"(?P<delim>(?P<block>```)|``?)\"        # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block\n    r\"(?(block)(?:(?P<lang>[a-z]+)\\n)?)\"    # if we're in a block, match optional language (only letters plus newline)\n    r\"(?:[ \\t]*\\n)*\"                        # any blank (empty or tabs/spaces only) lines before the code\n    r\"(?P<code>.*?)\"                        # extract all code inside the markup\n    r\"\\s*\"                                  # any more whitespace before the end of the code markup\n    r\"(?P=delim)\"                           # match the exact same delimiter from the start again\n    r\"\\s*$\",                                # any trailing whitespace until the end of the string\n    re.DOTALL | re.IGNORECASE               # \".\" also matches newlines, case insensitive\n)\nRAW_CODE_REGEX = re.compile(\n    r\"^(?:[ \\t]*\\n)*\"                       # any blank (empty or tabs/spaces only) lines before the code\n    r\"(?P<code>.*?)\"                        # extract all the rest as code\n    r\"\\s*$\",                                # any trailing whitespace until the end of the string\n    re.DOTALL                               # \".\" also matches newlines\n)\n\nMAX_PASTE_LEN = 1000\nEVAL_ROLES = (Roles.helpers, Roles.moderators, Roles.admins, Roles.owners, Roles.python_community, Roles.partners)\n\nSIGKILL = 9\n\nREEVAL_EMOJI = '\\U0001f501'  # :repeat:\n\n\nclass Snekbox(Cog):\n    \"\"\"Safe evaluation of Python code using Snekbox.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.jobs = {}\n\n    async def post_eval(self, code: str) -> dict:\n        \"\"\"Send a POST request to the Snekbox API to evaluate code and return the results.\"\"\"\n        url = URLs.snekbox_eval_api\n        data = {\"input\": code}\n        async with self.bot.http_session.post(url, json=data, raise_for_status=True) as resp:\n            return await resp.json()\n\n    async def upload_output(self, output: str) -> Optional[str]:\n        \"\"\"Upload the eval output to a paste service and return a URL to it if successful.\"\"\"\n        log.trace(\"Uploading full output to paste service...\")\n\n        if len(output) > MAX_PASTE_LEN:\n            log.info(\"Full output is too long to upload\")\n            return \"too long to upload\"\n\n        url = URLs.paste_service.format(key=\"documents\")\n        try:\n            async with self.bot.http_session.post(url, data=output, raise_for_status=True) as resp:\n                data = await resp.json()\n\n            if \"key\" in data:\n                return URLs.paste_service.format(key=data[\"key\"])\n        except Exception:\n            # 400 (Bad Request) means there are too many characters\n            log.exception(\"Failed to upload full output to paste service!\")\n\n    @staticmethod\n    def prepare_input(code: str) -> str:\n        \"\"\"Extract code from the Markdown, format it, and insert it into the code template.\"\"\"\n        match = FORMATTED_CODE_REGEX.fullmatch(code)\n        if match:\n            code, block, lang, delim = match.group(\"code\", \"block\", \"lang\", \"delim\")\n            code = textwrap.dedent(code)\n            if block:\n                info = (f\"'{lang}' highlighted\" if lang else \"plain\") + \" code block\"\n            else:\n                info = f\"{delim}-enclosed inline code\"\n            log.trace(f\"Extracted {info} for evaluation:\\n{code}\")\n        else:\n            code = textwrap.dedent(RAW_CODE_REGEX.fullmatch(code).group(\"code\"))\n            log.trace(\n                f\"Eval message contains unformatted or badly formatted code, \"\n                f\"stripping whitespace only:\\n{code}\"\n            )\n\n        return code\n\n    @staticmethod\n    def get_results_message(results: dict) -> Tuple[str, str]:\n        \"\"\"Return a user-friendly message and error corresponding to the process's return code.\"\"\"\n        stdout, returncode = results[\"stdout\"], results[\"returncode\"]\n        msg = f\"Your eval job has completed with return code {returncode}\"\n        error = \"\"\n\n        if returncode is None:\n            msg = \"Your eval job has failed\"\n            error = stdout.strip()\n        elif returncode == 128 + SIGKILL:\n            msg = \"Your eval job timed out or ran out of memory\"\n        elif returncode == 255:\n            msg = \"Your eval job has failed\"\n            error = \"A fatal NsJail error occurred\"\n        else:\n            # Try to append signal's name if one exists\n            try:\n                name = Signals(returncode - 128).name\n                msg = f\"{msg} ({name})\"\n            except ValueError:\n                pass\n\n        return msg, error\n\n    @staticmethod\n    def get_status_emoji(results: dict) -> str:\n        \"\"\"Return an emoji corresponding to the status code or lack of output in result.\"\"\"\n        if not results[\"stdout\"].strip():  # No output\n            return \":warning:\"\n        elif results[\"returncode\"] == 0:  # No error\n            return \":white_check_mark:\"\n        else:  # Exception\n            return \":x:\"\n\n    async def format_output(self, output: str) -> Tuple[str, Optional[str]]:\n        \"\"\"\n        Format the output and return a tuple of the formatted output and a URL to the full output.\n\n        Prepend each line with a line number. Truncate if there are over 10 lines or 1000 characters\n        and upload the full output to a paste service.\n        \"\"\"\n        log.trace(\"Formatting output...\")\n\n        output = output.rstrip(\"\\n\")\n        original_output = output  # To be uploaded to a pasting service if needed\n        paste_link = None\n\n        if \"<@\" in output:\n            output = output.replace(\"<@\", \"<@\\u200B\")  # Zero-width space\n\n        if \"<!@\" in output:\n            output = output.replace(\"<!@\", \"<!@\\u200B\")  # Zero-width space\n\n        if ESCAPE_REGEX.findall(output):\n            return \"Code block escape attempt detected; will not output result\", paste_link\n\n        truncated = False\n        lines = output.count(\"\\n\")\n\n        if lines > 0:\n            output = [f\"{i:03d} | {line}\" for i, line in enumerate(output.split('\\n'), 1)]\n            output = output[:11]  # Limiting to only 11 lines\n            output = \"\\n\".join(output)\n\n        if lines > 10:\n            truncated = True\n            if len(output) >= 1000:\n                output = f\"{output[:1000]}\\n... (truncated - too long, too many lines)\"\n            else:\n                output = f\"{output}\\n... (truncated - too many lines)\"\n        elif len(output) >= 1000:\n            truncated = True\n            output = f\"{output[:1000]}\\n... (truncated - too long)\"\n\n        if truncated:\n            paste_link = await self.upload_output(original_output)\n\n        output = output or \"[No output]\"\n\n        return output, paste_link\n\n    async def send_eval(self, ctx: Context, code: str) -> Message:\n        \"\"\"\n        Evaluate code, format it, and send the output to the corresponding channel.\n\n        Return the bot response.\n        \"\"\"\n        async with ctx.typing():\n            results = await self.post_eval(code)\n            msg, error = self.get_results_message(results)\n\n            if error:\n                output, paste_link = error, None\n            else:\n                output, paste_link = await self.format_output(results[\"stdout\"])\n\n            icon = self.get_status_emoji(results)\n            msg = f\"{ctx.author.mention} {icon} {msg}.\\n\\n```py\\n{output}\\n```\"\n            if paste_link:\n                msg = f\"{msg}\\nFull output: {paste_link}\"\n\n            response = await ctx.send(msg)\n            self.bot.loop.create_task(\n                wait_for_deletion(response, user_ids=(ctx.author.id,), client=ctx.bot)\n            )\n\n            log.info(f\"{ctx.author}'s job had a return code of {results['returncode']}\")\n        return response\n\n    async def continue_eval(self, ctx: Context, response: Message) -> Optional[str]:\n        \"\"\"\n        Check if the eval session should continue.\n\n        Return the new code to evaluate or None if the eval session should be terminated.\n        \"\"\"\n        _predicate_eval_message_edit = partial(predicate_eval_message_edit, ctx)\n        _predicate_emoji_reaction = partial(predicate_eval_emoji_reaction, ctx)\n\n        with contextlib.suppress(NotFound):\n            try:\n                _, new_message = await self.bot.wait_for(\n                    'message_edit',\n                    check=_predicate_eval_message_edit,\n                    timeout=10\n                )\n                await ctx.message.add_reaction(REEVAL_EMOJI)\n                await self.bot.wait_for(\n                    'reaction_add',\n                    check=_predicate_emoji_reaction,\n                    timeout=10\n                )\n\n                code = await self.get_code(new_message)\n                await ctx.message.clear_reactions()\n                with contextlib.suppress(HTTPException):\n                    await response.delete()\n\n            except asyncio.TimeoutError:\n                await ctx.message.clear_reactions()\n                return None\n\n            return code\n\n    async def get_code(self, message: Message) -> Optional[str]:\n        \"\"\"\n        Return the code from `message` to be evaluated.\n\n        If the message is an invocation of the eval command, return the first argument or None if it\n        doesn't exist. Otherwise, return the full content of the message.\n        \"\"\"\n        log.trace(f\"Getting context for message {message.id}.\")\n        new_ctx = await self.bot.get_context(message)\n\n        if new_ctx.command is self.eval_command:\n            log.trace(f\"Message {message.id} invokes eval command.\")\n            split = message.content.split(maxsplit=1)\n            code = split[1] if len(split) > 1 else None\n        else:\n            log.trace(f\"Message {message.id} does not invoke eval command.\")\n            code = message.content\n\n        return code\n\n    @command(name=\"eval\", aliases=(\"e\",))\n    @guild_only()\n    @in_channel(Channels.bot_commands, hidden_channels=(Channels.esoteric,), bypass_roles=EVAL_ROLES)\n    async def eval_command(self, ctx: Context, *, code: str = None) -> None:\n        \"\"\"\n        Run Python code and get the results.\n\n        This command supports multiple lines of code, including code wrapped inside a formatted code\n        block. Code can be re-evaluated by editing the original message within 10 seconds and\n        clicking the reaction that subsequently appears.\n\n        We've done our best to make this sandboxed, but do let us know if you manage to find an\n        issue with it!\n        \"\"\"\n        if ctx.author.id in self.jobs:\n            await ctx.send(\n                f\"{ctx.author.mention} You've already got a job running - \"\n                \"please wait for it to finish!\"\n            )\n            return\n\n        if not code:  # None or empty string\n            await ctx.invoke(self.bot.get_command(\"help\"), \"eval\")\n            return\n\n        log.info(f\"Received code from {ctx.author} for evaluation:\\n{code}\")\n\n        while True:\n            self.jobs[ctx.author.id] = datetime.datetime.now()\n            code = self.prepare_input(code)\n            try:\n                response = await self.send_eval(ctx, code)\n            finally:\n                del self.jobs[ctx.author.id]\n\n            code = await self.continue_eval(ctx, response)\n            if not code:\n                break\n            log.info(f\"Re-evaluating message {ctx.message.id}\")\n\n\ndef predicate_eval_message_edit(ctx: Context, old_msg: Message, new_msg: Message) -> bool:\n    \"\"\"Return True if the edited message is the context message and the content was indeed modified.\"\"\"\n    return new_msg.id == ctx.message.id and old_msg.content != new_msg.content\n\n\ndef predicate_eval_emoji_reaction(ctx: Context, reaction: Reaction, user: User) -> bool:\n    \"\"\"Return True if the reaction REEVAL_EMOJI was added by the context message author on this message.\"\"\"\n    return reaction.message.id == ctx.message.id and user.id == ctx.author.id and str(reaction) == REEVAL_EMOJI\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Snekbox cog.\"\"\"\n    bot.add_cog(Snekbox(bot))\n", "path": "bot/cogs/snekbox.py"}]}