async def send_eval(self, ctx: Context, code: str) -> Message: """ Evaluate code, format it, and send the output to the corresponding channel. Return the bot response. """ async with ctx.typing(): results = await self.post_eval(code) msg, error = self.get_results_message(results) if error: output, paste_link = error, None else: output, paste_link = await self.format_output(results["stdout"] ) icon = self.get_status_emoji(results) msg = f"{ctx.author.mention} {icon} {msg}.\n\n```py\n{output}\n```" if paste_link: msg = f"{msg}\nFull output: {paste_link}" response = await ctx.send(msg) self.bot.loop.create_task( wait_for_deletion(response, user_ids=(ctx.author.id, ), client=ctx.bot)) log.info( f"{ctx.author}'s job had a return code of {results['returncode']}" ) return response
async def eval_command(self, ctx: Context, *, code: str = None) -> None: """ Run Python code and get the results. This command supports multiple lines of code, including code wrapped inside a formatted code block. We've done our best to make this safe, but do let us know if you manage to find an issue with it! """ if ctx.author.id in self.jobs: await ctx.send( f"{ctx.author.mention} You've already got a job running - " f"please wait for it to finish!") return if not code: # None or empty string await ctx.invoke(self.bot.get_command("help"), "eval") return log.info( f"Received code from {ctx.author.name}#{ctx.author.discriminator} " f"for evaluation:\n{code}") self.jobs[ctx.author.id] = datetime.datetime.now() code = self.prepare_input(code) try: async with ctx.typing(): results = await self.post_eval(code) msg, error = self.get_results_message(results) if error: output, paste_link = error, None else: output, paste_link = await self.format_output( results["stdout"]) msg = f"{ctx.author.mention} {msg}.\n\n```py\n{output}\n```" if paste_link: msg = f"{msg}\nFull output: {paste_link}" response = await ctx.send(msg) self.bot.loop.create_task( wait_for_deletion(response, user_ids=(ctx.author.id, ), client=ctx.bot)) log.info( f"{ctx.author.name}#{ctx.author.discriminator}'s job had a return code of " f"{results['returncode']}") finally: del self.jobs[ctx.author.id]
async def send_instructions(self, message: discord.Message, instructions: str) -> None: """ Send an embed with `instructions` on fixing an incorrect code block in a `message`. The embed will be deleted automatically after 5 minutes. """ log.info(f"Sending code block formatting instructions for message {message.id}.") embed = self.create_embed(instructions) bot_message = await message.channel.send(f"Hey {message.author.mention}!", embed=embed) self.codeblock_message_ids[message.id] = bot_message.id self.bot.loop.create_task(wait_for_deletion(bot_message, (message.author.id,))) # Increase amount of codeblock correction in stats self.bot.stats.incr("codeblock_corrections")
async def send_eval(self, ctx: Context, code: str) -> Message: """ Evaluate code, format it, and send the output to the corresponding channel. Return the bot response. """ async with ctx.typing(): results = await self.post_eval(code) msg, error = self.get_results_message(results) if error: output, paste_link = error, None else: output, paste_link = await self.format_output(results["stdout"] ) icon = self.get_status_emoji(results) msg = f"{ctx.author.mention} {icon} {msg}.\n\n```\n{output}\n```" if paste_link: msg = f"{msg}\nFull output: {paste_link}" # Collect stats of eval fails + successes if icon == ":x:": self.bot.stats.incr("snekbox.python.fail") else: self.bot.stats.incr("snekbox.python.success") filter_cog = self.bot.get_cog("Filtering") filter_triggered = False if filter_cog: filter_triggered = await filter_cog.filter_eval( msg, ctx.message) if filter_triggered: response = await ctx.send( "Attempt to circumvent filter detected. Moderator team has been alerted." ) else: response = await ctx.send(msg) scheduling.create_task(wait_for_deletion(response, (ctx.author.id, )), event_loop=self.bot.loop) log.info( f"{ctx.author}'s job had a return code of {results['returncode']}" ) return response
async def on_message(self, msg: Message) -> None: """ Detect poorly formatted Python code in new messages. If poorly formatted code is detected, send the user a helpful message explaining how to do properly formatted Python syntax highlighting codeblocks. """ is_help_channel = (getattr(msg.channel, "category", None) and msg.channel.category.id in (Categories.help_available, Categories.help_in_use)) parse_codeblock = ((is_help_channel or msg.channel.id in self.channel_cooldowns or msg.channel.id in self.channel_whitelist) and not msg.author.bot and len(msg.content.splitlines()) > 3 and not TokenRemover.find_token_in_message(msg)) if parse_codeblock: # no token in the msg on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: if self.has_bad_ticks(msg): ticks = msg.content[:3] content = self.codeblock_stripping( f"```{msg.content[3:-3]}```", True) if content is None: return content, repl_code = content if len(content) == 2: content = content[1] else: content = content[0] space_left = 204 if len(content) >= space_left: current_length = 0 lines_walked = 0 for line in content.splitlines(keepends=True): if current_length + len( line ) > space_left or lines_walked == 10: break current_length += len(line) lines_walked += 1 content = content[:current_length] + "#..." content_escaped_markdown = RE_MARKDOWN.sub( r'\\\1', content) howto = ( "It looks like you are trying to paste code into this channel.\n\n" "You seem to be using the wrong symbols to indicate where the codeblock should start. " f"The correct symbols would be \\`\\`\\`, not `{ticks}`.\n\n" "**Here is an example of how it should look:**\n" f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" "**This will result in the following:**\n" f"```python\n{content}\n```") else: howto = "" content = self.codeblock_stripping(msg.content, False) if content is None: return content, repl_code = content # Attempts to parse the message into an AST node. # Invalid Python code will raise a SyntaxError. tree = ast.parse(content[0]) # Multiple lines of single words could be interpreted as expressions. # This check is to avoid all nodes being parsed as expressions. # (e.g. words over multiple lines) if not all( isinstance(node, ast.Expr) for node in tree.body) or repl_code: # Shorten the code to 10 lines and/or 204 characters. space_left = 204 if content and repl_code: content = content[1] else: content = content[0] if len(content) >= space_left: current_length = 0 lines_walked = 0 for line in content.splitlines(keepends=True): if current_length + len( line ) > space_left or lines_walked == 10: break current_length += len(line) lines_walked += 1 content = content[:current_length] + "#..." content_escaped_markdown = RE_MARKDOWN.sub( r'\\\1', content) howto += ( "It looks like you're trying to paste code into this channel.\n\n" "Discord has support for Markdown, which allows you to post code with full " "syntax highlighting. Please use these whenever you paste code, as this " "helps improve the legibility and makes it easier for us to help you.\n\n" f"**To do this, use the following method:**\n" f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" "**This will result in the following:**\n" f"```python\n{content}\n```") log.debug( f"{msg.author} posted something that needed to be put inside python code " "blocks. Sending the user some instructions.") else: log.trace( "The code consists only of expressions, not sending instructions" ) if howto != "": howto_embed = Embed(description=howto) bot_message = await msg.channel.send( f"Hey {msg.author.mention}!", embed=howto_embed) self.codeblock_message_ids[msg.id] = bot_message.id self.bot.loop.create_task( wait_for_deletion(bot_message, user_ids=(msg.author.id, ), client=self.bot)) else: return if msg.channel.id not in self.channel_whitelist: self.channel_cooldowns[msg.channel.id] = time.time() except SyntaxError: log.trace( f"{msg.author} posted in a help channel, and when we tried to parse it as Python code, " "ast.parse raised a SyntaxError. This probably just means it wasn't Python code. " f"The message that was posted was:\n\n{msg.content}\n\n" )
async def eval_command(self, ctx: Context, *, code: str = None): """ Run some code. get the result back. We've done our best to make this safe, but do let us know if you manage to find an issue with it! This command supports multiple lines of code, including code wrapped inside a formatted code block. """ if ctx.author.id in self.jobs: await ctx.send( f"{ctx.author.mention} You've already got a job running - please wait for it to finish!" ) return if not code: # None or empty string return await ctx.invoke(self.bot.get_command("help"), "eval") log.info( f"Received code from {ctx.author.name}#{ctx.author.discriminator} for evaluation:\n{code}" ) self.jobs[ctx.author.id] = datetime.datetime.now() # Strip whitespace and inline or block code markdown and extract the code and some formatting info match = FORMATTED_CODE_REGEX.fullmatch(code) if match: code, block, lang, delim = match.group("code", "block", "lang", "delim") code = textwrap.dedent(code) if block: info = (f"'{lang}' highlighted" if lang else "plain") + " code block" else: info = f"{delim}-enclosed inline code" log.trace(f"Extracted {info} for evaluation:\n{code}") else: code = textwrap.dedent( RAW_CODE_REGEX.fullmatch(code).group("code")) log.trace( f"Eval message contains not or badly formatted code, stripping whitespace only:\n{code}" ) try: stripped_lines = [ln.strip() for ln in code.split('\n')] if all(line.startswith('#') for line in stripped_lines): return await ctx.send( f"{ctx.author.mention} Your eval job has completed.\n\n```[No output]```" ) code = textwrap.indent(code, " ") code = CODE_TEMPLATE.replace("{CODE}", code) await self.rmq.send_json("input", snekid=str(ctx.author.id), message=code) async with ctx.typing(): message = await self.rmq.consume(str(ctx.author.id), **RMQ_ARGS) paste_link = None if isinstance(message, str): output = str.strip(" \n") else: output = message.body.decode().strip(" \n") if "<@" in output: output = output.replace("<@", "<@\u200B") # Zero-width space if "<!@" in output: output = output.replace("<!@", "<!@\u200B") # Zero-width space if ESCAPE_REGEX.findall(output): output = "Code block escape attempt detected; will not output result" else: # the original output, to send to a pasting service if needed full_output = output truncated = False if output.count("\n") > 0: output = [ f"{i:03d} | {line}" for i, line in enumerate(output.split("\n"), start=1) ] output = "\n".join(output) if output.count("\n") > 10: output = "\n".join(output.split("\n")[:10]) if len(output) >= 1000: output = f"{output[:1000]}\n... (truncated - too long, too many lines)" else: output = f"{output}\n... (truncated - too many lines)" truncated = True elif len(output) >= 1000: output = f"{output[:1000]}\n... (truncated - too long)" truncated = True if truncated: try: response = await self.bot.http_session.post( URLs.paste_service.format(key="documents"), data=full_output) data = await response.json() if "key" in data: paste_link = URLs.paste_service.format( key=data["key"]) except Exception: log.exception( "Failed to upload full output to paste service!" ) if output.strip(): if paste_link: msg = f"{ctx.author.mention} Your eval job has completed.\n\n```py\n{output}\n```" \ f"\nFull output: {paste_link}" else: msg = f"{ctx.author.mention} Your eval job has completed.\n\n```py\n{output}\n```" response = await ctx.send(msg) self.bot.loop.create_task( wait_for_deletion(response, user_ids=(ctx.author.id, ), client=ctx.bot)) else: await ctx.send( f"{ctx.author.mention} Your eval job has completed.\n\n```[No output]```" ) finally: del self.jobs[ctx.author.id]