We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[serverinfo/userinfo] Pluralize strings
# Commands improvement

#### Command name

`[p]userinfo` and `[p]serverinfo`

#### What cog is this command from?

From `Mod` cog.

#### What improvement are you expecting to?

I would like to see all strings pluralized (ie. `Text Channels` being `Text Channel` if there's only one channel and so on).

#### What actually happened?

Roles and previous nickname/names aren't pluralized.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redbot/cogs/mod/names.py`
Content:
```
1 from datetime import datetime
2 from typing import cast
3 
4 import discord
5 from redbot.core import commands, i18n, checks
6 from redbot.core.utils.common_filters import (
7     filter_invites,
8     filter_various_mentions,
9     escape_spoilers_and_mass_mentions,
10 )
11 from redbot.core.utils.mod import get_audit_reason
12 from .abc import MixinMeta
13 
14 _ = i18n.Translator("Mod", __file__)
15 
16 
17 class ModInfo(MixinMeta):
18     """
19     Commands regarding names, userinfo, etc.
20     """
21 
22     async def get_names_and_nicks(self, user):
23         names = await self.config.user(user).past_names()
24         nicks = await self.config.member(user).past_nicks()
25         if names:
26             names = [escape_spoilers_and_mass_mentions(name) for name in names if name]
27         if nicks:
28             nicks = [escape_spoilers_and_mass_mentions(nick) for nick in nicks if nick]
29         return names, nicks
30 
31     @commands.command()
32     @commands.guild_only()
33     @commands.bot_has_permissions(manage_nicknames=True)
34     @checks.admin_or_permissions(manage_nicknames=True)
35     async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname: str = ""):
36         """Change a user's nickname.
37 
38         Leaving the nickname empty will remove it.
39         """
40         nickname = nickname.strip()
41         me = cast(discord.Member, ctx.me)
42         if not nickname:
43             nickname = None
44         elif not 2 <= len(nickname) <= 32:
45             await ctx.send(_("Nicknames must be between 2 and 32 characters long."))
46             return
47         if not (
48             (me.guild_permissions.manage_nicknames or me.guild_permissions.administrator)
49             and me.top_role > user.top_role
50             and user != ctx.guild.owner
51         ):
52             await ctx.send(
53                 _(
54                     "I do not have permission to rename that member. They may be higher than or "
55                     "equal to me in the role hierarchy."
56                 )
57             )
58         else:
59             try:
60                 await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
61             except discord.Forbidden:
62                 # Just in case we missed something in the permissions check above
63                 await ctx.send(_("I do not have permission to rename that member."))
64             except discord.HTTPException as exc:
65                 if exc.status == 400:  # BAD REQUEST
66                     await ctx.send(_("That nickname is invalid."))
67                 else:
68                     await ctx.send(_("An unexpected error has occured."))
69             else:
70                 await ctx.send(_("Done."))
71 
72     def handle_custom(self, user):
73         a = [c for c in user.activities if c.type == discord.ActivityType.custom]
74         if not a:
75             return None, discord.ActivityType.custom
76         a = a[0]
77         c_status = None
78         if not a.name and not a.emoji:
79             return None, discord.ActivityType.custom
80         elif a.name and a.emoji:
81             c_status = _("Custom: {emoji} {name}").format(emoji=a.emoji, name=a.name)
82         elif a.emoji:
83             c_status = _("Custom: {emoji}").format(emoji=a.emoji)
84         elif a.name:
85             c_status = _("Custom: {name}").format(name=a.name)
86         return c_status, discord.ActivityType.custom
87 
88     def handle_playing(self, user):
89         p_acts = [c for c in user.activities if c.type == discord.ActivityType.playing]
90         if not p_acts:
91             return None, discord.ActivityType.playing
92         p_act = p_acts[0]
93         act = _("Playing: {name}").format(name=p_act.name)
94         return act, discord.ActivityType.playing
95 
96     def handle_streaming(self, user):
97         s_acts = [c for c in user.activities if c.type == discord.ActivityType.streaming]
98         if not s_acts:
99             return None, discord.ActivityType.streaming
100         s_act = s_acts[0]
101         if isinstance(s_act, discord.Streaming):
102             act = _("Streaming: [{name}{sep}{game}]({url})").format(
103                 name=discord.utils.escape_markdown(s_act.name),
104                 sep=" | " if s_act.game else "",
105                 game=discord.utils.escape_markdown(s_act.game) if s_act.game else "",
106                 url=s_act.url,
107             )
108         else:
109             act = _("Streaming: {name}").format(name=s_act.name)
110         return act, discord.ActivityType.streaming
111 
112     def handle_listening(self, user):
113         l_acts = [c for c in user.activities if c.type == discord.ActivityType.listening]
114         if not l_acts:
115             return None, discord.ActivityType.listening
116         l_act = l_acts[0]
117         if isinstance(l_act, discord.Spotify):
118             act = _("Listening: [{title}{sep}{artist}]({url})").format(
119                 title=discord.utils.escape_markdown(l_act.title),
120                 sep=" | " if l_act.artist else "",
121                 artist=discord.utils.escape_markdown(l_act.artist) if l_act.artist else "",
122                 url=f"https://open.spotify.com/track/{l_act.track_id}",
123             )
124         else:
125             act = _("Listening: {title}").format(title=l_act.name)
126         return act, discord.ActivityType.listening
127 
128     def handle_watching(self, user):
129         w_acts = [c for c in user.activities if c.type == discord.ActivityType.watching]
130         if not w_acts:
131             return None, discord.ActivityType.watching
132         w_act = w_acts[0]
133         act = _("Watching: {name}").format(name=w_act.name)
134         return act, discord.ActivityType.watching
135 
136     def get_status_string(self, user):
137         string = ""
138         for a in [
139             self.handle_custom(user),
140             self.handle_playing(user),
141             self.handle_listening(user),
142             self.handle_streaming(user),
143             self.handle_watching(user),
144         ]:
145             status_string, status_type = a
146             if status_string is None:
147                 continue
148             string += f"{status_string}\n"
149         return string
150 
151     @commands.command()
152     @commands.guild_only()
153     @commands.bot_has_permissions(embed_links=True)
154     async def userinfo(self, ctx, *, user: discord.Member = None):
155         """Show information about a user.
156 
157         This includes fields for status, discord join date, server
158         join date, voice state and previous names/nicknames.
159 
160         If the user has no roles, previous names or previous nicknames,
161         these fields will be omitted.
162         """
163         author = ctx.author
164         guild = ctx.guild
165 
166         if not user:
167             user = author
168 
169         #  A special case for a special someone :^)
170         special_date = datetime(2016, 1, 10, 6, 8, 4, 443000)
171         is_special = user.id == 96130341705637888 and guild.id == 133049272517001216
172 
173         roles = user.roles[-1:0:-1]
174         names, nicks = await self.get_names_and_nicks(user)
175 
176         joined_at = user.joined_at if not is_special else special_date
177         since_created = (ctx.message.created_at - user.created_at).days
178         if joined_at is not None:
179             since_joined = (ctx.message.created_at - joined_at).days
180             user_joined = joined_at.strftime("%d %b %Y %H:%M")
181         else:
182             since_joined = "?"
183             user_joined = _("Unknown")
184         user_created = user.created_at.strftime("%d %b %Y %H:%M")
185         voice_state = user.voice
186         member_number = (
187             sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user)
188             + 1
189         )
190 
191         created_on = _("{}\n({} days ago)").format(user_created, since_created)
192         joined_on = _("{}\n({} days ago)").format(user_joined, since_joined)
193 
194         if any(a.type is discord.ActivityType.streaming for a in user.activities):
195             statusemoji = "\N{LARGE PURPLE CIRCLE}"
196         elif user.status.name == "online":
197             statusemoji = "\N{LARGE GREEN CIRCLE}"
198         elif user.status.name == "offline":
199             statusemoji = "\N{MEDIUM WHITE CIRCLE}\N{VARIATION SELECTOR-16}"
200         elif user.status.name == "dnd":
201             statusemoji = "\N{LARGE RED CIRCLE}"
202         elif user.status.name == "idle":
203             statusemoji = "\N{LARGE ORANGE CIRCLE}"
204         activity = _("Chilling in {} status").format(user.status)
205         status_string = self.get_status_string(user)
206 
207         if roles:
208 
209             role_str = ", ".join([x.mention for x in roles])
210             # 400 BAD REQUEST (error code: 50035): Invalid Form Body
211             # In embed.fields.2.value: Must be 1024 or fewer in length.
212             if len(role_str) > 1024:
213                 # Alternative string building time.
214                 # This is not the most optimal, but if you're hitting this, you are losing more time
215                 # to every single check running on users than the occasional user info invoke
216                 # We don't start by building this way, since the number of times we hit this should be
217                 # infintesimally small compared to when we don't across all uses of Red.
218                 continuation_string = _(
219                     "and {numeric_number} more roles not displayed due to embed limits."
220                 )
221                 available_length = 1024 - len(continuation_string)  # do not attempt to tweak, i18n
222 
223                 role_chunks = []
224                 remaining_roles = 0
225 
226                 for r in roles:
227                     chunk = f"{r.mention}, "
228                     chunk_size = len(chunk)
229 
230                     if chunk_size < available_length:
231                         available_length -= chunk_size
232                         role_chunks.append(chunk)
233                     else:
234                         remaining_roles += 1
235 
236                 role_chunks.append(continuation_string.format(numeric_number=remaining_roles))
237 
238                 role_str = "".join(role_chunks)
239 
240         else:
241             role_str = None
242 
243         data = discord.Embed(description=status_string or activity, colour=user.colour)
244 
245         data.add_field(name=_("Joined Discord on"), value=created_on)
246         data.add_field(name=_("Joined this server on"), value=joined_on)
247         if role_str is not None:
248             data.add_field(name=_("Roles"), value=role_str, inline=False)
249         if names:
250             # May need sanitizing later, but mentions do not ping in embeds currently
251             val = filter_invites(", ".join(names))
252             data.add_field(name=_("Previous Names"), value=val, inline=False)
253         if nicks:
254             # May need sanitizing later, but mentions do not ping in embeds currently
255             val = filter_invites(", ".join(nicks))
256             data.add_field(name=_("Previous Nicknames"), value=val, inline=False)
257         if voice_state and voice_state.channel:
258             data.add_field(
259                 name=_("Current voice channel"),
260                 value="{0.mention} ID: {0.id}".format(voice_state.channel),
261                 inline=False,
262             )
263         data.set_footer(text=_("Member #{} | User ID: {}").format(member_number, user.id))
264 
265         name = str(user)
266         name = " ~ ".join((name, user.nick)) if user.nick else name
267         name = filter_invites(name)
268 
269         avatar = user.avatar_url_as(static_format="png")
270         data.set_author(name=f"{statusemoji} {name}", url=avatar)
271         data.set_thumbnail(url=avatar)
272 
273         await ctx.send(embed=data)
274 
275     @commands.command()
276     async def names(self, ctx: commands.Context, *, user: discord.Member):
277         """Show previous names and nicknames of a user."""
278         names, nicks = await self.get_names_and_nicks(user)
279         msg = ""
280         if names:
281             msg += _("**Past 20 names**:")
282             msg += "\n"
283             msg += ", ".join(names)
284         if nicks:
285             if msg:
286                 msg += "\n\n"
287             msg += _("**Past 20 nicknames**:")
288             msg += "\n"
289             msg += ", ".join(nicks)
290         if msg:
291             msg = filter_various_mentions(msg)
292             await ctx.send(msg)
293         else:
294             await ctx.send(_("That user doesn't have any recorded name or nickname change."))
295
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redbot/cogs/mod/names.py b/redbot/cogs/mod/names.py
--- a/redbot/cogs/mod/names.py
+++ b/redbot/cogs/mod/names.py
@@ -245,15 +245,25 @@
         data.add_field(name=_("Joined Discord on"), value=created_on)
         data.add_field(name=_("Joined this server on"), value=joined_on)
         if role_str is not None:
-            data.add_field(name=_("Roles"), value=role_str, inline=False)
+            data.add_field(
+                name=_("Roles") if len(roles) > 1 else _("Role"), value=role_str, inline=False
+            )
         if names:
             # May need sanitizing later, but mentions do not ping in embeds currently
             val = filter_invites(", ".join(names))
-            data.add_field(name=_("Previous Names"), value=val, inline=False)
+            data.add_field(
+                name=_("Previous Names") if len(names) > 1 else _("Previous Name"),
+                value=val,
+                inline=False,
+            )
         if nicks:
             # May need sanitizing later, but mentions do not ping in embeds currently
             val = filter_invites(", ".join(nicks))
-            data.add_field(name=_("Previous Nicknames"), value=val, inline=False)
+            data.add_field(
+                name=_("Previous Nicknames") if len(nicks) > 1 else _("Previous Nickname"),
+                value=val,
+                inline=False,
+            )
         if voice_state and voice_state.channel:
             data.add_field(
                 name=_("Current voice channel"),

{"golden_diff": "diff --git a/redbot/cogs/mod/names.py b/redbot/cogs/mod/names.py\n--- a/redbot/cogs/mod/names.py\n+++ b/redbot/cogs/mod/names.py\n@@ -245,15 +245,25 @@\n         data.add_field(name=_(\"Joined Discord on\"), value=created_on)\n         data.add_field(name=_(\"Joined this server on\"), value=joined_on)\n         if role_str is not None:\n-            data.add_field(name=_(\"Roles\"), value=role_str, inline=False)\n+            data.add_field(\n+                name=_(\"Roles\") if len(roles) > 1 else _(\"Role\"), value=role_str, inline=False\n+            )\n         if names:\n             # May need sanitizing later, but mentions do not ping in embeds currently\n             val = filter_invites(\", \".join(names))\n-            data.add_field(name=_(\"Previous Names\"), value=val, inline=False)\n+            data.add_field(\n+                name=_(\"Previous Names\") if len(names) > 1 else _(\"Previous Name\"),\n+                value=val,\n+                inline=False,\n+            )\n         if nicks:\n             # May need sanitizing later, but mentions do not ping in embeds currently\n             val = filter_invites(\", \".join(nicks))\n-            data.add_field(name=_(\"Previous Nicknames\"), value=val, inline=False)\n+            data.add_field(\n+                name=_(\"Previous Nicknames\") if len(nicks) > 1 else _(\"Previous Nickname\"),\n+                value=val,\n+                inline=False,\n+            )\n         if voice_state and voice_state.channel:\n             data.add_field(\n                 name=_(\"Current voice channel\"),\n", "issue": "[serverinfo/userinfo] Pluralize strings\n# Commands improvement\r\n\r\n#### Command name\r\n\r\n`[p]userinfo` and `[p]serverinfo`\r\n\r\n#### What cog is this command from?\r\n\r\nFrom `Mod` cog.\r\n\r\n#### What improvement are you expecting to?\r\n\r\nI would like to see all strings pluralized (ie. `Text Channels` being `Text Channel` if there's only one channel and so on).\r\n\r\n#### What actually happened?\r\n\r\nRoles and previous nickname/names aren't pluralized.\n", "before_files": [{"content": "from datetime import datetime\nfrom typing import cast\n\nimport discord\nfrom redbot.core import commands, i18n, checks\nfrom redbot.core.utils.common_filters import (\n    filter_invites,\n    filter_various_mentions,\n    escape_spoilers_and_mass_mentions,\n)\nfrom redbot.core.utils.mod import get_audit_reason\nfrom .abc import MixinMeta\n\n_ = i18n.Translator(\"Mod\", __file__)\n\n\nclass ModInfo(MixinMeta):\n    \"\"\"\n    Commands regarding names, userinfo, etc.\n    \"\"\"\n\n    async def get_names_and_nicks(self, user):\n        names = await self.config.user(user).past_names()\n        nicks = await self.config.member(user).past_nicks()\n        if names:\n            names = [escape_spoilers_and_mass_mentions(name) for name in names if name]\n        if nicks:\n            nicks = [escape_spoilers_and_mass_mentions(nick) for nick in nicks if nick]\n        return names, nicks\n\n    @commands.command()\n    @commands.guild_only()\n    @commands.bot_has_permissions(manage_nicknames=True)\n    @checks.admin_or_permissions(manage_nicknames=True)\n    async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname: str = \"\"):\n        \"\"\"Change a user's nickname.\n\n        Leaving the nickname empty will remove it.\n        \"\"\"\n        nickname = nickname.strip()\n        me = cast(discord.Member, ctx.me)\n        if not nickname:\n            nickname = None\n        elif not 2 <= len(nickname) <= 32:\n            await ctx.send(_(\"Nicknames must be between 2 and 32 characters long.\"))\n            return\n        if not (\n            (me.guild_permissions.manage_nicknames or me.guild_permissions.administrator)\n            and me.top_role > user.top_role\n            and user != ctx.guild.owner\n        ):\n            await ctx.send(\n                _(\n                    \"I do not have permission to rename that member. They may be higher than or \"\n                    \"equal to me in the role hierarchy.\"\n                )\n            )\n        else:\n            try:\n                await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)\n            except discord.Forbidden:\n                # Just in case we missed something in the permissions check above\n                await ctx.send(_(\"I do not have permission to rename that member.\"))\n            except discord.HTTPException as exc:\n                if exc.status == 400:  # BAD REQUEST\n                    await ctx.send(_(\"That nickname is invalid.\"))\n                else:\n                    await ctx.send(_(\"An unexpected error has occured.\"))\n            else:\n                await ctx.send(_(\"Done.\"))\n\n    def handle_custom(self, user):\n        a = [c for c in user.activities if c.type == discord.ActivityType.custom]\n        if not a:\n            return None, discord.ActivityType.custom\n        a = a[0]\n        c_status = None\n        if not a.name and not a.emoji:\n            return None, discord.ActivityType.custom\n        elif a.name and a.emoji:\n            c_status = _(\"Custom: {emoji} {name}\").format(emoji=a.emoji, name=a.name)\n        elif a.emoji:\n            c_status = _(\"Custom: {emoji}\").format(emoji=a.emoji)\n        elif a.name:\n            c_status = _(\"Custom: {name}\").format(name=a.name)\n        return c_status, discord.ActivityType.custom\n\n    def handle_playing(self, user):\n        p_acts = [c for c in user.activities if c.type == discord.ActivityType.playing]\n        if not p_acts:\n            return None, discord.ActivityType.playing\n        p_act = p_acts[0]\n        act = _(\"Playing: {name}\").format(name=p_act.name)\n        return act, discord.ActivityType.playing\n\n    def handle_streaming(self, user):\n        s_acts = [c for c in user.activities if c.type == discord.ActivityType.streaming]\n        if not s_acts:\n            return None, discord.ActivityType.streaming\n        s_act = s_acts[0]\n        if isinstance(s_act, discord.Streaming):\n            act = _(\"Streaming: [{name}{sep}{game}]({url})\").format(\n                name=discord.utils.escape_markdown(s_act.name),\n                sep=\" | \" if s_act.game else \"\",\n                game=discord.utils.escape_markdown(s_act.game) if s_act.game else \"\",\n                url=s_act.url,\n            )\n        else:\n            act = _(\"Streaming: {name}\").format(name=s_act.name)\n        return act, discord.ActivityType.streaming\n\n    def handle_listening(self, user):\n        l_acts = [c for c in user.activities if c.type == discord.ActivityType.listening]\n        if not l_acts:\n            return None, discord.ActivityType.listening\n        l_act = l_acts[0]\n        if isinstance(l_act, discord.Spotify):\n            act = _(\"Listening: [{title}{sep}{artist}]({url})\").format(\n                title=discord.utils.escape_markdown(l_act.title),\n                sep=\" | \" if l_act.artist else \"\",\n                artist=discord.utils.escape_markdown(l_act.artist) if l_act.artist else \"\",\n                url=f\"https://open.spotify.com/track/{l_act.track_id}\",\n            )\n        else:\n            act = _(\"Listening: {title}\").format(title=l_act.name)\n        return act, discord.ActivityType.listening\n\n    def handle_watching(self, user):\n        w_acts = [c for c in user.activities if c.type == discord.ActivityType.watching]\n        if not w_acts:\n            return None, discord.ActivityType.watching\n        w_act = w_acts[0]\n        act = _(\"Watching: {name}\").format(name=w_act.name)\n        return act, discord.ActivityType.watching\n\n    def get_status_string(self, user):\n        string = \"\"\n        for a in [\n            self.handle_custom(user),\n            self.handle_playing(user),\n            self.handle_listening(user),\n            self.handle_streaming(user),\n            self.handle_watching(user),\n        ]:\n            status_string, status_type = a\n            if status_string is None:\n                continue\n            string += f\"{status_string}\\n\"\n        return string\n\n    @commands.command()\n    @commands.guild_only()\n    @commands.bot_has_permissions(embed_links=True)\n    async def userinfo(self, ctx, *, user: discord.Member = None):\n        \"\"\"Show information about a user.\n\n        This includes fields for status, discord join date, server\n        join date, voice state and previous names/nicknames.\n\n        If the user has no roles, previous names or previous nicknames,\n        these fields will be omitted.\n        \"\"\"\n        author = ctx.author\n        guild = ctx.guild\n\n        if not user:\n            user = author\n\n        #  A special case for a special someone :^)\n        special_date = datetime(2016, 1, 10, 6, 8, 4, 443000)\n        is_special = user.id == 96130341705637888 and guild.id == 133049272517001216\n\n        roles = user.roles[-1:0:-1]\n        names, nicks = await self.get_names_and_nicks(user)\n\n        joined_at = user.joined_at if not is_special else special_date\n        since_created = (ctx.message.created_at - user.created_at).days\n        if joined_at is not None:\n            since_joined = (ctx.message.created_at - joined_at).days\n            user_joined = joined_at.strftime(\"%d %b %Y %H:%M\")\n        else:\n            since_joined = \"?\"\n            user_joined = _(\"Unknown\")\n        user_created = user.created_at.strftime(\"%d %b %Y %H:%M\")\n        voice_state = user.voice\n        member_number = (\n            sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user)\n            + 1\n        )\n\n        created_on = _(\"{}\\n({} days ago)\").format(user_created, since_created)\n        joined_on = _(\"{}\\n({} days ago)\").format(user_joined, since_joined)\n\n        if any(a.type is discord.ActivityType.streaming for a in user.activities):\n            statusemoji = \"\\N{LARGE PURPLE CIRCLE}\"\n        elif user.status.name == \"online\":\n            statusemoji = \"\\N{LARGE GREEN CIRCLE}\"\n        elif user.status.name == \"offline\":\n            statusemoji = \"\\N{MEDIUM WHITE CIRCLE}\\N{VARIATION SELECTOR-16}\"\n        elif user.status.name == \"dnd\":\n            statusemoji = \"\\N{LARGE RED CIRCLE}\"\n        elif user.status.name == \"idle\":\n            statusemoji = \"\\N{LARGE ORANGE CIRCLE}\"\n        activity = _(\"Chilling in {} status\").format(user.status)\n        status_string = self.get_status_string(user)\n\n        if roles:\n\n            role_str = \", \".join([x.mention for x in roles])\n            # 400 BAD REQUEST (error code: 50035): Invalid Form Body\n            # In embed.fields.2.value: Must be 1024 or fewer in length.\n            if len(role_str) > 1024:\n                # Alternative string building time.\n                # This is not the most optimal, but if you're hitting this, you are losing more time\n                # to every single check running on users than the occasional user info invoke\n                # We don't start by building this way, since the number of times we hit this should be\n                # infintesimally small compared to when we don't across all uses of Red.\n                continuation_string = _(\n                    \"and {numeric_number} more roles not displayed due to embed limits.\"\n                )\n                available_length = 1024 - len(continuation_string)  # do not attempt to tweak, i18n\n\n                role_chunks = []\n                remaining_roles = 0\n\n                for r in roles:\n                    chunk = f\"{r.mention}, \"\n                    chunk_size = len(chunk)\n\n                    if chunk_size < available_length:\n                        available_length -= chunk_size\n                        role_chunks.append(chunk)\n                    else:\n                        remaining_roles += 1\n\n                role_chunks.append(continuation_string.format(numeric_number=remaining_roles))\n\n                role_str = \"\".join(role_chunks)\n\n        else:\n            role_str = None\n\n        data = discord.Embed(description=status_string or activity, colour=user.colour)\n\n        data.add_field(name=_(\"Joined Discord on\"), value=created_on)\n        data.add_field(name=_(\"Joined this server on\"), value=joined_on)\n        if role_str is not None:\n            data.add_field(name=_(\"Roles\"), value=role_str, inline=False)\n        if names:\n            # May need sanitizing later, but mentions do not ping in embeds currently\n            val = filter_invites(\", \".join(names))\n            data.add_field(name=_(\"Previous Names\"), value=val, inline=False)\n        if nicks:\n            # May need sanitizing later, but mentions do not ping in embeds currently\n            val = filter_invites(\", \".join(nicks))\n            data.add_field(name=_(\"Previous Nicknames\"), value=val, inline=False)\n        if voice_state and voice_state.channel:\n            data.add_field(\n                name=_(\"Current voice channel\"),\n                value=\"{0.mention} ID: {0.id}\".format(voice_state.channel),\n                inline=False,\n            )\n        data.set_footer(text=_(\"Member #{} | User ID: {}\").format(member_number, user.id))\n\n        name = str(user)\n        name = \" ~ \".join((name, user.nick)) if user.nick else name\n        name = filter_invites(name)\n\n        avatar = user.avatar_url_as(static_format=\"png\")\n        data.set_author(name=f\"{statusemoji} {name}\", url=avatar)\n        data.set_thumbnail(url=avatar)\n\n        await ctx.send(embed=data)\n\n    @commands.command()\n    async def names(self, ctx: commands.Context, *, user: discord.Member):\n        \"\"\"Show previous names and nicknames of a user.\"\"\"\n        names, nicks = await self.get_names_and_nicks(user)\n        msg = \"\"\n        if names:\n            msg += _(\"**Past 20 names**:\")\n            msg += \"\\n\"\n            msg += \", \".join(names)\n        if nicks:\n            if msg:\n                msg += \"\\n\\n\"\n            msg += _(\"**Past 20 nicknames**:\")\n            msg += \"\\n\"\n            msg += \", \".join(nicks)\n        if msg:\n            msg = filter_various_mentions(msg)\n            await ctx.send(msg)\n        else:\n            await ctx.send(_(\"That user doesn't have any recorded name or nickname change.\"))\n", "path": "redbot/cogs/mod/names.py"}], "after_files": [{"content": "from datetime import datetime\nfrom typing import cast\n\nimport discord\nfrom redbot.core import commands, i18n, checks\nfrom redbot.core.utils.common_filters import (\n    filter_invites,\n    filter_various_mentions,\n    escape_spoilers_and_mass_mentions,\n)\nfrom redbot.core.utils.mod import get_audit_reason\nfrom .abc import MixinMeta\n\n_ = i18n.Translator(\"Mod\", __file__)\n\n\nclass ModInfo(MixinMeta):\n    \"\"\"\n    Commands regarding names, userinfo, etc.\n    \"\"\"\n\n    async def get_names_and_nicks(self, user):\n        names = await self.config.user(user).past_names()\n        nicks = await self.config.member(user).past_nicks()\n        if names:\n            names = [escape_spoilers_and_mass_mentions(name) for name in names if name]\n        if nicks:\n            nicks = [escape_spoilers_and_mass_mentions(nick) for nick in nicks if nick]\n        return names, nicks\n\n    @commands.command()\n    @commands.guild_only()\n    @commands.bot_has_permissions(manage_nicknames=True)\n    @checks.admin_or_permissions(manage_nicknames=True)\n    async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname: str = \"\"):\n        \"\"\"Change a user's nickname.\n\n        Leaving the nickname empty will remove it.\n        \"\"\"\n        nickname = nickname.strip()\n        me = cast(discord.Member, ctx.me)\n        if not nickname:\n            nickname = None\n        elif not 2 <= len(nickname) <= 32:\n            await ctx.send(_(\"Nicknames must be between 2 and 32 characters long.\"))\n            return\n        if not (\n            (me.guild_permissions.manage_nicknames or me.guild_permissions.administrator)\n            and me.top_role > user.top_role\n            and user != ctx.guild.owner\n        ):\n            await ctx.send(\n                _(\n                    \"I do not have permission to rename that member. They may be higher than or \"\n                    \"equal to me in the role hierarchy.\"\n                )\n            )\n        else:\n            try:\n                await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)\n            except discord.Forbidden:\n                # Just in case we missed something in the permissions check above\n                await ctx.send(_(\"I do not have permission to rename that member.\"))\n            except discord.HTTPException as exc:\n                if exc.status == 400:  # BAD REQUEST\n                    await ctx.send(_(\"That nickname is invalid.\"))\n                else:\n                    await ctx.send(_(\"An unexpected error has occured.\"))\n            else:\n                await ctx.send(_(\"Done.\"))\n\n    def handle_custom(self, user):\n        a = [c for c in user.activities if c.type == discord.ActivityType.custom]\n        if not a:\n            return None, discord.ActivityType.custom\n        a = a[0]\n        c_status = None\n        if not a.name and not a.emoji:\n            return None, discord.ActivityType.custom\n        elif a.name and a.emoji:\n            c_status = _(\"Custom: {emoji} {name}\").format(emoji=a.emoji, name=a.name)\n        elif a.emoji:\n            c_status = _(\"Custom: {emoji}\").format(emoji=a.emoji)\n        elif a.name:\n            c_status = _(\"Custom: {name}\").format(name=a.name)\n        return c_status, discord.ActivityType.custom\n\n    def handle_playing(self, user):\n        p_acts = [c for c in user.activities if c.type == discord.ActivityType.playing]\n        if not p_acts:\n            return None, discord.ActivityType.playing\n        p_act = p_acts[0]\n        act = _(\"Playing: {name}\").format(name=p_act.name)\n        return act, discord.ActivityType.playing\n\n    def handle_streaming(self, user):\n        s_acts = [c for c in user.activities if c.type == discord.ActivityType.streaming]\n        if not s_acts:\n            return None, discord.ActivityType.streaming\n        s_act = s_acts[0]\n        if isinstance(s_act, discord.Streaming):\n            act = _(\"Streaming: [{name}{sep}{game}]({url})\").format(\n                name=discord.utils.escape_markdown(s_act.name),\n                sep=\" | \" if s_act.game else \"\",\n                game=discord.utils.escape_markdown(s_act.game) if s_act.game else \"\",\n                url=s_act.url,\n            )\n        else:\n            act = _(\"Streaming: {name}\").format(name=s_act.name)\n        return act, discord.ActivityType.streaming\n\n    def handle_listening(self, user):\n        l_acts = [c for c in user.activities if c.type == discord.ActivityType.listening]\n        if not l_acts:\n            return None, discord.ActivityType.listening\n        l_act = l_acts[0]\n        if isinstance(l_act, discord.Spotify):\n            act = _(\"Listening: [{title}{sep}{artist}]({url})\").format(\n                title=discord.utils.escape_markdown(l_act.title),\n                sep=\" | \" if l_act.artist else \"\",\n                artist=discord.utils.escape_markdown(l_act.artist) if l_act.artist else \"\",\n                url=f\"https://open.spotify.com/track/{l_act.track_id}\",\n            )\n        else:\n            act = _(\"Listening: {title}\").format(title=l_act.name)\n        return act, discord.ActivityType.listening\n\n    def handle_watching(self, user):\n        w_acts = [c for c in user.activities if c.type == discord.ActivityType.watching]\n        if not w_acts:\n            return None, discord.ActivityType.watching\n        w_act = w_acts[0]\n        act = _(\"Watching: {name}\").format(name=w_act.name)\n        return act, discord.ActivityType.watching\n\n    def get_status_string(self, user):\n        string = \"\"\n        for a in [\n            self.handle_custom(user),\n            self.handle_playing(user),\n            self.handle_listening(user),\n            self.handle_streaming(user),\n            self.handle_watching(user),\n        ]:\n            status_string, status_type = a\n            if status_string is None:\n                continue\n            string += f\"{status_string}\\n\"\n        return string\n\n    @commands.command()\n    @commands.guild_only()\n    @commands.bot_has_permissions(embed_links=True)\n    async def userinfo(self, ctx, *, user: discord.Member = None):\n        \"\"\"Show information about a user.\n\n        This includes fields for status, discord join date, server\n        join date, voice state and previous names/nicknames.\n\n        If the user has no roles, previous names or previous nicknames,\n        these fields will be omitted.\n        \"\"\"\n        author = ctx.author\n        guild = ctx.guild\n\n        if not user:\n            user = author\n\n        #  A special case for a special someone :^)\n        special_date = datetime(2016, 1, 10, 6, 8, 4, 443000)\n        is_special = user.id == 96130341705637888 and guild.id == 133049272517001216\n\n        roles = user.roles[-1:0:-1]\n        names, nicks = await self.get_names_and_nicks(user)\n\n        joined_at = user.joined_at if not is_special else special_date\n        since_created = (ctx.message.created_at - user.created_at).days\n        if joined_at is not None:\n            since_joined = (ctx.message.created_at - joined_at).days\n            user_joined = joined_at.strftime(\"%d %b %Y %H:%M\")\n        else:\n            since_joined = \"?\"\n            user_joined = _(\"Unknown\")\n        user_created = user.created_at.strftime(\"%d %b %Y %H:%M\")\n        voice_state = user.voice\n        member_number = (\n            sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user)\n            + 1\n        )\n\n        created_on = _(\"{}\\n({} days ago)\").format(user_created, since_created)\n        joined_on = _(\"{}\\n({} days ago)\").format(user_joined, since_joined)\n\n        if any(a.type is discord.ActivityType.streaming for a in user.activities):\n            statusemoji = \"\\N{LARGE PURPLE CIRCLE}\"\n        elif user.status.name == \"online\":\n            statusemoji = \"\\N{LARGE GREEN CIRCLE}\"\n        elif user.status.name == \"offline\":\n            statusemoji = \"\\N{MEDIUM WHITE CIRCLE}\\N{VARIATION SELECTOR-16}\"\n        elif user.status.name == \"dnd\":\n            statusemoji = \"\\N{LARGE RED CIRCLE}\"\n        elif user.status.name == \"idle\":\n            statusemoji = \"\\N{LARGE ORANGE CIRCLE}\"\n        activity = _(\"Chilling in {} status\").format(user.status)\n        status_string = self.get_status_string(user)\n\n        if roles:\n\n            role_str = \", \".join([x.mention for x in roles])\n            # 400 BAD REQUEST (error code: 50035): Invalid Form Body\n            # In embed.fields.2.value: Must be 1024 or fewer in length.\n            if len(role_str) > 1024:\n                # Alternative string building time.\n                # This is not the most optimal, but if you're hitting this, you are losing more time\n                # to every single check running on users than the occasional user info invoke\n                # We don't start by building this way, since the number of times we hit this should be\n                # infintesimally small compared to when we don't across all uses of Red.\n                continuation_string = _(\n                    \"and {numeric_number} more roles not displayed due to embed limits.\"\n                )\n                available_length = 1024 - len(continuation_string)  # do not attempt to tweak, i18n\n\n                role_chunks = []\n                remaining_roles = 0\n\n                for r in roles:\n                    chunk = f\"{r.mention}, \"\n                    chunk_size = len(chunk)\n\n                    if chunk_size < available_length:\n                        available_length -= chunk_size\n                        role_chunks.append(chunk)\n                    else:\n                        remaining_roles += 1\n\n                role_chunks.append(continuation_string.format(numeric_number=remaining_roles))\n\n                role_str = \"\".join(role_chunks)\n\n        else:\n            role_str = None\n\n        data = discord.Embed(description=status_string or activity, colour=user.colour)\n\n        data.add_field(name=_(\"Joined Discord on\"), value=created_on)\n        data.add_field(name=_(\"Joined this server on\"), value=joined_on)\n        if role_str is not None:\n            data.add_field(\n                name=_(\"Roles\") if len(roles) > 1 else _(\"Role\"), value=role_str, inline=False\n            )\n        if names:\n            # May need sanitizing later, but mentions do not ping in embeds currently\n            val = filter_invites(\", \".join(names))\n            data.add_field(\n                name=_(\"Previous Names\") if len(names) > 1 else _(\"Previous Name\"),\n                value=val,\n                inline=False,\n            )\n        if nicks:\n            # May need sanitizing later, but mentions do not ping in embeds currently\n            val = filter_invites(\", \".join(nicks))\n            data.add_field(\n                name=_(\"Previous Nicknames\") if len(nicks) > 1 else _(\"Previous Nickname\"),\n                value=val,\n                inline=False,\n            )\n        if voice_state and voice_state.channel:\n            data.add_field(\n                name=_(\"Current voice channel\"),\n                value=\"{0.mention} ID: {0.id}\".format(voice_state.channel),\n                inline=False,\n            )\n        data.set_footer(text=_(\"Member #{} | User ID: {}\").format(member_number, user.id))\n\n        name = str(user)\n        name = \" ~ \".join((name, user.nick)) if user.nick else name\n        name = filter_invites(name)\n\n        avatar = user.avatar_url_as(static_format=\"png\")\n        data.set_author(name=f\"{statusemoji} {name}\", url=avatar)\n        data.set_thumbnail(url=avatar)\n\n        await ctx.send(embed=data)\n\n    @commands.command()\n    async def names(self, ctx: commands.Context, *, user: discord.Member):\n        \"\"\"Show previous names and nicknames of a user.\"\"\"\n        names, nicks = await self.get_names_and_nicks(user)\n        msg = \"\"\n        if names:\n            msg += _(\"**Past 20 names**:\")\n            msg += \"\\n\"\n            msg += \", \".join(names)\n        if nicks:\n            if msg:\n                msg += \"\\n\\n\"\n            msg += _(\"**Past 20 nicknames**:\")\n            msg += \"\\n\"\n            msg += \", \".join(nicks)\n        if msg:\n            msg = filter_various_mentions(msg)\n            await ctx.send(msg)\n        else:\n            await ctx.send(_(\"That user doesn't have any recorded name or nickname change.\"))\n", "path": "redbot/cogs/mod/names.py"}]}