diff --git a/redbot/core/context.py b/redbot/core/context.py index 482d29588..0e69168c4 100644 --- a/redbot/core/context.py +++ b/redbot/core/context.py @@ -1,12 +1,15 @@ -"""Module for Red's Context class - +""" The purpose of this module is to allow for Red to further customise the command invocation context provided by discord.py. """ +import asyncio +from typing import Iterable, List import discord from discord.ext import commands +from redbot.core.utils.chat_formatting import box + __all__ = ["RedContext"] TICK = "\N{WHITE HEAVY CHECK MARK}" @@ -20,9 +23,9 @@ class RedContext(commands.Context): This class inherits from `commands.Context `. """ - async def send_help(self): + async def send_help(self) -> List[discord.Message]: """Send the command help message. - + Returns ------- `list` of `discord.Message` @@ -36,7 +39,7 @@ class RedContext(commands.Context): ret.append(await self.send(page)) return ret - async def tick(self): + async def tick(self) -> bool: """Add a tick reaction to the command message. Returns @@ -51,3 +54,60 @@ class RedContext(commands.Context): return False else: return True + + async def send_interactive(self, + messages: Iterable[str], + box_lang: str=None, + timeout: int=15) -> List[discord.Message]: + """Send multiple messages interactively. + + The user will be prompted for whether or not they would like to view + the next message, one at a time. They will also be notified of how + many messages are remaining on each prompt. + + Parameters + ---------- + messages : `iterable` of `str` + The messages to send. + box_lang : str + If specified, each message will be contained within a codeblock of + this language. + timeout : int + How long the user has to respond to the prompt before it times out. + After timing out, the bot deletes its prompt message. + + """ + messages = tuple(messages) + ret = [] + + more_check = lambda m: (m.author == self.author and + m.channel == self.channel and + m.content.lower() == "more") + + for idx, page in enumerate(messages, 1): + if box_lang is None: + msg = await self.send(page) + else: + msg = await self.send(box(page, lang=box_lang)) + ret.append(msg) + n_remaining = len(messages) - idx + if n_remaining > 0: + if n_remaining == 1: + plural = "" + is_are = "is" + else: + plural = "s" + is_are = "are" + query = await self.send( + "There {} still {} message{} remaining. " + "Type `more` to continue." + "".format(is_are, n_remaining, plural)) + try: + resp = await self.bot.wait_for( + 'message', check=more_check, timeout=timeout) + except asyncio.TimeoutError: + await query.delete() + break + else: + await self.channel.delete_messages((query, resp)) + return ret diff --git a/redbot/core/dev_commands.py b/redbot/core/dev_commands.py index b71f76218..ed71127a5 100644 --- a/redbot/core/dev_commands.py +++ b/redbot/core/dev_commands.py @@ -51,6 +51,11 @@ class Dev: ''.format(e, '^', type(e).__name__), lang="py") + @staticmethod + def get_pages(msg: str): + """Pagify the given message for output to the user.""" + return pagify(msg, delims=["\n", " "], priority=True, shorten_by=10) + @staticmethod def sanitize_output(ctx: commands.Context, input_: str) -> str: """Hides the bot's token from a string.""" @@ -115,7 +120,7 @@ class Dev: result = self.sanitize_output(ctx, str(result)) - await ctx.send(box(result, lang="py")) + await ctx.send_interactive(self.get_pages(result), box_lang="py") @commands.command(name='eval') @checks.is_owner() @@ -162,28 +167,24 @@ class Dev: return await ctx.send(self.get_syntax_error(e)) func = env['func'] + result = None try: with redirect_stdout(stdout): - ret = await func() + result = await func() except: - value = stdout.getvalue() - await ctx.send( - box('\n{}{}'.format(value, traceback.format_exc()), lang="py")) + printed = "{}{}".format(stdout.getvalue(), traceback.format_exc()) else: - value = stdout.getvalue() - try: - await ctx.message.add_reaction('\N{White Heavy Check Mark}') - except: - pass + printed = stdout.getvalue() + await ctx.tick() - if ret is None: - if value: - value = self.sanitize_output(ctx, str(value)) - await ctx.send(box(value, lang="py")) - else: - self._last_result = ret - ret = self.sanitize_output(ctx, str(ret)) - await ctx.send(box("{}{}".format(value, ret), lang="py")) + if result is not None: + self._last_result = result + msg = "{}{}".format(printed, result) + else: + msg = printed + msg = self.sanitize_output(ctx, msg) + + await ctx.send_interactive(self.get_pages(msg), box_lang="py") @commands.command() @checks.is_owner() @@ -260,7 +261,6 @@ class Dev: result = await result except: value = stdout.getvalue() - value = self.sanitize_output(ctx, value) msg = "{}{}".format(value, traceback.format_exc()) else: value = stdout.getvalue() @@ -270,10 +270,10 @@ class Dev: elif value: msg = "{}".format(value) + msg = self.sanitize_output(ctx, msg) + try: - for page in pagify(str(msg), shorten_by=12): - page = self.sanitize_output(ctx, page) - await ctx.send(box(page, "py")) + await ctx.send_interactive(self.get_pages(msg), box_lang="py") except discord.Forbidden: pass except discord.HTTPException as e: