Basic api stuff and bot exec

This commit is contained in:
Dustin Pianalto
2019-09-16 22:01:11 -08:00
parent d6fa090a56
commit fb9744a93a
98 changed files with 1056 additions and 896 deletions
+1 -3
View File
@@ -53,8 +53,6 @@ logger.info(f'Process Libs Import Complete - Took {(datetime.utcnow() - start).t
start = datetime.utcnow()
import re # noqa: E402
logger.info('re Imported')
from typing import Dict # noqa: E402
logger.info('Typing Dict Imported')
import json # noqa: E402
logger.info('JSON Imported')
import aiohttp # noqa: E402
@@ -98,7 +96,7 @@ class Geeksbot(commands.Bot):
async def unload_ext(self, mod):
self.unload_extension(f'geeksbot.{self.extension_dir}.{mod}')
logger.info(f'Extension Loaded: {mod}')
logger.info(f'Extension Unloaded: {mod}')
def load_default_extensions(self):
for load_item in self.bot_config['load_list']:
+221
View File
@@ -0,0 +1,221 @@
from discord.ext import commands
import asyncio
import traceback
import discord
import inspect
import textwrap
import time
import os
from datetime import datetime
from contextlib import redirect_stdout
import io
from geeksbot.imports.utils import run_command, format_output, Paginator, Book
import logging
repl_log = logging.getLogger('repl')
class Exec(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._last_result = None
self.sessions = set()
@staticmethod
def cleanup_code(content):
"""Automatically removes code blocks from the code."""
if content.startswith('```') and content.endswith('```'):
return '\n'.join(content.split('\n')[1:(- 1)])
return content.strip('` \n')
@staticmethod
def get_syntax_error(e):
if e.text is None:
return '```py\n{0.__class__.__name__}: {0}\n```'.format(e)
return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__)
@commands.command(hidden=True, name='exec')
async def _eval(self, ctx, *, body: str):
if ctx.author.id != self.bot.owner_id:
return
pag = Paginator(self.bot)
env = {
'bot': self.bot,
'ctx': ctx,
'channel': ctx.channel,
'author': ctx.author,
'server': ctx.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:
return await ctx.send(self.get_syntax_error(e))
func = env['func']
# noinspection PyBroadException
try:
with redirect_stdout(stdout):
ret = await func()
except Exception:
pag.add(stdout.getvalue())
pag.add(traceback.format_exc())
for page in pag.pages():
await ctx.send(page)
else:
value = stdout.getvalue()
# noinspection PyBroadException
try:
await ctx.message.add_reaction('')
except Exception:
pass
value = format_output(value)
pag.add(value)
pag.add(f'\nReturned: {ret}')
self._last_result = ret
for page in pag.pages():
await ctx.send(page)
@commands.command(hidden=True)
async def repl(self, ctx):
if ctx.author.id != self.bot.owner_id:
return
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:
await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.')
return
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('`')))
if response.author.id == self.bot.owner_id:
cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'):
await response.channel.send('Exiting.')
self.sessions.remove(msg.channel.id)
return
executor = exec
if cleaned.count('\n') == 0:
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 response.channel.send(self.get_syntax_error(e))
continue
variables['message'] = response
fmt = None
stdout = io.StringIO()
# noinspection PyBroadException
try:
with redirect_stdout(stdout):
result = executor(code, variables)
if inspect.isawaitable(result):
result = await result
except Exception:
value = stdout.getvalue()
fmt = '{}{}'.format(value, traceback.format_exc())
else:
value = stdout.getvalue()
if result is not None:
fmt = '{}{}'.format(value, result)
variables['_'] = result
elif value:
fmt = '{}'.format(value)
try:
if fmt is not None:
pag = Paginator(self.bot)
pag.add(fmt)
for page in pag.pages():
await response.channel.send(page)
await ctx.send(response.channel)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await msg.channel.send('Unexpected error: `{}`'.format(e))
@commands.command(hidden=True)
async def os(self, ctx, *, body: str):
if ctx.author.id != self.bot.owner_id:
return
try:
body = self.cleanup_code(body)
pag = Paginator(self.bot)
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 120))
for page in pag.pages():
await ctx.send(page)
await ctx.message.add_reaction('')
except asyncio.TimeoutError:
await ctx.send(f"Command did not complete in the time allowed.")
await ctx.message.add_reaction('')
@commands.command(name='haskell', aliases=['hs'])
async def haskell_compiler(self, ctx, *, body: str = None):
if ctx.author.id != self.bot.owner_id:
return
if body is None:
await ctx.send('Nothing to do.')
return
async with ctx.typing():
msg = await ctx.send('Warming up GHC... Please wait.')
try:
body = self.cleanup_code(body)
file_name = f'haskell_{datetime.utcnow().strftime("%Y%m%dT%H%M%S%f")}'
with open(f'{file_name}.hs', 'w') as f:
f.write(body)
pag = Paginator(self.bot)
compile_start = time.time()
pag.add(await asyncio.wait_for(
self.bot.loop.create_task(run_command(f'ghc -o {file_name} {file_name}.hs')), timeout=60))
compile_end = time.time()
compile_real = compile_end - compile_start
book = Book(pag, (msg, ctx.channel, ctx.bot, ctx.message))
await book.create_book()
pag = Paginator(self.bot)
if file_name in os.listdir():
run_start = time.time()
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(f'./{file_name}')),
timeout=600))
run_end = time.time()
run_real = run_end - run_start
total_real = run_real + compile_real
pag.add(f'\n\nCompile took {compile_real:.2f} seconds')
pag.add(f'Total Time {total_real:.2f} seconds')
book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message))
await msg.delete()
await book.create_book()
os.remove(file_name)
os.remove(f'{file_name}.hs')
os.remove(f'{file_name}.o')
os.remove(f'{file_name}.hi')
except asyncio.TimeoutError:
await msg.delete()
await ctx.send(f"Command did not complete in the time allowed.")
await ctx.message.add_reaction('')
except FileNotFoundError as e:
repl_log.warning(e)
def setup(bot):
bot.add_cog(Exec(bot))
+72 -9
View File
@@ -3,6 +3,69 @@ import asyncio
import typing
# noinspection PyDefaultArgument
def to_list_of_str(items, out: list = list(), level=1, recurse=0):
# noinspection PyShadowingNames
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def format_output(text):
if type(text) == list:
text = to_list_of_str(text)
elif type(text) == dict:
text = to_list_of_str(text)
return text
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
f'time -f "Process took %e seconds (%U user | %S system) and used %P of the CPU" {args}',
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
if stderr and stderr.strip() != '':
output = f'{stdout.decode().strip()}\n{stderr.decode().strip()}'
else:
output = stdout.decode().strip()
return output
# noinspection PyShadowingNames
class Paginator:
def __init__(self,
@@ -44,12 +107,12 @@ class Paginator:
self._embed_url = None
self._bot = bot
def set_embed_meta(self, title: str=None,
description: str=None,
color: discord.Colour=None,
thumbnail: str=None,
footer: str='',
url: str=None):
def set_embed_meta(self, title: str = None,
description: str = None,
color: discord.Colour = None,
thumbnail: str = None,
footer: str = '',
url: str = None):
if title and len(title) > self._max_field_name:
raise RuntimeError('Provided Title is too long')
else:
@@ -111,7 +174,7 @@ class Paginator:
_field_name = name
_field_value = self._prefix
def close_field(next_name: str=None):
def close_field(next_name: str = None):
nonlocal _field_name, _field_value, _fields
_field_value += self._suffix
if _field_value != self._prefix + self._suffix:
@@ -188,10 +251,10 @@ class Paginator:
# noinspection PyProtectedMember
return self.__class__ == other.__class__ and self._parts == other._parts
def add_page_break(self, *, to_beginning: bool=False) -> None:
def add_page_break(self, *, to_beginning: bool = False) -> None:
self.add(self._page_break, to_beginning=to_beginning)
def add(self, item: typing.Any, *, to_beginning: bool=False, keep_intact: bool=False, truncate=False) -> None:
def add(self, item: typing.Any, *, to_beginning: bool = False, keep_intact: bool = False, truncate=False) -> None:
item = str(item)
i = 0
if not keep_intact and not item == self._page_break: