from discord.ext import commands import traceback import discord import inspect import textwrap from contextlib import redirect_stdout import io class REPL: """Python in Discords""" def __init__(self, bot): self.bot = bot self._last_result = None self.sessions = set() def cleanup_code(self, content): """ Automatically removes code blocks from the code. """ # remove ```py\n``` if content.startswith("```") and content.endswith("```"): return "\n".join(content.split("\n")[1:-1]) # remove `foo` return content.strip("` \n") def get_syntax_error(self, e): if e.text is None: return "{0.__class__.__name__}: {0}".format(e) return "{0.text}{1:>{0.offset}}\n{2}: {0}".format(e, "^", type(e).__name__) @commands.command(name="exec") async def _eval(self, ctx, *, body: str = None): """ Execute python code in discord chat. Only the owner of this bot can use this command. Alias: - exec Usage: - exec < python code > Example: - exec print(546132) """ if ctx.author.id not in self.bot.ownerlist: return await ctx.send( "Only my contributors can use me like this :blush:", delete_after=10 ) if body is None: return await ctx.send( "Please, use\n" f'`{self.bot.config["prefix"]}exec`\n\n' "\n`\\`\\`\\`py\n[python code]\n\\`\\`\\`\n" "to get the most out of the command" ) env = { "bot": self.bot, "ctx": ctx, "channel": ctx.message.channel, "author": ctx.message.author, "server": ctx.message.guild, "message": ctx.message, "_": self._last_result, } env.update(globals()) body = self.cleanup_code(body) stdout = io.StringIO() to_compile = "async def func():\n%s" % textwrap.indent(body, " ") try: exec(to_compile, env) except SyntaxError as e: try: await ctx.send(f"```py\n{self.get_syntax_error(e)}\n```") except Exception as e: error = [ self.get_syntax_error(e)[i : i + 2000] for i in range(0, len(self.get_syntax_error(e)), 2000) ] for i in error: await ctx.send(f"```py\n{i}\n```") func = env["func"] try: with redirect_stdout(stdout): ret = await func() except Exception as e: value = stdout.getvalue() try: await ctx.send(f"```py\n{value}{traceback.format_exc()}\n```") except Exception as e: error = [value[i : i + 2000] for i in range(0, len(value), 2000)] for i in error: await ctx.send(f"```py\n{i}\n```") tracebackerror = [ traceback.format_exc()[i : i + 2000] for i in range(0, len(traceback.format_exc()), 2000) ] for i in tracebackerror: await ctx.send(f"```py\n{i}\n```") else: value = stdout.getvalue() if ret is None: if value: try: await ctx.send(f"```py\n{value}\n```") except Exception as e: code = [value[i : i + 1980] for i in range(0, len(value), 1980)] for i in code: await ctx.send(f"```py\n{i}\n```") else: self._last_result = ret try: code = [value[i : i + 1980] for i in range(0, len(value), 1980)] for i in code: await ctx.send(f"```py\n{i}\n```") except Exception as e: code = [value[i : i + 1980] for i in range(0, len(value), 1980)] for i in code: await ctx.send(f"```py\n{i}\n```") modifyd_ret = [ret[i : i + 1980] for i in range(0, len(ret), 1980)] for i in modifyd_ret: await ctx.send(f"```py\n{i}\n```") @commands.command(hidden=True) async def repl(self, ctx): """ Start a interactive python shell in chat. Only the owner of this bot can use this command. Usage: - repl < python code > Example: - repl print(205554) """ if ctx.author.id not in self.bot.ownerlist: return await ctx.send( "Only my contributors can use me like this :blush:", delete_after=10 ) msg = ctx.message variables = { "ctx": ctx, "bot": self.bot, "message": msg, "server": msg.guild, "channel": msg.channel, "author": msg.author, "_": None, } if msg.channel.id in self.sessions: msg = await ctx.send( "Already running a REPL session in this channel. Exit it with `quit`." ) self.sessions.add(msg.channel.id) await ctx.send("Enter code to execute or evaluate. `exit()` or `quit` to exit.") while True: response = await self.bot.wait_for( "message", check=lambda m: m.content.startswith("`") and m.author == ctx.author and m.channel == ctx.channel, ) cleaned = self.cleanup_code(response.content) if cleaned in ("quit", "exit", "exit()"): msg = await ctx.send("Exiting.") self.sessions.remove(msg.channel.id) return executor = exec if cleaned.count("\n") == 0: # single statement, potentially 'eval' try: code = compile(cleaned, "", "eval") except SyntaxError: pass else: executor = eval if executor is exec: try: code = compile(cleaned, "", "exec") except SyntaxError as e: try: await ctx.send(f"```Python\n{self.get_syntax_error(e)}\n```") except Exception as e: error = [ self.get_syntax_error(e)[i : i + 2000] for i in range(0, len(self.get_syntax_error(e)), 2000) ] for i in error: await ctx.send(f"```Python\n{i}\n```") variables["message"] = response fmt = None stdout = io.StringIO() try: with redirect_stdout(stdout): result = executor(code, variables) if inspect.isawaitable(result): result = await result except Exception as e: value = stdout.getvalue() await ctx.send(f"```Python\n{value}{traceback.format_exc()}\n```") continue else: value = stdout.getvalue() if result is not None: fmt = "{}{}".format(value, result) variables["_"] = result elif value: fmt = value try: if fmt is not None: if len(fmt) > 1980: code = [fmt[i : i + 1980] for i in range(0, len(fmt), 1980)] for i in code: await ctx.send(f"```py\n{i}\n```") else: await ctx.send(fmt) except discord.Forbidden: pass except discord.HTTPException as e: await ctx.send(f"Unexpected error: `{e}`") def setup(bot): bot.add_cog(REPL(bot))