Repository: Cog-Creators/Red-DiscordBot

Issue:
[Alias] [p]alias list with long alias names exceeding char limit
# Command bugs

<!-- 
Did you find a bug with a command? Fill out the following:
-->

#### Command name
`[p]alias list / alias global list`

#### What cog is this command from?
Alias

#### What were you expecting to happen?
`[p]alias list / [p]alias global list` to display a list of the aliases / global aliases.

#### What actually happened?
My aliases exceeded the character limit per message in discord, leading to bad request.
```py
Exception in command 'alias list'
Traceback (most recent call last):
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\ext\commands\core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\redbot\cogs\alias\alias.py", line 296, in _list_alias
    await ctx.send(box("\n".join(names), "diff"))
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\redbot\core\commands\context.py", line 93, in send
    return await super().send(content=content, **kwargs)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\abc.py", line 856, in send
    data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\http.py", line 225, in request
    raise HTTPException(r, data)
discord.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In content: Must be 2000 or fewer in length.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\ext\commands\bot.py", line 892, in invoke
    await ctx.command.invoke(ctx)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\redbot\core\commands\commands.py", line 799, in invoke
    await super().invoke(ctx)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\ext\commands\core.py", line 1234, in invoke
    await ctx.invoked_subcommand.invoke(ctx)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\ext\commands\core.py", line 797, in invoke
    await injected(*ctx.args, **ctx.kwargs)
  File "C:\Users\dariu\AppData\Roaming\Python\Python38\site-packages\discord\ext\commands\core.py", line 92, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In content: Must be 2000 or fewer in length.
```

#### How can we reproduce this issue?

1. Create a few aliases / global aliases with long names, enough to exceed 2000 characters combined.
2. Run `[p]alias list / [p]alias global list` respectively.



Discussion:

Resolving patch:
diff --git a/redbot/cogs/alias/alias.py b/redbot/cogs/alias/alias.py
--- a/redbot/cogs/alias/alias.py
+++ b/redbot/cogs/alias/alias.py
@@ -1,12 +1,13 @@
 from copy import copy
 from re import search
 from string import Formatter
-from typing import Dict
+from typing import Dict, List
 
 import discord
 from redbot.core import Config, commands, checks
 from redbot.core.i18n import Translator, cog_i18n
-from redbot.core.utils.chat_formatting import box
+from redbot.core.utils.chat_formatting import box, pagify
+from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
 
 from redbot.core.bot import Red
 from .alias_entry import AliasEntry, AliasCache, ArgParseError
@@ -100,6 +101,28 @@ async def call_alias(self, message: discord.Message, prefix: str, alias: AliasEn
         )
         await self.bot.process_commands(new_message)
 
+    async def paginate_alias_list(
+        self, ctx: commands.Context, alias_list: List[AliasEntry]
+    ) -> None:
+        names = sorted(["+ " + a.name for a in alias_list])
+        message = "\n".join(names)
+        temp = list(pagify(message, delims=["\n"], page_length=1850))
+        alias_list = []
+        count = 0
+        for page in temp:
+            count += 1
+            page = page.lstrip("\n")
+            page = (
+                _("Aliases:\n")
+                + page
+                + _("\n\nPage {page}/{total}").format(page=count, total=len(temp))
+            )
+            alias_list.append(box("".join(page), "diff"))
+        if len(alias_list) == 1:
+            await ctx.send(alias_list[0])
+            return
+        await menu(ctx, alias_list, DEFAULT_CONTROLS)
+
     @commands.group()
     @commands.guild_only()
     async def alias(self, ctx: commands.Context):
@@ -287,22 +310,22 @@ async def _del_global_alias(self, ctx: commands.Context, alias_name: str):
 
     @alias.command(name="list")
     @commands.guild_only()
+    @checks.bot_has_permissions(add_reactions=True)
     async def _list_alias(self, ctx: commands.Context):
         """List the available aliases on this server."""
         guild_aliases = await self._aliases.get_guild_aliases(ctx.guild)
         if not guild_aliases:
             return await ctx.send(_("There are no aliases on this server."))
-        names = [_("Aliases:")] + sorted(["+ " + a.name for a in guild_aliases])
-        await ctx.send(box("\n".join(names), "diff"))
+        await self.paginate_alias_list(ctx, guild_aliases)
 
     @global_.command(name="list")
+    @checks.bot_has_permissions(add_reactions=True)
     async def _list_global_alias(self, ctx: commands.Context):
         """List the available global aliases on this bot."""
         global_aliases = await self._aliases.get_global_aliases()
         if not global_aliases:
             return await ctx.send(_("There are no global aliases."))
-        names = [_("Aliases:")] + sorted(["+ " + a.name for a in global_aliases])
-        await ctx.send(box("\n".join(names), "diff"))
+        await self.paginate_alias_list(ctx, global_aliases)
 
     @commands.Cog.listener()
     async def on_message_without_command(self, message: discord.Message):


Test patch: