We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
New custom user statuses should appear in !user
Related to #620, we must moderate the new custom statuses, but they don't appear when we issue a !user command. These should be included to form part of the channel record for moderation purposes.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bot/cogs/information.py`
Content:
```
1 import colorsys
2 import logging
3 import pprint
4 import textwrap
5 import typing
6 from typing import Any, Mapping, Optional
7 
8 import discord
9 from discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils
10 from discord.ext import commands
11 from discord.ext.commands import Bot, BucketType, Cog, Context, command, group
12 
13 from bot.constants import Channels, Emojis, MODERATION_ROLES, STAFF_ROLES
14 from bot.decorators import InChannelCheckFailure, in_channel, with_role
15 from bot.utils.checks import cooldown_with_role_bypass, with_role_check
16 from bot.utils.time import time_since
17 
18 log = logging.getLogger(__name__)
19 
20 
21 class Information(Cog):
22     """A cog with commands for generating embeds with server info, such as server stats and user info."""
23 
24     def __init__(self, bot: Bot):
25         self.bot = bot
26 
27     @with_role(*MODERATION_ROLES)
28     @command(name="roles")
29     async def roles_info(self, ctx: Context) -> None:
30         """Returns a list of all roles and their corresponding IDs."""
31         # Sort the roles alphabetically and remove the @everyone role
32         roles = sorted(ctx.guild.roles, key=lambda role: role.name)
33         roles = [role for role in roles if role.name != "@everyone"]
34 
35         # Build a string
36         role_string = ""
37         for role in roles:
38             role_string += f"`{role.id}` - {role.mention}\n"
39 
40         # Build an embed
41         embed = Embed(
42             title="Role information",
43             colour=Colour.blurple(),
44             description=role_string
45         )
46 
47         embed.set_footer(text=f"Total roles: {len(roles)}")
48 
49         await ctx.send(embed=embed)
50 
51     @with_role(*MODERATION_ROLES)
52     @command(name="role")
53     async def role_info(self, ctx: Context, *roles: typing.Union[Role, str]) -> None:
54         """
55         Return information on a role or list of roles.
56 
57         To specify multiple roles just add to the arguments, delimit roles with spaces in them using quotation marks.
58         """
59         parsed_roles = []
60 
61         for role_name in roles:
62             if isinstance(role_name, Role):
63                 # Role conversion has already succeeded
64                 parsed_roles.append(role_name)
65                 continue
66 
67             role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)
68 
69             if not role:
70                 await ctx.send(f":x: Could not convert `{role_name}` to a role")
71                 continue
72 
73             parsed_roles.append(role)
74 
75         for role in parsed_roles:
76             embed = Embed(
77                 title=f"{role.name} info",
78                 colour=role.colour,
79             )
80 
81             embed.add_field(name="ID", value=role.id, inline=True)
82 
83             embed.add_field(name="Colour (RGB)", value=f"#{role.colour.value:0>6x}", inline=True)
84 
85             h, s, v = colorsys.rgb_to_hsv(*role.colour.to_rgb())
86 
87             embed.add_field(name="Colour (HSV)", value=f"{h:.2f} {s:.2f} {v}", inline=True)
88 
89             embed.add_field(name="Member count", value=len(role.members), inline=True)
90 
91             embed.add_field(name="Position", value=role.position)
92 
93             embed.add_field(name="Permission code", value=role.permissions.value, inline=True)
94 
95             await ctx.send(embed=embed)
96 
97     @command(name="server", aliases=["server_info", "guild", "guild_info"])
98     async def server_info(self, ctx: Context) -> None:
99         """Returns an embed full of server information."""
100         created = time_since(ctx.guild.created_at, precision="days")
101         features = ", ".join(ctx.guild.features)
102         region = ctx.guild.region
103 
104         # How many of each type of channel?
105         roles = len(ctx.guild.roles)
106         channels = ctx.guild.channels
107         text_channels = 0
108         category_channels = 0
109         voice_channels = 0
110         for channel in channels:
111             if type(channel) == TextChannel:
112                 text_channels += 1
113             elif type(channel) == CategoryChannel:
114                 category_channels += 1
115             elif type(channel) == VoiceChannel:
116                 voice_channels += 1
117 
118         # How many of each user status?
119         member_count = ctx.guild.member_count
120         members = ctx.guild.members
121         online = 0
122         dnd = 0
123         idle = 0
124         offline = 0
125         for member in members:
126             if str(member.status) == "online":
127                 online += 1
128             elif str(member.status) == "offline":
129                 offline += 1
130             elif str(member.status) == "idle":
131                 idle += 1
132             elif str(member.status) == "dnd":
133                 dnd += 1
134 
135         embed = Embed(
136             colour=Colour.blurple(),
137             description=textwrap.dedent(f"""
138                 **Server information**
139                 Created: {created}
140                 Voice region: {region}
141                 Features: {features}
142 
143                 **Counts**
144                 Members: {member_count:,}
145                 Roles: {roles}
146                 Text: {text_channels}
147                 Voice: {voice_channels}
148                 Channel categories: {category_channels}
149 
150                 **Members**
151                 {Emojis.status_online} {online}
152                 {Emojis.status_idle} {idle}
153                 {Emojis.status_dnd} {dnd}
154                 {Emojis.status_offline} {offline}
155             """)
156         )
157 
158         embed.set_thumbnail(url=ctx.guild.icon_url)
159 
160         await ctx.send(embed=embed)
161 
162     @command(name="user", aliases=["user_info", "member", "member_info"])
163     async def user_info(self, ctx: Context, user: Member = None, hidden: bool = False) -> None:
164         """Returns info about a user."""
165         if user is None:
166             user = ctx.author
167 
168         # Do a role check if this is being executed on someone other than the caller
169         if user != ctx.author and not with_role_check(ctx, *MODERATION_ROLES):
170             await ctx.send("You may not use this command on users other than yourself.")
171             return
172 
173         # Non-moderators may only do this in #bot-commands and can't see hidden infractions.
174         if not with_role_check(ctx, *STAFF_ROLES):
175             if not ctx.channel.id == Channels.bot:
176                 raise InChannelCheckFailure(Channels.bot)
177             # Hide hidden infractions for users without a moderation role
178             hidden = False
179 
180         # User information
181         created = time_since(user.created_at, max_units=3)
182 
183         name = str(user)
184         if user.nick:
185             name = f"{user.nick} ({name})"
186 
187         # Member information
188         joined = time_since(user.joined_at, precision="days")
189 
190         # You're welcome, Volcyyyyyyyyyyyyyyyy
191         roles = ", ".join(role.mention for role in user.roles if role.name != "@everyone")
192 
193         # Infractions
194         infractions = await self.bot.api_client.get(
195             'bot/infractions',
196             params={
197                 'hidden': str(hidden),
198                 'user__id': str(user.id)
199             }
200         )
201 
202         infr_total = 0
203         infr_active = 0
204 
205         # At least it's readable.
206         for infr in infractions:
207             if infr["active"]:
208                 infr_active += 1
209 
210             infr_total += 1
211 
212         # Let's build the embed now
213         embed = Embed(
214             title=name,
215             description=textwrap.dedent(f"""
216                 **User Information**
217                 Created: {created}
218                 Profile: {user.mention}
219                 ID: {user.id}
220 
221                 **Member Information**
222                 Joined: {joined}
223                 Roles: {roles or None}
224 
225                 **Infractions**
226                 Total: {infr_total}
227                 Active: {infr_active}
228             """)
229         )
230 
231         embed.set_thumbnail(url=user.avatar_url_as(format="png"))
232         embed.colour = user.top_role.colour if roles else Colour.blurple()
233 
234         await ctx.send(embed=embed)
235 
236     def format_fields(self, mapping: Mapping[str, Any], field_width: Optional[int] = None) -> str:
237         """Format a mapping to be readable to a human."""
238         # sorting is technically superfluous but nice if you want to look for a specific field
239         fields = sorted(mapping.items(), key=lambda item: item[0])
240 
241         if field_width is None:
242             field_width = len(max(mapping.keys(), key=len))
243 
244         out = ''
245 
246         for key, val in fields:
247             if isinstance(val, dict):
248                 # if we have dicts inside dicts we want to apply the same treatment to the inner dictionaries
249                 inner_width = int(field_width * 1.6)
250                 val = '\n' + self.format_fields(val, field_width=inner_width)
251 
252             elif isinstance(val, str):
253                 # split up text since it might be long
254                 text = textwrap.fill(val, width=100, replace_whitespace=False)
255 
256                 # indent it, I guess you could do this with `wrap` and `join` but this is nicer
257                 val = textwrap.indent(text, ' ' * (field_width + len(': ')))
258 
259                 # the first line is already indented so we `str.lstrip` it
260                 val = val.lstrip()
261 
262             if key == 'color':
263                 # makes the base 10 representation of a hex number readable to humans
264                 val = hex(val)
265 
266             out += '{0:>{width}}: {1}\n'.format(key, val, width=field_width)
267 
268         # remove trailing whitespace
269         return out.rstrip()
270 
271     @cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=STAFF_ROLES)
272     @group(invoke_without_command=True)
273     @in_channel(Channels.bot, bypass_roles=STAFF_ROLES)
274     async def raw(self, ctx: Context, *, message: discord.Message, json: bool = False) -> None:
275         """Shows information about the raw API response."""
276         # I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling
277         # doing this extra request is also much easier than trying to convert everything back into a dictionary again
278         raw_data = await ctx.bot.http.get_message(message.channel.id, message.id)
279 
280         paginator = commands.Paginator()
281 
282         def add_content(title: str, content: str) -> None:
283             paginator.add_line(f'== {title} ==\n')
284             # replace backticks as it breaks out of code blocks. Spaces seemed to be the most reasonable solution.
285             # we hope it's not close to 2000
286             paginator.add_line(content.replace('```', '`` `'))
287             paginator.close_page()
288 
289         if message.content:
290             add_content('Raw message', message.content)
291 
292         transformer = pprint.pformat if json else self.format_fields
293         for field_name in ('embeds', 'attachments'):
294             data = raw_data[field_name]
295 
296             if not data:
297                 continue
298 
299             total = len(data)
300             for current, item in enumerate(data, start=1):
301                 title = f'Raw {field_name} ({current}/{total})'
302                 add_content(title, transformer(item))
303 
304         for page in paginator.pages:
305             await ctx.send(page)
306 
307     @raw.command()
308     async def json(self, ctx: Context, message: discord.Message) -> None:
309         """Shows information about the raw API response in a copy-pasteable Python format."""
310         await ctx.invoke(self.raw, message=message, json=True)
311 
312 
313 def setup(bot: Bot) -> None:
314     """Information cog load."""
315     bot.add_cog(Information(bot))
316     log.info("Cog loaded: Information")
317
```
--- 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/bot/cogs/information.py b/bot/cogs/information.py
--- a/bot/cogs/information.py
+++ b/bot/cogs/information.py
@@ -9,6 +9,7 @@
 from discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils
 from discord.ext import commands
 from discord.ext.commands import Bot, BucketType, Cog, Context, command, group
