Skip to content

Instantly share code, notes, and snippets.

@Chovin
Created August 30, 2016 08:04
Show Gist options
  • Save Chovin/6445540e61d640545f11d087c480ad2b to your computer and use it in GitHub Desktop.
Save Chovin/6445540e61d640545f11d087c480ad2b to your computer and use it in GitHub Desktop.
slight edit to RoboDanny's repl.py
from discord.ext import commands
from .utils import checks
from .utils.dataIO import fileIO
from .utils.chat_formatting import pagify
import asyncio
import traceback
import discord
import inspect
from contextlib import redirect_stdout
from __main__ import send_cmd_help
import io, sys, os
import subprocess
class REPL:
def __init__(self, bot):
self.bot = bot
self.settings = fileIO('data/repl/settings.json', "load")
self.output_file = "data/repl/temp_output.txt"
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):
return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__)
async def print_results(self, ctx, results):
msg = ctx.message
if len(results+'```py```') > 2000:
if self.settings["OUTPUT_REDIRECT"] == "pm":
await self.bot.send_message(msg.channel, 'Content too big. Check your PMs')
enough_paper = 20
for page in pagify(results, ['\n', ' '], shorten_by=12):
await self.bot.send_message(msg.author, "```py\n{}\n```".format(page))
enough_paper -= 1
if not enough_paper:
await self.bot.send_message(msg.author, "**Too many pages! Think of the trees!**")
return
elif self.settings["OUTPUT_REDIRECT"] == "console":
await self.bot.send_message(msg.channel, 'Content too big. Check your console')
print(results)
else:
await self.bot.send_message(msg.channel, 'Content too big. Writing to file')
with open(self.output_file, 'w') as f:
f.write(results)
open_cmd = self.settings["OPEN_CMD"]
if open_cmd:
subprocess.Popen([open_cmd, self.output_file])
else:
await self.bot.send_message(msg.channel, "```py\n{}\n```".format(results))
@commands.command(pass_context=True, hidden=True)
@checks.is_owner()
async def repl(self, ctx):
msg = ctx.message
variables = {
'ctx': ctx,
'bot': self.bot,
'message': msg,
'server': msg.server,
'channel': msg.channel,
'author': msg.author,
'last': None,
}
if msg.channel.id in self.sessions:
await self.bot.say('Already running a REPL session in this channel. Exit it with `quit`.')
return
self.sessions.add(msg.channel.id)
await self.bot.say('Enter code to execute or evaluate. `exit()` or `quit` to exit.')
while True:
response = await self.bot.wait_for_message(author=msg.author, channel=msg.channel,
check=lambda m: m.content.startswith('`'))
cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'):
await self.bot.say('Exiting.')
self.sessions.remove(msg.channel.id)
return
executor = exec
if cleaned.count('\n') == 0:
# single statement, potentially 'eval'
try:
code = compile(cleaned, '<repl session>', 'eval')
except SyntaxError:
pass
else:
executor = eval
if executor is exec:
try:
code = compile(cleaned, '<repl session>', 'exec')
except SyntaxError as e:
await self.bot.say(self.get_syntax_error(e))
continue
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()
fmt = '{}{}'.format(value, traceback.format_exc())
else:
value = stdout.getvalue()
if result is not None:
fmt = '{}{}'.format(value, result)
variables['last'] = result
elif value:
fmt = '{}'.format(value)
try:
if fmt is not None:
await self.print_results(ctx, fmt)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await self.bot.send_message(msg.channel, 'Unexpected error: `{}`'.format(e))
@commands.group(pass_context=True)
async def replset(self, ctx):
"""global repl settings"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@replset.command(pass_context=True, name="print", no_pm=True)
async def replset_print(self, ctx, discord_console_file):
"""Sets where repl content goes when response is too large.
Choices are
pm
console
file
"""
server = ctx.message.server
channel = ctx.message.channel
author = ctx.message.author
if discord_console_file not in ['pm','console','file']:
await self.bot.say('Choices are discord/console/file')
return
if discord_console_file == 'file':
choices = ['subl','subl.exe','atom','atom.exe']
await self.bot.say("You chose to print to file. What would you like to open it with?\n"
"Choose between: {}".format(' | '.join(choices+['nothing'])))
answer = await self.bot.wait_for_message(timeout=20, author=author)
answer = answer.content
if answer not in choices:
answer = None
await self.bot.say("ok, I won't open it after writing to {}".format(self.output_file))
else:
await self.bot.say("output will be opened with: {} {}".format(answer,self.output_file))
self.settings['OPEN_CMD'] = answer
self.settings["OUTPUT_REDIRECT"] = discord_console_file
fileIO("data/repl/settings.json", "save", self.settings)
await self.bot.say("repl overflow will now go to "+discord_console_file)
def check_folders():
folder = "data/repl"
if not os.path.exists(folder):
print("Creating {} folder...".format(folder))
os.makedirs(folder)
def check_files():
default = {"OUTPUT_REDIRECT" : "discord", "OPEN_CMD" : None}
settings_path = "data/repl/settings.json"
if not os.path.isfile(settings_path):
print("Creating default repl settings.json...")
fileIO(settings_path, "save", default)
else: # consistency check
current = fileIO(settings_path, "load")
if current.keys() != default.keys():
for key in default.keys():
if key not in current.keys():
current[key] = default[key]
print(
"Adding " + str(key) + " field to repl settings.json")
fileIO(settings_path, "save", current)
def setup(bot):
check_folders()
check_files()
bot.add_cog(REPL(bot))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment