async def jsk_curl(self, ctx: commands.Context, url: str): """ Download and display a text file from the internet. This command is similar to jsk cat, but accepts a URL. """ # remove embed maskers if present url = url.lstrip("<").rstrip(">") async with ReplResponseReactor(ctx.message): async with aiohttp.ClientSession() as session: async with session.get(url) as response: data = await response.read() hints = ( response.content_type, url ) code = response.status if not data: return await ctx.send(f"HTTP response was empty (status code {code}).") try: paginator = WrappedFilePaginator(io.BytesIO(data), language_hints=hints, max_size=1985) except UnicodeDecodeError: return await ctx.send(f"Couldn't determine the encoding of the response. (status code {code})") except ValueError as exc: return await ctx.send(f"Couldn't read response (status code {code}), {exc}") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) await interface.send_to(ctx)
async def jsk_disassemble(self, ctx: commands.Context, *, argument: codeblock_converter): """ Disassemble Python code into bytecode. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) async with ReplResponseReactor(ctx.message): text = "\n".join(disassemble(argument.content, arg_dict=arg_dict)) # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(text) < filesize_threshold: await ctx.send(file=discord.File( filename="dis.py", fp=io.BytesIO(text.encode('utf-8')) )) else: paginator = WrappedPaginator(prefix='```py', max_size=1985) paginator.add_line(text) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) await interface.send_to(ctx)
async def git(self, ctx: commands.Context, pull_push, *, commit_msg=None): """ Executes git statements in the system shell. This uses the system shell as defined in $SHELL, or `/bin/bash` otherwise. Execution can be cancelled by closing the paginator. """ if pull_push == "push": shellcmd = f'sudo git add .&&sudo git commit -m "{commit_msg}"&&sudo git push' if pull_push == "pull": shellcmd = 'sudo git pull' if pull_push not in ['pull', 'push']: return await ctx.send("Invalid option given") async with ReplResponseReactor(ctx.message): paginator = WrappedPaginator(prefix="```sh", max_size=1985) paginator.add_line(f"$ git {pull_push}\n") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) self.bot.loop.create_task(interface.send_to(ctx)) if commit_msg is None: commit_msg = "File changes" with ShellReader(shellcmd) as reader: async for line in reader: if interface.closed: return await interface.add_line(line)
async def jsk_python(self, ctx: commands.Context, *, argument: codeblock_converter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor( argument.content, scope, arg_dict=arg_dict ) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.send(file=result)) elif isinstance(result, discord.Embed): send(await ctx.send(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) else: if not isinstance(result, str): # repr all non-strings result = repr(result) if len(result) > 2000: # inconsistency here, results get wrapped in codeblocks when they are too large # but don't if they're not. probably not that bad, but noting for later review paginator = WrappedPaginator( prefix="```py", suffix="```", max_size=1985 ) paginator.add_line(result) interface = PaginatorInterface( ctx.bot, paginator, owner=ctx.author ) send(await interface.send_to(ctx)) else: if result.strip() == "": result = "\u200b" send( await ctx.send( result.replace( self.bot.http.token, "[token omitted]", ) ) ) finally: scope.clear_intersection(arg_dict)
async def git(self, ctx, pull_push, *, message=None): """ Executes git statements in the system shell. This uses the system shell as defined in $SHELL, or `/bin/bash` otherwise. Execution can be cancelled by closing the paginator. """ message = message if message is not None else "Updated files." if pull_push == "push": shellcmd = f'sudo git add .&&sudo git commit -m "{message}"&&sudo git push' elif pull_push == "pull": shellcmd = 'sudo git pull' else: return await ctx.send("Invalid option given") async with ReplResponseReactor(ctx.message): paginator = WrappedPaginator(prefix="```sh", max_size=1985) paginator.add_line(f"$ git {pull_push}\n") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) self.bot.loop.create_task(interface.send_to(ctx)) with ShellReader(shellcmd) as reader: async for line in reader: if interface.closed: return await interface.add_line(line)
async def jsk_shell(self, ctx: commands.Context, *, argument: codeblock_converter): """ Executes statements in the system shell. This uses the system shell as defined in $SHELL, or `/bin/bash` otherwise. Execution can be cancelled by closing the paginator. """ async with ReplResponseReactor(ctx.message): with self.submit(ctx): with ShellReader(argument.content) as reader: prefix = "```" + reader.highlight paginator = WrappedPaginator(prefix=prefix, max_size=1975) paginator.add_line(f"{reader.ps1} {argument.content}\n") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) self.bot.loop.create_task(interface.send_to(ctx)) async for line in reader: if interface.closed: return await interface.add_line(line) await interface.add_line( f"\n[status] Return code {reader.close_code}")
async def jsk_python(self, ctx: commands.Context, *, argument: codeblock_converter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx, Flags.SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result send(await self.jsk_python_result_handling(ctx, result)) finally: scope.clear_intersection(arg_dict)
async def jsk_python_inspect(self, ctx: commands.Context, *, argument: codeblock_converter): """ Evaluation of Python code with inspect information. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): self.last_result = result header = repr(result).replace("``", "`\u200b`").replace(self.bot.http.token, "[token omitted]") if len(header) > 485: header = header[0:482] + "..." paginator = WrappedPaginator(prefix=f"```prolog\n=== {header} ===\n", max_size=1985) for name, res in all_inspections(result): paginator.add_line(f"{name:16.16} :: {res}") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def jsk_python_inspect(self, ctx: commands.Context, *, argument: codeblock_converter): """ Evaluation of Python code with inspect information. """ arg_dict = get_var_dict_from_ctx(ctx, Flags.SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): self.last_result = result header = repr(result).replace("``", "`\u200b`").replace( self.bot.http.token, "[token omitted]") if len(header) > 485: header = header[0:482] + "..." lines = [f"=== {header} ===", ""] for name, res in all_inspections(result): lines.append(f"{name:16.16} :: {res}") docstring = (inspect.getdoc(result) or '').strip() if docstring: lines.append(f"\n=== Help ===\n\n{docstring}") text = "\n".join(lines) if use_file_check(ctx, len( text)): # File "full content" preview limit send(await ctx.send(file=discord.File( filename="inspection.prolog", fp=io.BytesIO(text.encode('utf-8'))))) else: paginator = WrappedPaginator(prefix="```prolog", max_size=1985) paginator.add_line(text) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def jsk_curl(self, ctx: commands.Context, url: str): """ Download and display a text file from the internet. This command is similar to jsk cat, but accepts a URL. """ # remove embed maskers if present url = url.lstrip("<").rstrip(">") async with ReplResponseReactor(ctx.message): async with aiohttp.ClientSession() as session: async with session.get(url) as response: data = await response.read() hints = (response.content_type, url) code = response.status if not data: return await ctx.send( f"HTTP response was empty (status code {code}).") # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(data) < filesize_threshold: # Shallow language detection language = None for hint in hints: language = get_language(hint) if language: break await ctx.send( file=discord.File(filename=f"response.{language or 'txt'}", fp=io.BytesIO(data))) else: try: paginator = WrappedFilePaginator(io.BytesIO(data), language_hints=hints, max_size=1985) except UnicodeDecodeError: return await ctx.send( f"Couldn't determine the encoding of the response. (status code {code})" ) except ValueError as exc: return await ctx.send( f"Couldn't read response (status code {code}), {exc}") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) await interface.send_to(ctx)
async def jsk_ast(self, ctx: commands.Context, *, argument: codeblock_converter): """ Disassemble Python code into AST. """ async with ReplResponseReactor(ctx.message): text = create_tree(argument.content, use_ansi=Flags.use_ansi(ctx)) await ctx.send(file=discord.File( filename="ast.ansi", fp=io.BytesIO(text.encode('utf-8'))))
async def jsk_python(self, ctx: SubContext, *, argument: codeblock_converter): arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.send(file=result)) elif isinstance(result, discord.Embed): send(await ctx.send(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) elif isinstance(result, DCMenuPages): send(await result.start(ctx)) else: if not isinstance(result, str): # repr all non-strings result = repr(result) result = result.replace(self.bot.http.token, "[token omitted]") if result.strip() == "": result = "[empty string]" if len(result) > 2000: paginator = PartitionPaginator(prefix="```py") paginator.add_line(result) source = NormalPageSource(paginator.pages) menu = DCMenuPages(source) send(await menu.start(ctx)) else: send(await ctx.send(f"```py\n{result}```")) finally: scope.clear_intersection(arg_dict)
async def jsk_disassemble(self, ctx: commands.Context, *, argument: codeblock_converter): """ Disassemble Python code into bytecode. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) async with ReplResponseReactor(ctx.message): paginator = WrappedPaginator(prefix='```py', suffix='```', max_size=1985) for line in disassemble(argument.content, arg_dict=arg_dict): paginator.add_line(line) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) await interface.send_to(ctx)
async def jsk_python_inspect(self, ctx: commands.Context, *, argument: codeblock_converter): # pylint: disable=too-many-locals """ Evaluation of Python code with inspect information. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): self.last_result = result header = repr(result).replace("``", "`\u200b`").replace(self.bot.http.token, "[token omitted]") if len(header) > 485: header = header[0:482] + "..." lines = [f"=== {header} ===", ""] for name, res in all_inspections(result): lines.append(f"{name:16.16} :: {res}") text = "\n".join(lines) # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(text) < filesize_threshold: send(await ctx.send(file=discord.File( filename="inspection.prolog", fp=io.BytesIO(text.encode('utf-8')) ))) else: paginator = WrappedPaginator(prefix="```prolog", max_size=1985) paginator.add_line(text) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)
async def jsk_debug(self, ctx: commands.Context, *, command_string: str): """ Run a command timing execution and catching exceptions. """ alt_ctx = await copy_context_with(ctx, content=ctx.prefix + command_string) if alt_ctx.command is None: return await ctx.send(f'Command "{alt_ctx.invoked_with}" is not found') start = time.perf_counter() async with ReplResponseReactor(ctx.message): with self.submit(ctx): await alt_ctx.command.invoke(alt_ctx) end = time.perf_counter() return await ctx.send(f"Command `{alt_ctx.command.qualified_name}` finished in {end - start:.3f}s.")
async def speedtest(self, ctx): async with ReplResponseReactor(ctx.message): with ShellReader("speedtest-cli") as reader: prefix = "```" + reader.highlight paginator = WrappedPaginator(prefix=prefix, max_size=1975) paginator.add_line(f"{reader.ps1} 'speedtest-cli'\n") interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) self.bot.loop.create_task(interface.send_to(ctx)) async for line in reader: if interface.closed: return await interface.add_line(line) await interface.add_line( f"\n[status] Return code {reader.close_code}")
async def jsk_python(self, ctx: commands.Context, *, argument: CodeblockConverter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx) scope = self.scope scope.clean() arg_dict["_"] = self.last_result async with ReplResponseReactor(ctx.message): with self.submit(ctx): async for result in AsyncCodeExecutor( argument.content, scope, arg_dict=arg_dict ): if result is None: continue self.last_result = result if isinstance(result, discord.File): await ctx.send(file=result) elif isinstance(result, discord.Embed): await ctx.send(embed=result) else: if not isinstance(result, str): # repr all non-strings result = repr(result) if len(result) > 2000: result = result[0:1997] + "..." elif result.strip() == "": result = "\u200b" await ctx.send( result.replace(self.bot.http.token, "[token omitted]") )
async def jsk_disassemble(self, ctx: commands.Context, *, argument: codeblock_converter): """ Disassemble Python code into bytecode. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) async with ReplResponseReactor(ctx.message): text = "\n".join(disassemble(argument.content, arg_dict=arg_dict)) if len(text) < 50_000: # File "full content" preview limit await ctx.send(file=discord.File( filename="dis.py", fp=io.BytesIO(text.encode('utf-8')))) else: paginator = WrappedPaginator(prefix='```py', max_size=1985) paginator.add_line(text) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) await interface.send_to(ctx)
async def _dev_python(self, ctx, *, code: codeblock_converter): ''' Evaluate python code ''' arg_dict = get_var_dict_from_ctx(ctx, "") arg_dict["_"] = self.last_result scope = self._scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(code.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.reply(file=result)) elif isinstance(result, discord.Embed): send(await ctx.reply(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) else: if not isinstance(result, str): result = repr(result) if len(result) > 2000: result = result.replace("`", "`\u200b") width = 2000 pages = [ result[i:i + width] for i in range(0, len(result), width) ] for page in pages: embed = discord.Embed( description=f"```py\n{page}```", colour=discord.Colour.teal()) pages[pages.index(page)] = embed embedpaginator = paginator( ctx, remove_reactions=True) embedpaginator.add_reaction( "\U000023ea", "first") embedpaginator.add_reaction( "\U000025c0", "back") embedpaginator.add_reaction( "\U0001f5d1", "delete") embedpaginator.add_reaction( "\U000025b6", "next") embedpaginator.add_reaction( "\U000023e9", "last") send(await embedpaginator.send(pages)) else: if result.strip() == '': result = "\u200b" embed = discord.Embed( description= f"```py\n{result.replace(self.bot.http.token, '[token]')}```", colour=discord.Colour.teal()) send(await ctx.reply(embed=embed, mention_author=False)) finally: scope.clear_intersection(arg_dict)
async def jsk_python(self, ctx: commands.Context, *, argument: codeblock_converter): """ Direct evaluation of Python code. """ arg_dict = get_var_dict_from_ctx(ctx, SCOPE_PREFIX) arg_dict["_"] = self.last_result scope = self.scope try: async with ReplResponseReactor(ctx.message): with self.submit(ctx): executor = AsyncCodeExecutor(argument.content, scope, arg_dict=arg_dict) async for send, result in AsyncSender(executor): if result is None: continue self.last_result = result if isinstance(result, discord.File): send(await ctx.send(file=result)) elif isinstance(result, discord.Embed): send(await ctx.send(embed=result)) elif isinstance(result, PaginatorInterface): send(await result.send_to(ctx)) else: if not isinstance(result, str): # repr all non-strings result = repr(result) # Guild's advertised limit minus 1KiB for the HTTP content filesize_threshold = (ctx.guild.filesize_limit if ctx.guild else 8 * 1024 * 1024) - 1024 if len(result) <= 2000: if result.strip() == '': result = "\u200b" send(await ctx.send(result.replace(self.bot.http.token, "[token omitted]"))) elif len(result) < filesize_threshold: # Discord's desktop and web client now supports an interactive file content # display for files encoded in UTF-8. # Since this avoids escape issues and is more intuitive than pagination for # long results, it will now be prioritized over PaginatorInterface if the # resultant content is below the filesize threshold send(await ctx.send(file=discord.File( filename="output.py", fp=io.BytesIO(result.encode('utf-8')) ))) else: # inconsistency here, results get wrapped in codeblocks when they are too large # but don't if they're not. probably not that bad, but noting for later review paginator = WrappedPaginator(prefix='```py', suffix='```', max_size=1985) paginator.add_line(result) interface = PaginatorInterface(ctx.bot, paginator, owner=ctx.author) send(await interface.send_to(ctx)) finally: scope.clear_intersection(arg_dict)