Repository: Cog-Creators/Red-DiscordBot

Issue:
[Downloader] cog update can error if the output of the command is greater than 2000 characters
# Command bugs

Cog update can error when the output is greater than 2000 characters.

#### Command name

`[p]cog update`

#### What cog is this command from?

Downloader

#### What were you expecting to happen?

I was expecting the output of cog update to list the updated cogs.

#### What actually happened?

The command errored stating the request was larger than 2000 characters.

#### How can we reproduce this issue?

Have a LOT of cogs installed and run `[p]cog update` after migrating from 3.1.9 to 3.2.2.



Discussion:

Resolving patch:
diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py
--- a/redbot/cogs/downloader/downloader.py
+++ b/redbot/cogs/downloader/downloader.py
@@ -418,6 +418,11 @@ async def _delete_cog(target: Path) -> None:
         elif target.is_file():
             os.remove(str(target))
 
+    @staticmethod
+    async def send_pagified(target: discord.abc.Messageable, content: str) -> None:
+        for page in pagify(content):
+            await target.send(page)
+
     @commands.command()
     @checks.is_owner()
     async def pipinstall(self, ctx: commands.Context, *deps: str) -> None:
@@ -550,7 +555,7 @@ async def _repo_update(self, ctx: commands.Context, *repos: Repo) -> None:
             if failed:
                 message += "\n" + self.format_failed_repos(failed)
 
-        await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
     @commands.group()
     @checks.is_owner()
@@ -596,12 +601,13 @@ async def _cog_reinstallreqs(self, ctx: commands.Context) -> None:
                 tuple(map(inline, libnames))
             )
         if message:
-            await ctx.send(
+            await self.send_pagified(
+                ctx,
                 _(
                     "Cog requirements and shared libraries for all installed cogs"
                     " have been reinstalled but there were some errors:\n"
                 )
-                + message
+                + message,
             )
         else:
             await ctx.send(
@@ -643,8 +649,7 @@ async def _cog_installrev(
                             f"**{candidate.object_type} {candidate.rev}**"
                             f" - {candidate.description}\n"
                         )
-                    for page in pagify(msg):
-                        await ctx.send(msg)
+                    await self.send_pagified(ctx, msg)
                     return
                 except errors.UnknownRevision:
                     await ctx.send(
@@ -658,14 +663,14 @@ async def _cog_installrev(
             async with repo.checkout(commit, exit_to_rev=repo.branch):
                 cogs, message = await self._filter_incorrect_cogs_by_names(repo, cog_names)
                 if not cogs:
-                    await ctx.send(message)
+                    await self.send_pagified(ctx, message)
                     return
                 failed_reqs = await self._install_requirements(cogs)
                 if failed_reqs:
                     message += _("\nFailed to install requirements: ") + humanize_list(
                         tuple(map(inline, failed_reqs))
                     )
-                    await ctx.send(message)
+                    await self.send_pagified(ctx, message)
                     return
 
                 installed_cogs, failed_cogs = await self._install_cogs(cogs)
@@ -711,7 +716,7 @@ async def _cog_installrev(
                     + message
                 )
         # "---" added to separate cog install messages from Downloader's message
-        await ctx.send(f"{message}{deprecation_notice}\n---")
+        await self.send_pagified(ctx, f"{message}{deprecation_notice}\n---")
         for cog in installed_cogs:
             if cog.install_msg:
                 await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
@@ -759,7 +764,7 @@ async def _cog_uninstall(self, ctx: commands.Context, *cogs: InstalledCog) -> No
                         " If so, ensure the cogs have been unloaded with `{prefix}unload {cogs}`."
                     ).format(prefix=ctx.prefix, cogs=" ".join(failed_cogs))
                 )
-        await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
     @cog.command(name="pin", usage="<cogs>")
     async def _cog_pin(self, ctx: commands.Context, *cogs: InstalledCog) -> None:
@@ -782,7 +787,7 @@ async def _cog_pin(self, ctx: commands.Context, *cogs: InstalledCog) -> None:
             message += _("Pinned cogs: ") + humanize_list(cognames)
         if already_pinned:
             message += _("\nThese cogs were already pinned: ") + humanize_list(already_pinned)
-        await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
     @cog.command(name="unpin", usage="<cogs>")
     async def _cog_unpin(self, ctx: commands.Context, *cogs: InstalledCog) -> None:
@@ -805,7 +810,7 @@ async def _cog_unpin(self, ctx: commands.Context, *cogs: InstalledCog) -> None:
             message += _("Unpinned cogs: ") + humanize_list(cognames)
         if not_pinned:
             message += _("\nThese cogs weren't pinned: ") + humanize_list(not_pinned)
-        await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
     @cog.command(name="checkforupdates")
     async def _cog_checkforupdates(self, ctx: commands.Context) -> None:
@@ -837,7 +842,7 @@ async def _cog_checkforupdates(self, ctx: commands.Context) -> None:
             if failed:
                 message += "\n" + self.format_failed_repos(failed)
 
-            await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
     @cog.command(name="update")
     async def _cog_update(self, ctx: commands.Context, *cogs: InstalledCog) -> None:
@@ -873,7 +878,6 @@ async def _cog_update_logic(
         rev: Optional[str] = None,
         cogs: Optional[List[InstalledModule]] = None,
     ) -> None:
-        message = ""
         failed_repos = set()
         updates_available = set()
 
@@ -886,7 +890,7 @@ async def _cog_update_logic(
                     await repo.update()
                 except errors.UpdateError:
                     message = self.format_failed_repos([repo.name])
-                    await ctx.send(message)
+                    await self.send_pagified(ctx, message)
                     return
 
                 try:
@@ -900,11 +904,10 @@ async def _cog_update_logic(
                             f"**{candidate.object_type} {candidate.rev}**"
                             f" - {candidate.description}\n"
                         )
-                    for page in pagify(msg):
-                        await ctx.send(msg)
+                    await self.send_pagified(ctx, msg)
                     return
                 except errors.UnknownRevision:
-                    message += _(
+                    message = _(
                         "Error: there is no revision `{rev}` in repo `{repo.name}`"
                     ).format(rev=rev, repo=repo)
                     await ctx.send(message)
@@ -921,6 +924,8 @@ async def _cog_update_logic(
 
             pinned_cogs = {cog for cog in cogs_to_check if cog.pinned}
             cogs_to_check -= pinned_cogs
+
+            message = ""
             if not cogs_to_check:
                 cogs_to_update = libs_to_update = ()
                 message += _("There were no cogs to check.")
@@ -976,7 +981,7 @@ async def _cog_update_logic(
         if repos_with_libs:
             message += DEPRECATION_NOTICE.format(repo_list=humanize_list(list(repos_with_libs)))
 
-        await ctx.send(message)
+        await self.send_pagified(ctx, message)
 
         if updates_available and updated_cognames:
             await self._ask_for_cog_reload(ctx, updated_cognames)


Test patch: