Repository: Cog-Creators/Red-DiscordBot

Issue:
Add file output for code evaluation commands & Better long output handling
### What component of Red (cog, command, API) would you like to see improvements on?

Dev

### Describe the enhancement you're suggesting.

Currently when evaluating code with a large response, it would be shown in multiple messages. (when typing more)

Dropping a file would be way more convenient. 

Furthermore instead of typing more, a button will be nice. When clicking the button, the button disappears and a new message with a new button appears. (if there is further output)

### Anything else?

_No response_


Discussion:
I'd be OK with a config option for outputting long dev results to a file rather than with `send_interactive`, but there's currently no config editing commands in `Dev`, so that would probably need a new command.

Using buttons rather than typing would be a change to `send_interactive` itself, which has cascading effects on all other cogs that use that method. I don't think that is a good idea unless it is behind a kwarg as to not break existing usage. I also don't know if it's a good idea for dev commands in particular.
@Flame442 
> I'd be OK with a config option for outputting long dev results to a file rather than with send_interactive, but there's currently no config editing commands in Dev, so that would probably need a new command.

Maybe instead of having a config option it could be enabled automatically once the output doesn't fit in one message. (That's the way I do it in my custom discord.py bot)

> Using buttons rather than typing would be a change to send_interactive itself, which has cascading effects on all other cogs that use that method. I don't think that is a good idea unless it is behind a kwarg as to not break existing usage. I also don't know if it's a good idea for dev commands in particular.

Am not aware on all use-cases of `send_interactive `, but I fell like the current UX isn't that good. If you got more than a few messages, it can be annoying due to the delay and if you send more too fast, it doesn't get recognized. The button would be only clickable if the bot is ready to send a new message


> Maybe instead of having a config option it could be enabled automatically once the output doesn't fit in one message.

The current behavior (using `send_interactive`) only happens when the message is too long already. I am saying that whether it uses buttons or text is a matter of personal preference, so it should be configurable not entirely replaced to always use buttons. When I'm writing an eval, I'm just typing, not using my mouse, and I'd personally prefer to not have to use my mouse just to see more output. I think it would be fine to have an option (with a kwarg to `send_interactive` or otherwise), but it should **not** be the only option.

> If you got more than a few messages, it can be annoying due to the delay and if you send more too fast, it doesn't get recognized. The button would be only clickable if the bot is ready to send a new message

That's automatically handled by dpy, there isn't an easy way for the bot to know when it's rate limits are clear, since dpy just quietly handles that for it. Even if it was possible to know, adding a button after the message is sent requires editing the message, which is another api call. 

> The current behavior (using send_interactive) only happens when the message is too long already. I am saying that whether it uses buttons or text is a matter of personal preference, so it should be configurable not entirely replaced to always use buttons. When I'm writing an eval, I'm just typing, not using my mouse, and I'd personally prefer to not have to use my mouse just to see more output. I think it would be fine to have an option (with a kwarg to send_interactive or otherwise), but it should not be the only option.

I wanted to refer to the file, not to the button. If the output is longer than 1 message, the first message should include a file with the complete output **and** allows the user to type `more`

> That's automatically handled by dpy, there isn't an easy way for the bot to know when it's rate limits are clear, since dpy just quietly handles that for it. Even if it was possible to know, adding a button after the message is sent requires editing the message, which is another api call.

Good point. Keeping `more` at least as an option should be inevitable. Think better focusing then on what should be the default. 
Especially devs prefer using keyboard only, but I assume the avarage Discord user would benefit from buttons UX.
Actually an idea that might work to solve both issues is have `send_interactive` listen for the word `file` so whenever it's used we can use the traditional `more` response to receive the next page but you can optionally say `file` and it would then upload all the content as a file in the next message. This would be backwards compatible.

Resolving patch:
diff --git a/redbot/core/commands/context.py b/redbot/core/commands/context.py
--- a/redbot/core/commands/context.py
+++ b/redbot/core/commands/context.py
@@ -9,7 +9,7 @@
 from discord.ext.commands import Context as DPYContext
 
 from .requires import PermState
-from ..utils.chat_formatting import box
+from ..utils.chat_formatting import box, text_to_file
 from ..utils.predicates import MessagePredicate
 from ..utils import can_user_react_in, common_filters
 
@@ -150,7 +150,11 @@ async def react_quietly(
             return True
 
     async def send_interactive(
-        self, messages: Iterable[str], box_lang: str = None, timeout: int = 15
+        self,
+        messages: Iterable[str],
+        box_lang: str = None,
+        timeout: int = 15,
+        join_character: str = "",
     ) -> List[discord.Message]:
         """Send multiple messages interactively.
 
@@ -168,6 +172,9 @@ async def send_interactive(
         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.
+        join_character : str
+            The character used to join all the messages when the file output
+            is selected.
 
         """
         messages = tuple(messages)
@@ -189,13 +196,14 @@ async def send_interactive(
                     is_are = "are"
                 query = await self.send(
                     "There {} still {} message{} remaining. "
-                    "Type `more` to continue."
+                    "Type `more` to continue or `file` to upload all contents as a file."
                     "".format(is_are, n_remaining, plural)
                 )
+                pred = MessagePredicate.lower_contained_in(("more", "file"), self)
                 try:
                     resp = await self.bot.wait_for(
                         "message",
-                        check=MessagePredicate.lower_equal_to("more", self),
+                        check=pred,
                         timeout=timeout,
                     )
                 except asyncio.TimeoutError:
@@ -211,6 +219,9 @@ async def send_interactive(
                         # or channel is a DM
                         with contextlib.suppress(discord.HTTPException):
                             await query.delete()
+                    if pred.result == 1:
+                        await self.send(file=text_to_file(join_character.join(messages)))
+                        break
         return ret
 
     async def embed_colour(self):


Test patch: