Repository: Cog-Creators/Red-DiscordBot

Issue:
[V3] Escape markdown properly on Custom Command list
# Command bugs

Yes I found bug

#### Command name

`cc list`

#### What cog is this command from?

customcom

#### What were you expecting to happen?

It list the cc beautifully 

#### What actually happened?

![image](https://user-images.githubusercontent.com/340284/45192128-1cdf8e00-b279-11e8-8897-00b07ef90135.png)


#### How can we reproduce this issue?

Just have several custom commands with code block / inline code block markdown



Discussion:
Not exactly a duplicate but resolving #1841 would fix this, so I'm gonna close this in favour of that. Thanks for reporting though
Turns out #1841 was not a way of fixing this. Re-opening.

Resolving patch:
diff --git a/redbot/cogs/customcom/customcom.py b/redbot/cogs/customcom/customcom.py
--- a/redbot/cogs/customcom/customcom.py
+++ b/redbot/cogs/customcom/customcom.py
@@ -8,8 +8,9 @@
 import discord
 
 from redbot.core import Config, checks, commands
-from redbot.core.utils.chat_formatting import box, pagify
 from redbot.core.i18n import Translator, cog_i18n
+from redbot.core.utils import menus
+from redbot.core.utils.chat_formatting import box, pagify, escape
 from redbot.core.utils.predicates import MessagePredicate
 
 _ = Translator("CustomCommands", __file__)
@@ -114,7 +115,7 @@ async def edit(
         *,
         response=None,
         cooldowns: Mapping[str, int] = None,
-        ask_for: bool = True
+        ask_for: bool = True,
     ):
         """Edit an already existing custom command"""
         ccinfo = await self.db(ctx.guild).commands.get_raw(command, default=None)
@@ -323,12 +324,16 @@ async def cc_edit(self, ctx, command: str.lower, *, text: str = None):
             await ctx.send(e.args[0])
 
     @customcom.command(name="list")
-    async def cc_list(self, ctx):
-        """List all available custom commands."""
+    @checks.bot_has_permissions(add_reactions=True)
+    async def cc_list(self, ctx: commands.Context):
+        """List all available custom commands.
 
-        response = await CommandObj.get_commands(self.config.guild(ctx.guild))
+        The list displays a preview of each command's response, with
+        markdown escaped and newlines replaced with spaces.
+        """
+        cc_dict = await CommandObj.get_commands(self.config.guild(ctx.guild))
 
-        if not response:
+        if not cc_dict:
             await ctx.send(
                 _(
                     "There are no custom commands in this server."
@@ -338,8 +343,7 @@ async def cc_list(self, ctx):
             return
 
         results = []
-
-        for command, body in response.items():
+        for command, body in sorted(cc_dict.items(), key=lambda t: t[0]):
             responses = body["response"]
             if isinstance(responses, list):
                 result = ", ".join(responses)
@@ -347,15 +351,33 @@ async def cc_list(self, ctx):
                 result = responses
             else:
                 continue
-            results.append("{command:<15} : {result}".format(command=command, result=result))
-
-        _commands = "\n".join(results)
-
-        if len(_commands) < 1500:
-            await ctx.send(box(_commands))
+            # Replace newlines with spaces
+            # Cut preview to 52 characters max
+            if len(result) > 52:
+                result = result[:49] + "..."
+            # Replace newlines with spaces
+            result = result.replace("\n", " ")
+            # Escape markdown and mass mentions
+            result = escape(result, formatting=True, mass_mentions=True)
+            results.append((f"{ctx.clean_prefix}{command}", result))
+
+        if await ctx.embed_requested():
+            content = "\n".join(map("**{0[0]}** {0[1]}".format, results))
+            pages = list(pagify(content, page_length=1024))
+            embed_pages = []
+            for idx, page in enumerate(pages, start=1):
+                embed = discord.Embed(
+                    title=_("Custom Command List"),
+                    description=page,
+                    colour=await ctx.embed_colour(),
+                )
+                embed.set_footer(text=_("Page {num}/{total}").format(num=idx, total=len(pages)))
+                embed_pages.append(embed)
+            await menus.menu(ctx, embed_pages, menus.DEFAULT_CONTROLS)
         else:
-            for page in pagify(_commands, delims=[" ", "\n"]):
-                await ctx.author.send(box(page))
+            content = "\n".join(map("{0[0]:<12} : {0[1]}".format, results))
+            pages = list(map(box, pagify(content, page_length=2000, shorten_by=10)))
+            await menus.menu(ctx, pages, menus.DEFAULT_CONTROLS)
 
     async def on_message(self, message):
         is_private = isinstance(message.channel, discord.abc.PrivateChannel)
diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py
--- a/redbot/cogs/mod/mod.py
+++ b/redbot/cogs/mod/mod.py
@@ -825,7 +825,7 @@ async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason:
     @admin_or_voice_permissions(mute_members=True, deafen_members=True)
     @bot_has_voice_permissions(mute_members=True, deafen_members=True)
     async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
-        """Unban a the user from speaking and listening in the server's voice channels."""
+        """Unban a user from speaking and listening in the server's voice channels."""
         user_voice_state = user.voice
         if user_voice_state is None:
             await ctx.send(_("No voice state for that user!"))


Test patch: