Beispiel #1
0
    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
Beispiel #2
0
    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]
Beispiel #3
0
    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")
Beispiel #4
0
    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
Beispiel #5
0
    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"
                    )
Beispiel #6
0
    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]