+from discord.utils import escape_markdown
 
 from bot.constants import Channels, Emojis, MODERATION_ROLES, STAFF_ROLES
 from bot.decorators import InChannelCheckFailure, in_channel, with_role
@@ -180,6 +181,13 @@
         # User information
         created = time_since(user.created_at, max_units=3)
 
+        # Custom status
+        custom_status = ''
+        for activity in user.activities:
+            if activity.name == 'Custom Status':
+                state = escape_markdown(activity.state)
+                custom_status = f'Status: {state}\n'
+
         name = str(user)
         if user.nick:
             name = f"{user.nick} ({name})"
@@ -217,7 +225,7 @@
                 Created: {created}
                 Profile: {user.mention}
                 ID: {user.id}
-
+                {custom_status}
                 **Member Information**
                 Joined: {joined}
                 Roles: {roles or None}

{"golden_diff": "diff --git a/bot/cogs/information.py b/bot/cogs/information.py\n--- a/bot/cogs/information.py\n+++ b/bot/cogs/information.py\n@@ -9,6 +9,7 @@\n from discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils\n from discord.ext import commands\n from discord.ext.commands import Bot, BucketType, Cog, Context, command, group\n+from discord.utils import escape_markdown\n \n from bot.constants import Channels, Emojis, MODERATION_ROLES, STAFF_ROLES\n from bot.decorators import InChannelCheckFailure, in_channel, with_role\n@@ -180,6 +181,13 @@\n         # User information\n         created = time_since(user.created_at, max_units=3)\n \n+        # Custom status\n+        custom_status = ''\n+        for activity in user.activities:\n+            if activity.name == 'Custom Status':\n+                state = escape_markdown(activity.state)\n+                custom_status = f'Status: {state}\\n'\n+\n         name = str(user)\n         if user.nick:\n             name = f\"{user.nick} ({name})\"\n@@ -217,7 +225,7 @@\n                 Created: {created}\n                 Profile: {user.mention}\n                 ID: {user.id}\n-\n+                {custom_status}\n                 **Member Information**\n                 Joined: {joined}\n                 Roles: {roles or None}\n", "issue": "New custom user statuses should appear in !user\nRelated to #620, we must moderate the new custom statuses, but they don't appear when we issue a !user command. These should be included to form part of the channel record for moderation purposes.\n", "before_files": [{"content": "import colorsys\nimport logging\nimport pprint\nimport textwrap\nimport typing\nfrom typing import Any, Mapping, Optional\n\nimport discord\nfrom discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils\nfrom discord.ext import commands\nfrom discord.ext.commands import Bot, BucketType, Cog, Context, command, group\n\nfrom bot.constants import Channels, Emojis, MODERATION_ROLES, STAFF_ROLES\nfrom bot.decorators import InChannelCheckFailure, in_channel, with_role\nfrom bot.utils.checks import cooldown_with_role_bypass, with_role_check\nfrom bot.utils.time import time_since\n\nlog = logging.getLogger(__name__)\n\n\nclass Information(Cog):\n    \"\"\"A cog with commands for generating embeds with server info, such as server stats and user info.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n    @with_role(*MODERATION_ROLES)\n    @command(name=\"roles\")\n    async def roles_info(self, ctx: Context) -> None:\n        \"\"\"Returns a list of all roles and their corresponding IDs.\"\"\"\n        # Sort the roles alphabetically and remove the @everyone role\n        roles = sorted(ctx.guild.roles, key=lambda role: role.name)\n        roles = [role for role in roles if role.name != \"@everyone\"]\n\n        # Build a string\n        role_string = \"\"\n        for role in roles:\n            role_string += f\"`{role.id}` - {role.mention}\\n\"\n\n        # Build an embed\n        embed = Embed(\n            title=\"Role information\",\n            colour=Colour.blurple(),\n            description=role_string\n        )\n\n        embed.set_footer(text=f\"Total roles: {len(roles)}\")\n\n        await ctx.send(embed=embed)\n\n    @with_role(*MODERATION_ROLES)\n    @command(name=\"role\")\n    async def role_info(self, ctx: Context, *roles: typing.Union[Role, str]) -> None:\n        \"\"\"\n        Return information on a role or list of roles.\n\n        To specify multiple roles just add to the arguments, delimit roles with spaces in them using quotation marks.\n        \"\"\"\n        parsed_roles = []\n\n        for role_name in roles:\n            if isinstance(role_name, Role):\n                # Role conversion has already succeeded\n                parsed_roles.append(role_name)\n                continue\n\n            role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)\n\n            if not role:\n                await ctx.send(f\":x: Could not convert `{role_name}` to a role\")\n                continue\n\n            parsed_roles.append(role)\n\n        for role in parsed_roles:\n            embed = Embed(\n                title=f\"{role.name} info\",\n                colour=role.colour,\n            )\n\n            embed.add_field(name=\"ID\", value=role.id, inline=True)\n\n            embed.add_field(name=\"Colour (RGB)\", value=f\"#{role.colour.value:0>6x}\", inline=True)\n\n            h, s, v = colorsys.rgb_to_hsv(*role.colour.to_rgb())\n\n            embed.add_field(name=\"Colour (HSV)\", value=f\"{h:.2f} {s:.2f} {v}\", inline=True)\n\n            embed.add_field(name=\"Member count\", value=len(role.members), inline=True)\n\n            embed.add_field(name=\"Position\", value=role.position)\n\n            embed.add_field(name=\"Permission code\", value=role.permissions.value, inline=True)\n\n            await ctx.send(embed=embed)\n\n    @command(name=\"server\", aliases=[\"server_info\", \"guild\", \"guild_info\"])\n    async def server_info(self, ctx: Context) -> None:\n        \"\"\"Returns an embed full of server information.\"\"\"\n        created = time_since(ctx.guild.created_at, precision=\"days\")\n        features = \", \".join(ctx.guild.features)\n        region = ctx.guild.region\n\n        # How many of each type of channel?\n        roles = len(ctx.guild.roles)\n        channels = ctx.guild.channels\n        text_channels = 0\n        category_channels = 0\n        voice_channels = 0\n        for channel in channels:\n            if type(channel) == TextChannel:\n                text_channels += 1\n            elif type(channel) == CategoryChannel:\n                category_channels += 1\n            elif type(channel) == VoiceChannel:\n                voice_channels += 1\n\n        # How many of each user status?\n        member_count = ctx.guild.member_count\n        members = ctx.guild.members\n        online = 0\n        dnd = 0\n        idle = 0\n        offline = 0\n        for member in members:\n            if str(member.status) == \"online\":\n                online += 1\n            elif str(member.status) == \"offline\":\n                offline += 1\n            elif str(member.status) == \"idle\":\n                idle += 1\n            elif str(member.status) == \"dnd\":\n                dnd += 1\n\n        embed = Embed(\n            colour=Colour.blurple(),\n            description=textwrap.dedent(f\"\"\"\n                **Server information**\n                Created: {created}\n                Voice region: {region}\n                Features: {features}\n\n                **Counts**\n                Members: {member_count:,}\n                Roles: {roles}\n                Text: {text_channels}\n                Voice: {voice_channels}\n                Channel categories: {category_channels}\n\n                **Members**\n                {Emojis.status_online} {online}\n                {Emojis.status_idle} {idle}\n                {Emojis.status_dnd} {dnd}\n                {Emojis.status_offline} {offline}\n            \"\"\")\n        )\n\n        embed.set_thumbnail(url=ctx.guild.icon_url)\n\n        await ctx.send(embed=embed)\n\n    @command(name=\"user\", aliases=[\"user_info\", \"member\", \"member_info\"])\n    async def user_info(self, ctx: Context, user: Member = None, hidden: bool = False) -> None:\n        \"\"\"Returns info about a user.\"\"\"\n        if user is None:\n            user = ctx.author\n\n        # Do a role check if this is being executed on someone other than the caller\n        if user != ctx.author and not with_role_check(ctx, *MODERATION_ROLES):\n            await ctx.send(\"You may not use this command on users other than yourself.\")\n            return\n\n        # Non-moderators may only do this in #bot-commands and can't see hidden infractions.\n        if not with_role_check(ctx, *STAFF_ROLES):\n            if not ctx.channel.id == Channels.bot:\n                raise InChannelCheckFailure(Channels.bot)\n            # Hide hidden infractions for users without a moderation role\n            hidden = False\n\n        # User information\n        created = time_since(user.created_at, max_units=3)\n\n        name = str(user)\n        if user.nick:\n            name = f\"{user.nick} ({name})\"\n\n        # Member information\n        joined = time_since(user.joined_at, precision=\"days\")\n\n        # You're welcome, Volcyyyyyyyyyyyyyyyy\n        roles = \", \".join(role.mention for role in user.roles if role.name != \"@everyone\")\n\n        # Infractions\n        infractions = await self.bot.api_client.get(\n            'bot/infractions',\n            params={\n                'hidden': str(hidden),\n                'user__id': str(user.id)\n            }\n        )\n\n        infr_total = 0\n        infr_active = 0\n\n        # At least it's readable.\n        for infr in infractions:\n            if infr[\"active\"]:\n                infr_active += 1\n\n            infr_total += 1\n\n        # Let's build the embed now\n        embed = Embed(\n            title=name,\n            description=textwrap.dedent(f\"\"\"\n                **User Information**\n                Created: {created}\n                Profile: {user.mention}\n                ID: {user.id}\n\n                **Member Information**\n                Joined: {joined}\n                Roles: {roles or None}\n\n                **Infractions**\n                Total: {infr_total}\n                Active: {infr_active}\n            \"\"\")\n        )\n\n        embed.set_thumbnail(url=user.avatar_url_as(format=\"png\"))\n        embed.colour = user.top_role.colour if roles else Colour.blurple()\n\n        await ctx.send(embed=embed)\n\n    def format_fields(self, mapping: Mapping[str, Any], field_width: Optional[int] = None) -> str:\n        \"\"\"Format a mapping to be readable to a human.\"\"\"\n        # sorting is technically superfluous but nice if you want to look for a specific field\n        fields = sorted(mapping.items(), key=lambda item: item[0])\n\n        if field_width is None:\n            field_width = len(max(mapping.keys(), key=len))\n\n        out = ''\n\n        for key, val in fields:\n            if isinstance(val, dict):\n                # if we have dicts inside dicts we want to apply the same treatment to the inner dictionaries\n                inner_width = int(field_width * 1.6)\n                val = '\\n' + self.format_fields(val, field_width=inner_width)\n\n            elif isinstance(val, str):\n                # split up text since it might be long\n                text = textwrap.fill(val, width=100, replace_whitespace=False)\n\n                # indent it, I guess you could do this with `wrap` and `join` but this is nicer\n                val = textwrap.indent(text, ' ' * (field_width + len(': ')))\n\n                # the first line is already indented so we `str.lstrip` it\n                val = val.lstrip()\n\n            if key == 'color':\n                # makes the base 10 representation of a hex number readable to humans\n                val = hex(val)\n\n            out += '{0:>{width}}: {1}\\n'.format(key, val, width=field_width)\n\n        # remove trailing whitespace\n        return out.rstrip()\n\n    @cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=STAFF_ROLES)\n    @group(invoke_without_command=True)\n    @in_channel(Channels.bot, bypass_roles=STAFF_ROLES)\n    async def raw(self, ctx: Context, *, message: discord.Message, json: bool = False) -> None:\n        \"\"\"Shows information about the raw API response.\"\"\"\n        # I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling\n        # doing this extra request is also much easier than trying to convert everything back into a dictionary again\n        raw_data = await ctx.bot.http.get_message(message.channel.id, message.id)\n\n        paginator = commands.Paginator()\n\n        def add_content(title: str, content: str) -> None:\n            paginator.add_line(f'== {title} ==\\n')\n            # replace backticks as it breaks out of code blocks. Spaces seemed to be the most reasonable solution.\n            # we hope it's not close to 2000\n            paginator.add_line(content.replace('```', '`` `'))\n            paginator.close_page()\n\n        if message.content:\n            add_content('Raw message', message.content)\n\n        transformer = pprint.pformat if json else self.format_fields\n        for field_name in ('embeds', 'attachments'):\n            data = raw_data[field_name]\n\n            if not data:\n                continue\n\n            total = len(data)\n            for current, item in enumerate(data, start=1):\n                title = f'Raw {field_name} ({current}/{total})'\n                add_content(title, transformer(item))\n\n        for page in paginator.pages:\n            await ctx.send(page)\n\n    @raw.command()\n    async def json(self, ctx: Context, message: discord.Message) -> None:\n        \"\"\"Shows information about the raw API response in a copy-pasteable Python format.\"\"\"\n        await ctx.invoke(self.raw, message=message, json=True)\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Information cog load.\"\"\"\n    bot.add_cog(Information(bot))\n    log.info(\"Cog loaded: Information\")\n", "path": "bot/cogs/information.py"}], "after_files": [{"content": "import colorsys\nimport logging\nimport pprint\nimport textwrap\nimport typing\nfrom typing import Any, Mapping, Optional\n\nimport discord\nfrom discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils\nfrom discord.ext import commands\nfrom discord.ext.commands import Bot, BucketType, Cog, Context, command, group\nfrom discord.utils import escape_markdown\n\nfrom bot.constants import Channels, Emojis, MODERATION_ROLES, STAFF_ROLES\nfrom bot.decorators import InChannelCheckFailure, in_channel, with_role\nfrom bot.utils.checks import cooldown_with_role_bypass, with_role_check\nfrom bot.utils.time import time_since\n\nlog = logging.getLogger(__name__)\n\n\nclass Information(Cog):\n    \"\"\"A cog with commands for generating embeds with server info, such as server stats and user info.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n    @with_role(*MODERATION_ROLES)\n    @command(name=\"roles\")\n    async def roles_info(self, ctx: Context) -> None:\n        \"\"\"Returns a list of all roles and their corresponding IDs.\"\"\"\n        # Sort the roles alphabetically and remove the @everyone role\n        roles = sorted(ctx.guild.roles, key=lambda role: role.name)\n        roles = [role for role in roles if role.name != \"@everyone\"]\n\n        # Build a string\n        role_string = \"\"\n        for role in roles:\n            role_string += f\"`{role.id}` - {role.mention}\\n\"\n\n        # Build an embed\n        embed = Embed(\n            title=\"Role information\",\n            colour=Colour.blurple(),\n            description=role_string\n        )\n\n        embed.set_footer(text=f\"Total roles: {len(roles)}\")\n\n        await ctx.send(embed=embed)\n\n    @with_role(*MODERATION_ROLES)\n    @command(name=\"role\")\n    async def role_info(self, ctx: Context, *roles: typing.Union[Role, str]) -> None:\n        \"\"\"\n        Return information on a role or list of roles.\n\n        To specify multiple roles just add to the arguments, delimit roles with spaces in them using quotation marks.\n        \"\"\"\n        parsed_roles = []\n\n        for role_name in roles:\n            if isinstance(role_name, Role):\n                # Role conversion has already succeeded\n                parsed_roles.append(role_name)\n                continue\n\n            role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)\n\n            if not role:\n                await ctx.send(f\":x: Could not convert `{role_name}` to a role\")\n                continue\n\n            parsed_roles.append(role)\n\n        for role in parsed_roles:\n            embed = Embed(\n                title=f\"{role.name} info\",\n                colour=role.colour,\n            )\n\n            embed.add_field(name=\"ID\", value=role.id, inline=True)\n\n            embed.add_field(name=\"Colour (RGB)\", value=f\"#{role.colour.value:0>6x}\", inline=True)\n\n            h, s, v = colorsys.rgb_to_hsv(*role.colour.to_rgb())\n\n            embed.add_field(name=\"Colour (HSV)\", value=f\"{h:.2f} {s:.2f} {v}\", inline=True)\n\n            embed.add_field(name=\"Member count\", value=len(role.members), inline=True)\n\n            embed.add_field(name=\"Position\", value=role.position)\n\n            embed.add_field(name=\"Permission code\", value=role.permissions.value, inline=True)\n\n            await ctx.send(embed=embed)\n\n    @command(name=\"server\", aliases=[\"server_info\", \"guild\", \"guild_info\"])\n    async def server_info(self, ctx: Context) -> None:\n        \"\"\"Returns an embed full of server information.\"\"\"\n        created = time_since(ctx.guild.created_at, precision=\"days\")\n        features = \", \".join(ctx.guild.features)\n        region = ctx.guild.region\n\n        # How many of each type of channel?\n        roles = len(ctx.guild.roles)\n        channels = ctx.guild.channels\n        text_channels = 0\n        category_channels = 0\n        voice_channels = 0\n        for channel in channels:\n            if type(channel) == TextChannel:\n                text_channels += 1\n            elif type(channel) == CategoryChannel:\n                category_channels += 1\n            elif type(channel) == VoiceChannel:\n                voice_channels += 1\n\n        # How many of each user status?\n        member_count = ctx.guild.member_count\n        members = ctx.guild.members\n        online = 0\n        dnd = 0\n        idle = 0\n        offline = 0\n        for member in members:\n            if str(member.status) == \"online\":\n                online += 1\n            elif str(member.status) == \"offline\":\n                offline += 1\n            elif str(member.status) == \"idle\":\n                idle += 1\n            elif str(member.status) == \"dnd\":\n                dnd += 1\n\n        embed = Embed(\n            colour=Colour.blurple(),\n            description=textwrap.dedent(f\"\"\"\n                **Server information**\n                Created: {created}\n                Voice region: {region}\n                Features: {features}\n\n                **Counts**\n                Members: {member_count:,}\n                Roles: {roles}\n                Text: {text_channels}\n                Voice: {voice_channels}\n                Channel categories: {category_channels}\n\n                **Members**\n                {Emojis.status_online} {online}\n                {Emojis.status_idle} {idle}\n                {Emojis.status_dnd} {dnd}\n                {Emojis.status_offline} {offline}\n            \"\"\")\n        )\n\n        embed.set_thumbnail(url=ctx.guild.icon_url)\n\n        await ctx.send(embed=embed)\n\n    @command(name=\"user\", aliases=[\"user_info\", \"member\", \"member_info\"])\n    async def user_info(self, ctx: Context, user: Member = None, hidden: bool = False) -> None:\n        \"\"\"Returns info about a user.\"\"\"\n        if user is None:\n            user = ctx.author\n\n        # Do a role check if this is being executed on someone other than the caller\n        if user != ctx.author and not with_role_check(ctx, *MODERATION_ROLES):\n            await ctx.send(\"You may not use this command on users other than yourself.\")\n            return\n\n        # Non-moderators may only do this in #bot-commands and can't see hidden infractions.\n        if not with_role_check(ctx, *STAFF_ROLES):\n            if not ctx.channel.id == Channels.bot:\n                raise InChannelCheckFailure(Channels.bot)\n            # Hide hidden infractions for users without a moderation role\n            hidden = False\n\n        # User information\n        created = time_since(user.created_at, max_units=3)\n\n        # Custom status\n        custom_status = ''\n        for activity in user.activities:\n            if activity.name == 'Custom Status':\n                state = escape_markdown(activity.state)\n                custom_status = f'Status: {state}\\n'\n\n        name = str(user)\n        if user.nick:\n            name = f\"{user.nick} ({name})\"\n\n        # Member information\n        joined = time_since(user.joined_at, precision=\"days\")\n\n        # You're welcome, Volcyyyyyyyyyyyyyyyy\n        roles = \", \".join(role.mention for role in user.roles if role.name != \"@everyone\")\n\n        # Infractions\n        infractions = await self.bot.api_client.get(\n            'bot/infractions',\n            params={\n                'hidden': str(hidden),\n                'user__id': str(user.id)\n            }\n        )\n\n        infr_total = 0\n        infr_active = 0\n\n        # At least it's readable.\n        for infr in infractions:\n            if infr[\"active\"]:\n                infr_active += 1\n\n            infr_total += 1\n\n        # Let's build the embed now\n        embed = Embed(\n            title=name,\n            description=textwrap.dedent(f\"\"\"\n                **User Information**\n                Created: {created}\n                Profile: {user.mention}\n                ID: {user.id}\n                {custom_status}\n                **Member Information**\n                Joined: {joined}\n                Roles: {roles or None}\n\n                **Infractions**\n                Total: {infr_total}\n                Active: {infr_active}\n            \"\"\")\n        )\n\n        embed.set_thumbnail(url=user.avatar_url_as(format=\"png\"))\n        embed.colour = user.top_role.colour if roles else Colour.blurple()\n\n        await ctx.send(embed=embed)\n\n    def format_fields(self, mapping: Mapping[str, Any], field_width: Optional[int] = None) -> str:\n        \"\"\"Format a mapping to be readable to a human.\"\"\"\n        # sorting is technically superfluous but nice if you want to look for a specific field\n        fields = sorted(mapping.items(), key=lambda item: item[0])\n\n        if field_width is None:\n            field_width = len(max(mapping.keys(), key=len))\n\n        out = ''\n\n        for key, val in fields:\n            if isinstance(val, dict):\n                # if we have dicts inside dicts we want to apply the same treatment to the inner dictionaries\n                inner_width = int(field_width * 1.6)\n                val = '\\n' + self.format_fields(val, field_width=inner_width)\n\n            elif isinstance(val, str):\n                # split up text since it might be long\n                text = textwrap.fill(val, width=100, replace_whitespace=False)\n\n                # indent it, I guess you could do this with `wrap` and `join` but this is nicer\n                val = textwrap.indent(text, ' ' * (field_width + len(': ')))\n\n                # the first line is already indented so we `str.lstrip` it\n                val = val.lstrip()\n\n            if key == 'color':\n                # makes the base 10 representation of a hex number readable to humans\n                val = hex(val)\n\n            out += '{0:>{width}}: {1}\\n'.format(key, val, width=field_width)\n\n        # remove trailing whitespace\n        return out.rstrip()\n\n    @cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=STAFF_ROLES)\n    @group(invoke_without_command=True)\n    @in_channel(Channels.bot, bypass_roles=STAFF_ROLES)\n    async def raw(self, ctx: Context, *, message: discord.Message, json: bool = False) -> None:\n        \"\"\"Shows information about the raw API response.\"\"\"\n        # I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling\n        # doing this extra request is also much easier than trying to convert everything back into a dictionary again\n        raw_data = await ctx.bot.http.get_message(message.channel.id, message.id)\n\n        paginator = commands.Paginator()\n\n        def add_content(title: str, content: str) -> None:\n            paginator.add_line(f'== {title} ==\\n')\n            # replace backticks as it breaks out of code blocks. Spaces seemed to be the most reasonable solution.\n            # we hope it's not close to 2000\n            paginator.add_line(content.replace('```', '`` `'))\n            paginator.close_page()\n\n        if message.content:\n            add_content('Raw message', message.content)\n\n        transformer = pprint.pformat if json else self.format_fields\n        for field_name in ('embeds', 'attachments'):\n            data = raw_data[field_name]\n\n            if not data:\n                continue\n\n            total = len(data)\n            for current, item in enumerate(data, start=1):\n                title = f'Raw {field_name} ({current}/{total})'\n                add_content(title, transformer(item))\n\n        for page in paginator.pages:\n            await ctx.send(page)\n\n    @raw.command()\n    async def json(self, ctx: Context, message: discord.Message) -> None:\n        \"\"\"Shows information about the raw API response in a copy-pasteable Python format.\"\"\"\n        await ctx.invoke(self.raw, message=message, json=True)\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Information cog load.\"\"\"\n    bot.add_cog(Information(bot))\n    log.info(\"Cog loaded: Information\")\n", "path": "bot/cogs/information.py"}]}