We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Pep command cannot fetch PEP 0.
The [PEP 0](https://www.python.org/dev/peps/) is an index of all the other PEPs;
instead of being in the github repo directly, it's generated automatically through a script present there.
~~If implemented, scraping the site for it seems to be the better option as generating it would require the whole up to date repo.~~
The header is a constant in the pep0 package and can be gotten from there in case of future changes 
 or as suggested in the comment below https://github.com/python/peps/blob/master/pep0/constants.py#L10-L20

--- 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/utils.py`
Content:
```
1 import difflib
2 import logging
3 import re
4 import unicodedata
5 from asyncio import TimeoutError, sleep
6 from email.parser import HeaderParser
7 from io import StringIO
8 from typing import Tuple, Union
9 
10 from dateutil import relativedelta
11 from discord import Colour, Embed, Message, Role
12 from discord.ext.commands import BadArgument, Cog, Context, command
13 
14 from bot.bot import Bot
15 from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES
16 from bot.decorators import in_channel, with_role
17 from bot.utils.time import humanize_delta
18 
19 log = logging.getLogger(__name__)
20 
21 ZEN_OF_PYTHON = """\
22 Beautiful is better than ugly.
23 Explicit is better than implicit.
24 Simple is better than complex.
25 Complex is better than complicated.
26 Flat is better than nested.
27 Sparse is better than dense.
28 Readability counts.
29 Special cases aren't special enough to break the rules.
30 Although practicality beats purity.
31 Errors should never pass silently.
32 Unless explicitly silenced.
33 In the face of ambiguity, refuse the temptation to guess.
34 There should be one-- and preferably only one --obvious way to do it.
35 Although that way may not be obvious at first unless you're Dutch.
36 Now is better than never.
37 Although never is often better than *right* now.
38 If the implementation is hard to explain, it's a bad idea.
39 If the implementation is easy to explain, it may be a good idea.
40 Namespaces are one honking great idea -- let's do more of those!
41 """
42 
43 
44 class Utils(Cog):
45     """A selection of utilities which don't have a clear category."""
46 
47     def __init__(self, bot: Bot):
48         self.bot = bot
49 
50         self.base_pep_url = "http://www.python.org/dev/peps/pep-"
51         self.base_github_pep_url = "https://raw.githubusercontent.com/python/peps/master/pep-"
52 
53     @command(name='pep', aliases=('get_pep', 'p'))
54     async def pep_command(self, ctx: Context, pep_number: str) -> None:
55         """Fetches information about a PEP and sends it to the channel."""
56         if pep_number.isdigit():
57             pep_number = int(pep_number)
58         else:
59             await ctx.invoke(self.bot.get_command("help"), "pep")
60             return
61 
62         possible_extensions = ['.txt', '.rst']
63         found_pep = False
64         for extension in possible_extensions:
65             # Attempt to fetch the PEP
66             pep_url = f"{self.base_github_pep_url}{pep_number:04}{extension}"
67             log.trace(f"Requesting PEP {pep_number} with {pep_url}")
68             response = await self.bot.http_session.get(pep_url)
69 
70             if response.status == 200:
71                 log.trace("PEP found")
72                 found_pep = True
73 
74                 pep_content = await response.text()
75 
76                 # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179
77                 pep_header = HeaderParser().parse(StringIO(pep_content))
78 
79                 # Assemble the embed
80                 pep_embed = Embed(
81                     title=f"**PEP {pep_number} - {pep_header['Title']}**",
82                     description=f"[Link]({self.base_pep_url}{pep_number:04})",
83                 )
84 
85                 pep_embed.set_thumbnail(url="https://www.python.org/static/opengraph-icon-200x200.png")
86 
87                 # Add the interesting information
88                 fields_to_check = ("Status", "Python-Version", "Created", "Type")
89                 for field in fields_to_check:
90                     # Check for a PEP metadata field that is present but has an empty value
91                     # embed field values can't contain an empty string
92                     if pep_header.get(field, ""):
93                         pep_embed.add_field(name=field, value=pep_header[field])
94 
95             elif response.status != 404:
96                 # any response except 200 and 404 is expected
97                 found_pep = True  # actually not, but it's easier to display this way
98                 log.trace(f"The user requested PEP {pep_number}, but the response had an unexpected status code: "
99                           f"{response.status}.\n{response.text}")
100 
101                 error_message = "Unexpected HTTP error during PEP search. Please let us know."
102                 pep_embed = Embed(title="Unexpected error", description=error_message)
103                 pep_embed.colour = Colour.red()
104                 break
105 
106         if not found_pep:
107             log.trace("PEP was not found")
108             not_found = f"PEP {pep_number} does not exist."
109             pep_embed = Embed(title="PEP not found", description=not_found)
110             pep_embed.colour = Colour.red()
111 
112         await ctx.message.channel.send(embed=pep_embed)
113 
114     @command()
115     @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES)
116     async def charinfo(self, ctx: Context, *, characters: str) -> None:
117         """Shows you information on up to 25 unicode characters."""
118         match = re.match(r"<(a?):(\w+):(\d+)>", characters)
119         if match:
120             embed = Embed(
121                 title="Non-Character Detected",
122                 description=(
123                     "Only unicode characters can be processed, but a custom Discord emoji "
124                     "was found. Please remove it and try again."
125                 )
126             )
127             embed.colour = Colour.red()
128             await ctx.send(embed=embed)
129             return
130 
131         if len(characters) > 25:
132             embed = Embed(title=f"Too many characters ({len(characters)}/25)")
133             embed.colour = Colour.red()
134             await ctx.send(embed=embed)
135             return
136 
137         def get_info(char: str) -> Tuple[str, str]:
138             digit = f"{ord(char):x}"
139             if len(digit) <= 4:
140                 u_code = f"\\u{digit:>04}"
141             else:
142                 u_code = f"\\U{digit:>08}"
143             url = f"https://www.compart.com/en/unicode/U+{digit:>04}"
144             name = f"[{unicodedata.name(char, '')}]({url})"
145             info = f"`{u_code.ljust(10)}`: {name} - {char}"
146             return info, u_code
147 
148         charlist, rawlist = zip(*(get_info(c) for c in characters))
149 
150         embed = Embed(description="\n".join(charlist))
151         embed.set_author(name="Character Info")
152 
153         if len(characters) > 1:
154             embed.add_field(name='Raw', value=f"`{''.join(rawlist)}`", inline=False)
155 
156         await ctx.send(embed=embed)
157 
158     @command()
159     @with_role(*MODERATION_ROLES)
160     async def mention(self, ctx: Context, *, role: Role) -> None:
161         """Set a role to be mentionable for a limited time."""
162         if role.mentionable:
163             await ctx.send(f"{role} is already mentionable!")
164             return
165 
166         await role.edit(reason=f"Role unlocked by {ctx.author}", mentionable=True)
167 
168         human_time = humanize_delta(relativedelta.relativedelta(seconds=Mention.message_timeout))
169         await ctx.send(
170             f"{role} has been made mentionable. I will reset it in {human_time}, or when someone mentions this role."
171         )
172 
173         def check(m: Message) -> bool:
174             """Checks that the message contains the role mention."""
175             return role in m.role_mentions
176 
177         try:
178             msg = await self.bot.wait_for("message", check=check, timeout=Mention.message_timeout)
179         except TimeoutError:
180             await role.edit(mentionable=False, reason="Automatic role lock - timeout.")
181             await ctx.send(f"{ctx.author.mention}, you took too long. I have reset {role} to be unmentionable.")
182             return
183 
184         if any(r.id in MODERATION_ROLES for r in msg.author.roles):
185             await sleep(Mention.reset_delay)
186             await role.edit(mentionable=False, reason=f"Automatic role lock by {msg.author}")
187             await ctx.send(
188                 f"{ctx.author.mention}, I have reset {role} to be unmentionable as "
189                 f"{msg.author if msg.author != ctx.author else 'you'} sent a message mentioning it."
190             )
191             return
192 
193         await role.edit(mentionable=False, reason=f"Automatic role lock - unauthorised use by {msg.author}")
194         await ctx.send(
195             f"{ctx.author.mention}, I have reset {role} to be unmentionable "
196             f"as I detected unauthorised use by {msg.author} (ID: {msg.author.id})."
197         )
198 
199     @command()
200     async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None:
201         """
202         Show the Zen of Python.
203 
204         Without any arguments, the full Zen will be produced.
205         If an integer is provided, the line with that index will be produced.
206         If a string is provided, the line which matches best will be produced.
207         """
208         embed = Embed(
209             colour=Colour.blurple(),
210             title="The Zen of Python",
211             description=ZEN_OF_PYTHON
212         )
213 
214         if search_value is None:
215             embed.title += ", by Tim Peters"
216             await ctx.send(embed=embed)
217             return
218 
219         zen_lines = ZEN_OF_PYTHON.splitlines()
220 
221         # handle if it's an index int
222         if isinstance(search_value, int):
223             upper_bound = len(zen_lines) - 1
224             lower_bound = -1 * upper_bound
225             if not (lower_bound <= search_value <= upper_bound):
226                 raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.")
227 
228             embed.title += f" (line {search_value % len(zen_lines)}):"
229             embed.description = zen_lines[search_value]
230             await ctx.send(embed=embed)
231             return
232 
233         # handle if it's a search string
234         matcher = difflib.SequenceMatcher(None, search_value.lower())
235 
236         best_match = ""
237         match_index = 0
238         best_ratio = 0
239 
240         for index, line in enumerate(zen_lines):
241             matcher.set_seq2(line.lower())
242 
243             # the match ratio needs to be adjusted because, naturally,
244             # longer lines will have worse ratios than shorter lines when
245             # fuzzy searching for keywords. this seems to work okay.
246             adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio()
247 
248             if adjusted_ratio > best_ratio:
249                 best_ratio = adjusted_ratio
250                 best_match = line
251                 match_index = index
252 
253         if not best_match:
254             raise BadArgument("I didn't get a match! Please try again with a different search term.")
255 
256         embed.title += f" (line {match_index}):"
257         embed.description = best_match
258         await ctx.send(embed=embed)
259 
260     @command(aliases=("poll",))
261     @with_role(*MODERATION_ROLES)
262     async def vote(self, ctx: Context, title: str, *options: str) -> None:
263         """
264         Build a quick voting poll with matching reactions with the provided options.
265 
266         A maximum of 20 options can be provided, as Discord supports a max of 20
267         reactions on a single message.
268         """
269         if len(options) < 2:
270             raise BadArgument("Please provide at least 2 options.")
271         if len(options) > 20:
272             raise BadArgument("I can only handle 20 options!")
273 
274         codepoint_start = 127462  # represents "regional_indicator_a" unicode value
275         options = {chr(i): f"{chr(i)} - {v}" for i, v in enumerate(options, start=codepoint_start)}
276         embed = Embed(title=title, description="\n".join(options.values()))
277         message = await ctx.send(embed=embed)
278         for reaction in options:
279             await message.add_reaction(reaction)
280 
281 
282 def setup(bot: Bot) -> None:
283     """Load the Utils cog."""
284     bot.add_cog(Utils(bot))
285
```
--- 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/utils.py b/bot/cogs/utils.py
--- a/bot/cogs/utils.py
+++ b/bot/cogs/utils.py
@@ -40,6 +40,8 @@
 Namespaces are one honking great idea -- let's do more of those!
 """
 
+ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png"
+
 
 class Utils(Cog):
     """A selection of utilities which don't have a clear category."""
@@ -59,6 +61,10 @@
             await ctx.invoke(self.bot.get_command("help"), "pep")
             return
 
+        # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs.
+        if pep_number == 0:
+            return await self.send_pep_zero(ctx)
+
         possible_extensions = ['.txt', '.rst']
         found_pep = False
         for extension in possible_extensions:
@@ -82,7 +88,7 @@
                     description=f"[Link]({self.base_pep_url}{pep_number:04})",
                 )
 
-                pep_embed.set_thumbnail(url="https://www.python.org/static/opengraph-icon-200x200.png")
+                pep_embed.set_thumbnail(url=ICON_URL)
 
                 # Add the interesting information
                 fields_to_check = ("Status", "Python-Version", "Created", "Type")
@@ -278,6 +284,19 @@
         for reaction in options:
             await message.add_reaction(reaction)
 
+    async def send_pep_zero(self, ctx: Context) -> None:
+        """Send information about PEP 0."""
+        pep_embed = Embed(
+            title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**",
+            description=f"[Link](https://www.python.org/dev/peps/)"
+        )
+        pep_embed.set_thumbnail(url=ICON_URL)
+        pep_embed.add_field(name="Status", value="Active")
+        pep_embed.add_field(name="Created", value="13-Jul-2000")
+        pep_embed.add_field(name="Type", value="Informational")
+
+        await ctx.send(embed=pep_embed)
+
 
 def setup(bot: Bot) -> None:
     """Load the Utils cog."""

{"golden_diff": "diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py\n--- a/bot/cogs/utils.py\n+++ b/bot/cogs/utils.py\n@@ -40,6 +40,8 @@\n Namespaces are one honking great idea -- let's do more of those!\n \"\"\"\n \n+ICON_URL = \"https://www.python.org/static/opengraph-icon-200x200.png\"\n+\n \n class Utils(Cog):\n     \"\"\"A selection of utilities which don't have a clear category.\"\"\"\n@@ -59,6 +61,10 @@\n             await ctx.invoke(self.bot.get_command(\"help\"), \"pep\")\n             return\n \n+        # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs.\n+        if pep_number == 0:\n+            return await self.send_pep_zero(ctx)\n+\n         possible_extensions = ['.txt', '.rst']\n         found_pep = False\n         for extension in possible_extensions:\n@@ -82,7 +88,7 @@\n                     description=f\"[Link]({self.base_pep_url}{pep_number:04})\",\n                 )\n \n-                pep_embed.set_thumbnail(url=\"https://www.python.org/static/opengraph-icon-200x200.png\")\n+                pep_embed.set_thumbnail(url=ICON_URL)\n \n                 # Add the interesting information\n                 fields_to_check = (\"Status\", \"Python-Version\", \"Created\", \"Type\")\n@@ -278,6 +284,19 @@\n         for reaction in options:\n             await message.add_reaction(reaction)\n \n+    async def send_pep_zero(self, ctx: Context) -> None:\n+        \"\"\"Send information about PEP 0.\"\"\"\n+        pep_embed = Embed(\n+            title=f\"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**\",\n+            description=f\"[Link](https://www.python.org/dev/peps/)\"\n+        )\n+        pep_embed.set_thumbnail(url=ICON_URL)\n+        pep_embed.add_field(name=\"Status\", value=\"Active\")\n+        pep_embed.add_field(name=\"Created\", value=\"13-Jul-2000\")\n+        pep_embed.add_field(name=\"Type\", value=\"Informational\")\n+\n+        await ctx.send(embed=pep_embed)\n+\n \n def setup(bot: Bot) -> None:\n     \"\"\"Load the Utils cog.\"\"\"\n", "issue": "Pep command cannot fetch PEP 0.\nThe [PEP 0](https://www.python.org/dev/peps/) is an index of all the other PEPs;\r\ninstead of being in the github repo directly, it's generated automatically through a script present there.\r\n~~If implemented, scraping the site for it seems to be the better option as generating it would require the whole up to date repo.~~\r\nThe header is a constant in the pep0 package and can be gotten from there in case of future changes \r\n or as suggested in the comment below https://github.com/python/peps/blob/master/pep0/constants.py#L10-L20\n", "before_files": [{"content": "import difflib\nimport logging\nimport re\nimport unicodedata\nfrom asyncio import TimeoutError, sleep\nfrom email.parser import HeaderParser\nfrom io import StringIO\nfrom typing import Tuple, Union\n\nfrom dateutil import relativedelta\nfrom discord import Colour, Embed, Message, Role\nfrom discord.ext.commands import BadArgument, Cog, Context, command\n\nfrom bot.bot import Bot\nfrom bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES\nfrom bot.decorators import in_channel, with_role\nfrom bot.utils.time import humanize_delta\n\nlog = logging.getLogger(__name__)\n\nZEN_OF_PYTHON = \"\"\"\\\nBeautiful is better than ugly.\nExplicit is better than implicit.\nSimple is better than complex.\nComplex is better than complicated.\nFlat is better than nested.\nSparse is better than dense.\nReadability counts.\nSpecial cases aren't special enough to break the rules.\nAlthough practicality beats purity.\nErrors should never pass silently.\nUnless explicitly silenced.\nIn the face of ambiguity, refuse the temptation to guess.\nThere should be one-- and preferably only one --obvious way to do it.\nAlthough that way may not be obvious at first unless you're Dutch.\nNow is better than never.\nAlthough never is often better than *right* now.\nIf the implementation is hard to explain, it's a bad idea.\nIf the implementation is easy to explain, it may be a good idea.\nNamespaces are one honking great idea -- let's do more of those!\n\"\"\"\n\n\nclass Utils(Cog):\n    \"\"\"A selection of utilities which don't have a clear category.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n        self.base_pep_url = \"http://www.python.org/dev/peps/pep-\"\n        self.base_github_pep_url = \"https://raw.githubusercontent.com/python/peps/master/pep-\"\n\n    @command(name='pep', aliases=('get_pep', 'p'))\n    async def pep_command(self, ctx: Context, pep_number: str) -> None:\n        \"\"\"Fetches information about a PEP and sends it to the channel.\"\"\"\n        if pep_number.isdigit():\n            pep_number = int(pep_number)\n        else:\n            await ctx.invoke(self.bot.get_command(\"help\"), \"pep\")\n            return\n\n        possible_extensions = ['.txt', '.rst']\n        found_pep = False\n        for extension in possible_extensions:\n            # Attempt to fetch the PEP\n            pep_url = f\"{self.base_github_pep_url}{pep_number:04}{extension}\"\n            log.trace(f\"Requesting PEP {pep_number} with {pep_url}\")\n            response = await self.bot.http_session.get(pep_url)\n\n            if response.status == 200:\n                log.trace(\"PEP found\")\n                found_pep = True\n\n                pep_content = await response.text()\n\n                # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179\n                pep_header = HeaderParser().parse(StringIO(pep_content))\n\n                # Assemble the embed\n                pep_embed = Embed(\n                    title=f\"**PEP {pep_number} - {pep_header['Title']}**\",\n                    description=f\"[Link]({self.base_pep_url}{pep_number:04})\",\n                )\n\n                pep_embed.set_thumbnail(url=\"https://www.python.org/static/opengraph-icon-200x200.png\")\n\n                # Add the interesting information\n                fields_to_check = (\"Status\", \"Python-Version\", \"Created\", \"Type\")\n                for field in fields_to_check:\n                    # Check for a PEP metadata field that is present but has an empty value\n                    # embed field values can't contain an empty string\n                    if pep_header.get(field, \"\"):\n                        pep_embed.add_field(name=field, value=pep_header[field])\n\n            elif response.status != 404:\n                # any response except 200 and 404 is expected\n                found_pep = True  # actually not, but it's easier to display this way\n                log.trace(f\"The user requested PEP {pep_number}, but the response had an unexpected status code: \"\n                          f\"{response.status}.\\n{response.text}\")\n\n                error_message = \"Unexpected HTTP error during PEP search. Please let us know.\"\n                pep_embed = Embed(title=\"Unexpected error\", description=error_message)\n                pep_embed.colour = Colour.red()\n                break\n\n        if not found_pep:\n            log.trace(\"PEP was not found\")\n            not_found = f\"PEP {pep_number} does not exist.\"\n            pep_embed = Embed(title=\"PEP not found\", description=not_found)\n            pep_embed.colour = Colour.red()\n\n        await ctx.message.channel.send(embed=pep_embed)\n\n    @command()\n    @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES)\n    async def charinfo(self, ctx: Context, *, characters: str) -> None:\n        \"\"\"Shows you information on up to 25 unicode characters.\"\"\"\n        match = re.match(r\"<(a?):(\\w+):(\\d+)>\", characters)\n        if match:\n            embed = Embed(\n                title=\"Non-Character Detected\",\n                description=(\n                    \"Only unicode characters can be processed, but a custom Discord emoji \"\n                    \"was found. Please remove it and try again.\"\n                )\n            )\n            embed.colour = Colour.red()\n            await ctx.send(embed=embed)\n            return\n\n        if len(characters) > 25:\n            embed = Embed(title=f\"Too many characters ({len(characters)}/25)\")\n            embed.colour = Colour.red()\n            await ctx.send(embed=embed)\n            return\n\n        def get_info(char: str) -> Tuple[str, str]:\n            digit = f\"{ord(char):x}\"\n            if len(digit) <= 4:\n                u_code = f\"\\\\u{digit:>04}\"\n            else:\n                u_code = f\"\\\\U{digit:>08}\"\n            url = f\"https://www.compart.com/en/unicode/U+{digit:>04}\"\n            name = f\"[{unicodedata.name(char, '')}]({url})\"\n            info = f\"`{u_code.ljust(10)}`: {name} - {char}\"\n            return info, u_code\n\n        charlist, rawlist = zip(*(get_info(c) for c in characters))\n\n        embed = Embed(description=\"\\n\".join(charlist))\n        embed.set_author(name=\"Character Info\")\n\n        if len(characters) > 1:\n            embed.add_field(name='Raw', value=f\"`{''.join(rawlist)}`\", inline=False)\n\n        await ctx.send(embed=embed)\n\n    @command()\n    @with_role(*MODERATION_ROLES)\n    async def mention(self, ctx: Context, *, role: Role) -> None:\n        \"\"\"Set a role to be mentionable for a limited time.\"\"\"\n        if role.mentionable:\n            await ctx.send(f\"{role} is already mentionable!\")\n            return\n\n        await role.edit(reason=f\"Role unlocked by {ctx.author}\", mentionable=True)\n\n        human_time = humanize_delta(relativedelta.relativedelta(seconds=Mention.message_timeout))\n        await ctx.send(\n            f\"{role} has been made mentionable. I will reset it in {human_time}, or when someone mentions this role.\"\n        )\n\n        def check(m: Message) -> bool:\n            \"\"\"Checks that the message contains the role mention.\"\"\"\n            return role in m.role_mentions\n\n        try:\n            msg = await self.bot.wait_for(\"message\", check=check, timeout=Mention.message_timeout)\n        except TimeoutError:\n            await role.edit(mentionable=False, reason=\"Automatic role lock - timeout.\")\n            await ctx.send(f\"{ctx.author.mention}, you took too long. I have reset {role} to be unmentionable.\")\n            return\n\n        if any(r.id in MODERATION_ROLES for r in msg.author.roles):\n            await sleep(Mention.reset_delay)\n            await role.edit(mentionable=False, reason=f\"Automatic role lock by {msg.author}\")\n            await ctx.send(\n                f\"{ctx.author.mention}, I have reset {role} to be unmentionable as \"\n                f\"{msg.author if msg.author != ctx.author else 'you'} sent a message mentioning it.\"\n            )\n            return\n\n        await role.edit(mentionable=False, reason=f\"Automatic role lock - unauthorised use by {msg.author}\")\n        await ctx.send(\n            f\"{ctx.author.mention}, I have reset {role} to be unmentionable \"\n            f\"as I detected unauthorised use by {msg.author} (ID: {msg.author.id}).\"\n        )\n\n    @command()\n    async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None:\n        \"\"\"\n        Show the Zen of Python.\n\n        Without any arguments, the full Zen will be produced.\n        If an integer is provided, the line with that index will be produced.\n        If a string is provided, the line which matches best will be produced.\n        \"\"\"\n        embed = Embed(\n            colour=Colour.blurple(),\n            title=\"The Zen of Python\",\n            description=ZEN_OF_PYTHON\n        )\n\n        if search_value is None:\n            embed.title += \", by Tim Peters\"\n            await ctx.send(embed=embed)\n            return\n\n        zen_lines = ZEN_OF_PYTHON.splitlines()\n\n        # handle if it's an index int\n        if isinstance(search_value, int):\n            upper_bound = len(zen_lines) - 1\n            lower_bound = -1 * upper_bound\n            if not (lower_bound <= search_value <= upper_bound):\n                raise BadArgument(f\"Please provide an index between {lower_bound} and {upper_bound}.\")\n\n            embed.title += f\" (line {search_value % len(zen_lines)}):\"\n            embed.description = zen_lines[search_value]\n            await ctx.send(embed=embed)\n            return\n\n        # handle if it's a search string\n        matcher = difflib.SequenceMatcher(None, search_value.lower())\n\n        best_match = \"\"\n        match_index = 0\n        best_ratio = 0\n\n        for index, line in enumerate(zen_lines):\n            matcher.set_seq2(line.lower())\n\n            # the match ratio needs to be adjusted because, naturally,\n            # longer lines will have worse ratios than shorter lines when\n            # fuzzy searching for keywords. this seems to work okay.\n            adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio()\n\n            if adjusted_ratio > best_ratio:\n                best_ratio = adjusted_ratio\n                best_match = line\n                match_index = index\n\n        if not best_match:\n            raise BadArgument(\"I didn't get a match! Please try again with a different search term.\")\n\n        embed.title += f\" (line {match_index}):\"\n        embed.description = best_match\n        await ctx.send(embed=embed)\n\n    @command(aliases=(\"poll\",))\n    @with_role(*MODERATION_ROLES)\n    async def vote(self, ctx: Context, title: str, *options: str) -> None:\n        \"\"\"\n        Build a quick voting poll with matching reactions with the provided options.\n\n        A maximum of 20 options can be provided, as Discord supports a max of 20\n        reactions on a single message.\n        \"\"\"\n        if len(options) < 2:\n            raise BadArgument(\"Please provide at least 2 options.\")\n        if len(options) > 20:\n            raise BadArgument(\"I can only handle 20 options!\")\n\n        codepoint_start = 127462  # represents \"regional_indicator_a\" unicode value\n        options = {chr(i): f\"{chr(i)} - {v}\" for i, v in enumerate(options, start=codepoint_start)}\n        embed = Embed(title=title, description=\"\\n\".join(options.values()))\n        message = await ctx.send(embed=embed)\n        for reaction in options:\n            await message.add_reaction(reaction)\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Utils cog.\"\"\"\n    bot.add_cog(Utils(bot))\n", "path": "bot/cogs/utils.py"}], "after_files": [{"content": "import difflib\nimport logging\nimport re\nimport unicodedata\nfrom asyncio import TimeoutError, sleep\nfrom email.parser import HeaderParser\nfrom io import StringIO\nfrom typing import Tuple, Union\n\nfrom dateutil import relativedelta\nfrom discord import Colour, Embed, Message, Role\nfrom discord.ext.commands import BadArgument, Cog, Context, command\n\nfrom bot.bot import Bot\nfrom bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES\nfrom bot.decorators import in_channel, with_role\nfrom bot.utils.time import humanize_delta\n\nlog = logging.getLogger(__name__)\n\nZEN_OF_PYTHON = \"\"\"\\\nBeautiful is better than ugly.\nExplicit is better than implicit.\nSimple is better than complex.\nComplex is better than complicated.\nFlat is better than nested.\nSparse is better than dense.\nReadability counts.\nSpecial cases aren't special enough to break the rules.\nAlthough practicality beats purity.\nErrors should never pass silently.\nUnless explicitly silenced.\nIn the face of ambiguity, refuse the temptation to guess.\nThere should be one-- and preferably only one --obvious way to do it.\nAlthough that way may not be obvious at first unless you're Dutch.\nNow is better than never.\nAlthough never is often better than *right* now.\nIf the implementation is hard to explain, it's a bad idea.\nIf the implementation is easy to explain, it may be a good idea.\nNamespaces are one honking great idea -- let's do more of those!\n\"\"\"\n\nICON_URL = \"https://www.python.org/static/opengraph-icon-200x200.png\"\n\n\nclass Utils(Cog):\n    \"\"\"A selection of utilities which don't have a clear category.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n        self.base_pep_url = \"http://www.python.org/dev/peps/pep-\"\n        self.base_github_pep_url = \"https://raw.githubusercontent.com/python/peps/master/pep-\"\n\n    @command(name='pep', aliases=('get_pep', 'p'))\n    async def pep_command(self, ctx: Context, pep_number: str) -> None:\n        \"\"\"Fetches information about a PEP and sends it to the channel.\"\"\"\n        if pep_number.isdigit():\n            pep_number = int(pep_number)\n        else:\n            await ctx.invoke(self.bot.get_command(\"help\"), \"pep\")\n            return\n\n        # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs.\n        if pep_number == 0:\n            return await self.send_pep_zero(ctx)\n\n        possible_extensions = ['.txt', '.rst']\n        found_pep = False\n        for extension in possible_extensions:\n            # Attempt to fetch the PEP\n            pep_url = f\"{self.base_github_pep_url}{pep_number:04}{extension}\"\n            log.trace(f\"Requesting PEP {pep_number} with {pep_url}\")\n            response = await self.bot.http_session.get(pep_url)\n\n            if response.status == 200:\n                log.trace(\"PEP found\")\n                found_pep = True\n\n                pep_content = await response.text()\n\n                # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179\n                pep_header = HeaderParser().parse(StringIO(pep_content))\n\n                # Assemble the embed\n                pep_embed = Embed(\n                    title=f\"**PEP {pep_number} - {pep_header['Title']}**\",\n                    description=f\"[Link]({self.base_pep_url}{pep_number:04})\",\n                )\n\n                pep_embed.set_thumbnail(url=ICON_URL)\n\n                # Add the interesting information\n                fields_to_check = (\"Status\", \"Python-Version\", \"Created\", \"Type\")\n                for field in fields_to_check:\n                    # Check for a PEP metadata field that is present but has an empty value\n                    # embed field values can't contain an empty string\n                    if pep_header.get(field, \"\"):\n                        pep_embed.add_field(name=field, value=pep_header[field])\n\n            elif response.status != 404:\n                # any response except 200 and 404 is expected\n                found_pep = True  # actually not, but it's easier to display this way\n                log.trace(f\"The user requested PEP {pep_number}, but the response had an unexpected status code: \"\n                          f\"{response.status}.\\n{response.text}\")\n\n                error_message = \"Unexpected HTTP error during PEP search. Please let us know.\"\n                pep_embed = Embed(title=\"Unexpected error\", description=error_message)\n                pep_embed.colour = Colour.red()\n                break\n\n        if not found_pep:\n            log.trace(\"PEP was not found\")\n            not_found = f\"PEP {pep_number} does not exist.\"\n            pep_embed = Embed(title=\"PEP not found\", description=not_found)\n            pep_embed.colour = Colour.red()\n\n        await ctx.message.channel.send(embed=pep_embed)\n\n    @command()\n    @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES)\n    async def charinfo(self, ctx: Context, *, characters: str) -> None:\n        \"\"\"Shows you information on up to 25 unicode characters.\"\"\"\n        match = re.match(r\"<(a?):(\\w+):(\\d+)>\", characters)\n        if match:\n            embed = Embed(\n                title=\"Non-Character Detected\",\n                description=(\n                    \"Only unicode characters can be processed, but a custom Discord emoji \"\n                    \"was found. Please remove it and try again.\"\n                )\n            )\n            embed.colour = Colour.red()\n            await ctx.send(embed=embed)\n            return\n\n        if len(characters) > 25:\n            embed = Embed(title=f\"Too many characters ({len(characters)}/25)\")\n            embed.colour = Colour.red()\n            await ctx.send(embed=embed)\n            return\n\n        def get_info(char: str) -> Tuple[str, str]:\n            digit = f\"{ord(char):x}\"\n            if len(digit) <= 4:\n                u_code = f\"\\\\u{digit:>04}\"\n            else:\n                u_code = f\"\\\\U{digit:>08}\"\n            url = f\"https://www.compart.com/en/unicode/U+{digit:>04}\"\n            name = f\"[{unicodedata.name(char, '')}]({url})\"\n            info = f\"`{u_code.ljust(10)}`: {name} - {char}\"\n            return info, u_code\n\n        charlist, rawlist = zip(*(get_info(c) for c in characters))\n\n        embed = Embed(description=\"\\n\".join(charlist))\n        embed.set_author(name=\"Character Info\")\n\n        if len(characters) > 1:\n            embed.add_field(name='Raw', value=f\"`{''.join(rawlist)}`\", inline=False)\n\n        await ctx.send(embed=embed)\n\n    @command()\n    @with_role(*MODERATION_ROLES)\n    async def mention(self, ctx: Context, *, role: Role) -> None:\n        \"\"\"Set a role to be mentionable for a limited time.\"\"\"\n        if role.mentionable:\n            await ctx.send(f\"{role} is already mentionable!\")\n            return\n\n        await role.edit(reason=f\"Role unlocked by {ctx.author}\", mentionable=True)\n\n        human_time = humanize_delta(relativedelta.relativedelta(seconds=Mention.message_timeout))\n        await ctx.send(\n            f\"{role} has been made mentionable. I will reset it in {human_time}, or when someone mentions this role.\"\n        )\n\n        def check(m: Message) -> bool:\n            \"\"\"Checks that the message contains the role mention.\"\"\"\n            return role in m.role_mentions\n\n        try:\n            msg = await self.bot.wait_for(\"message\", check=check, timeout=Mention.message_timeout)\n        except TimeoutError:\n            await role.edit(mentionable=False, reason=\"Automatic role lock - timeout.\")\n            await ctx.send(f\"{ctx.author.mention}, you took too long. I have reset {role} to be unmentionable.\")\n            return\n\n        if any(r.id in MODERATION_ROLES for r in msg.author.roles):\n            await sleep(Mention.reset_delay)\n            await role.edit(mentionable=False, reason=f\"Automatic role lock by {msg.author}\")\n            await ctx.send(\n                f\"{ctx.author.mention}, I have reset {role} to be unmentionable as \"\n                f\"{msg.author if msg.author != ctx.author else 'you'} sent a message mentioning it.\"\n            )\n            return\n\n        await role.edit(mentionable=False, reason=f\"Automatic role lock - unauthorised use by {msg.author}\")\n        await ctx.send(\n            f\"{ctx.author.mention}, I have reset {role} to be unmentionable \"\n            f\"as I detected unauthorised use by {msg.author} (ID: {msg.author.id}).\"\n        )\n\n    @command()\n    async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None:\n        \"\"\"\n        Show the Zen of Python.\n\n        Without any arguments, the full Zen will be produced.\n        If an integer is provided, the line with that index will be produced.\n        If a string is provided, the line which matches best will be produced.\n        \"\"\"\n        embed = Embed(\n            colour=Colour.blurple(),\n            title=\"The Zen of Python\",\n            description=ZEN_OF_PYTHON\n        )\n\n        if search_value is None:\n            embed.title += \", by Tim Peters\"\n            await ctx.send(embed=embed)\n            return\n\n        zen_lines = ZEN_OF_PYTHON.splitlines()\n\n        # handle if it's an index int\n        if isinstance(search_value, int):\n            upper_bound = len(zen_lines) - 1\n            lower_bound = -1 * upper_bound\n            if not (lower_bound <= search_value <= upper_bound):\n                raise BadArgument(f\"Please provide an index between {lower_bound} and {upper_bound}.\")\n\n            embed.title += f\" (line {search_value % len(zen_lines)}):\"\n            embed.description = zen_lines[search_value]\n            await ctx.send(embed=embed)\n            return\n\n        # handle if it's a search string\n        matcher = difflib.SequenceMatcher(None, search_value.lower())\n\n        best_match = \"\"\n        match_index = 0\n        best_ratio = 0\n\n        for index, line in enumerate(zen_lines):\n            matcher.set_seq2(line.lower())\n\n            # the match ratio needs to be adjusted because, naturally,\n            # longer lines will have worse ratios than shorter lines when\n            # fuzzy searching for keywords. this seems to work okay.\n            adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio()\n\n            if adjusted_ratio > best_ratio:\n                best_ratio = adjusted_ratio\n                best_match = line\n                match_index = index\n\n        if not best_match:\n            raise BadArgument(\"I didn't get a match! Please try again with a different search term.\")\n\n        embed.title += f\" (line {match_index}):\"\n        embed.description = best_match\n        await ctx.send(embed=embed)\n\n    @command(aliases=(\"poll\",))\n    @with_role(*MODERATION_ROLES)\n    async def vote(self, ctx: Context, title: str, *options: str) -> None:\n        \"\"\"\n        Build a quick voting poll with matching reactions with the provided options.\n\n        A maximum of 20 options can be provided, as Discord supports a max of 20\n        reactions on a single message.\n        \"\"\"\n        if len(options) < 2:\n            raise BadArgument(\"Please provide at least 2 options.\")\n        if len(options) > 20:\n            raise BadArgument(\"I can only handle 20 options!\")\n\n        codepoint_start = 127462  # represents \"regional_indicator_a\" unicode value\n        options = {chr(i): f\"{chr(i)} - {v}\" for i, v in enumerate(options, start=codepoint_start)}\n        embed = Embed(title=title, description=\"\\n\".join(options.values()))\n        message = await ctx.send(embed=embed)\n        for reaction in options:\n            await message.add_reaction(reaction)\n\n    async def send_pep_zero(self, ctx: Context) -> None:\n        \"\"\"Send information about PEP 0.\"\"\"\n        pep_embed = Embed(\n            title=f\"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**\",\n            description=f\"[Link](https://www.python.org/dev/peps/)\"\n        )\n        pep_embed.set_thumbnail(url=ICON_URL)\n        pep_embed.add_field(name=\"Status\", value=\"Active\")\n        pep_embed.add_field(name=\"Created\", value=\"13-Jul-2000\")\n        pep_embed.add_field(name=\"Type\", value=\"Informational\")\n\n        await ctx.send(embed=pep_embed)\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Utils cog.\"\"\"\n    bot.add_cog(Utils(bot))\n", "path": "bot/cogs/utils.py"}]}