We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
create command for getting parts of the zen of python
it would be nice to be able to get specific parts of the zen of python from a command. for example,

`!zen 13` would retrieve the fourteenth line (because we index from zero, obviously).

`!zen namespaces` would search for the string "namespaces", and produce the line which matches best. 

`!zen` without any arguments would still produce the entire thing in the same way that the `zen` tag does.

i think this is reasonably simple, and could come in handy from time to time. :D

edit: also, i'd quite like to implement this. seems simple enough while i get used to all of the new changes since last year :D

--- 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 logging
2 import re
3 import unicodedata
4 from asyncio import TimeoutError, sleep
5 from email.parser import HeaderParser
6 from io import StringIO
7 from typing import Tuple
8 
9 from dateutil import relativedelta
10 from discord import Colour, Embed, Message, Role
11 from discord.ext.commands import Cog, Context, command
12 
13 from bot.bot import Bot
14 from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES
15 from bot.decorators import in_channel, with_role
16 from bot.utils.time import humanize_delta
17 
18 log = logging.getLogger(__name__)
19 
20 
21 class Utils(Cog):
22     """A selection of utilities which don't have a clear category."""
23 
24     def __init__(self, bot: Bot):
25         self.bot = bot
26 
27         self.base_pep_url = "http://www.python.org/dev/peps/pep-"
28         self.base_github_pep_url = "https://raw.githubusercontent.com/python/peps/master/pep-"
29 
30     @command(name='pep', aliases=('get_pep', 'p'))
31     async def pep_command(self, ctx: Context, pep_number: str) -> None:
32         """Fetches information about a PEP and sends it to the channel."""
33         if pep_number.isdigit():
34             pep_number = int(pep_number)
35         else:
36             await ctx.invoke(self.bot.get_command("help"), "pep")
37             return
38 
39         possible_extensions = ['.txt', '.rst']
40         found_pep = False
41         for extension in possible_extensions:
42             # Attempt to fetch the PEP
43             pep_url = f"{self.base_github_pep_url}{pep_number:04}{extension}"
44             log.trace(f"Requesting PEP {pep_number} with {pep_url}")
45             response = await self.bot.http_session.get(pep_url)
46 
47             if response.status == 200:
48                 log.trace("PEP found")
49                 found_pep = True
50 
51                 pep_content = await response.text()
52 
53                 # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179
54                 pep_header = HeaderParser().parse(StringIO(pep_content))
55 
56                 # Assemble the embed
57                 pep_embed = Embed(
58                     title=f"**PEP {pep_number} - {pep_header['Title']}**",
59                     description=f"[Link]({self.base_pep_url}{pep_number:04})",
60                 )
61 
62                 pep_embed.set_thumbnail(url="https://www.python.org/static/opengraph-icon-200x200.png")
63 
64                 # Add the interesting information
65                 fields_to_check = ("Status", "Python-Version", "Created", "Type")
66                 for field in fields_to_check:
67                     # Check for a PEP metadata field that is present but has an empty value
68                     # embed field values can't contain an empty string
69                     if pep_header.get(field, ""):
70                         pep_embed.add_field(name=field, value=pep_header[field])
71 
72             elif response.status != 404:
73                 # any response except 200 and 404 is expected
74                 found_pep = True  # actually not, but it's easier to display this way
75                 log.trace(f"The user requested PEP {pep_number}, but the response had an unexpected status code: "
76                           f"{response.status}.\n{response.text}")
77 
78                 error_message = "Unexpected HTTP error during PEP search. Please let us know."
79                 pep_embed = Embed(title="Unexpected error", description=error_message)
80                 pep_embed.colour = Colour.red()
81                 break
82 
83         if not found_pep:
84             log.trace("PEP was not found")
85             not_found = f"PEP {pep_number} does not exist."
86             pep_embed = Embed(title="PEP not found", description=not_found)
87             pep_embed.colour = Colour.red()
88 
89         await ctx.message.channel.send(embed=pep_embed)
90 
91     @command()
92     @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES)
93     async def charinfo(self, ctx: Context, *, characters: str) -> None:
94         """Shows you information on up to 25 unicode characters."""
95         match = re.match(r"<(a?):(\w+):(\d+)>", characters)
96         if match:
97             embed = Embed(
98                 title="Non-Character Detected",
99                 description=(
100                     "Only unicode characters can be processed, but a custom Discord emoji "
101                     "was found. Please remove it and try again."
102                 )
103             )
104             embed.colour = Colour.red()
105             await ctx.send(embed=embed)
106             return
107 
108         if len(characters) > 25:
109             embed = Embed(title=f"Too many characters ({len(characters)}/25)")
110             embed.colour = Colour.red()
111             await ctx.send(embed=embed)
112             return
113 
114         def get_info(char: str) -> Tuple[str, str]:
115             digit = f"{ord(char):x}"
116             if len(digit) <= 4:
117                 u_code = f"\\u{digit:>04}"
118             else:
119                 u_code = f"\\U{digit:>08}"
120             url = f"https://www.compart.com/en/unicode/U+{digit:>04}"
121             name = f"[{unicodedata.name(char, '')}]({url})"
122             info = f"`{u_code.ljust(10)}`: {name} - {char}"
123             return info, u_code
124 
125         charlist, rawlist = zip(*(get_info(c) for c in characters))
126 
127         embed = Embed(description="\n".join(charlist))
128         embed.set_author(name="Character Info")
129 
130         if len(characters) > 1:
131             embed.add_field(name='Raw', value=f"`{''.join(rawlist)}`", inline=False)
132 
133         await ctx.send(embed=embed)
134 
135     @command()
136     @with_role(*MODERATION_ROLES)
137     async def mention(self, ctx: Context, *, role: Role) -> None:
138         """Set a role to be mentionable for a limited time."""
139         if role.mentionable:
140             await ctx.send(f"{role} is already mentionable!")
141             return
142 
143         await role.edit(reason=f"Role unlocked by {ctx.author}", mentionable=True)
144 
145         human_time = humanize_delta(relativedelta.relativedelta(seconds=Mention.message_timeout))
146         await ctx.send(
147             f"{role} has been made mentionable. I will reset it in {human_time}, or when someone mentions this role."
148         )
149 
150         def check(m: Message) -> bool:
151             """Checks that the message contains the role mention."""
152             return role in m.role_mentions
153 
154         try:
155             msg = await self.bot.wait_for("message", check=check, timeout=Mention.message_timeout)
156         except TimeoutError:
157             await role.edit(mentionable=False, reason="Automatic role lock - timeout.")
158             await ctx.send(f"{ctx.author.mention}, you took too long. I have reset {role} to be unmentionable.")
159             return
160 
161         if any(r.id in MODERATION_ROLES for r in msg.author.roles):
162             await sleep(Mention.reset_delay)
163             await role.edit(mentionable=False, reason=f"Automatic role lock by {msg.author}")
164             await ctx.send(
165                 f"{ctx.author.mention}, I have reset {role} to be unmentionable as "
166                 f"{msg.author if msg.author != ctx.author else 'you'} sent a message mentioning it."
167             )
168             return
169 
170         await role.edit(mentionable=False, reason=f"Automatic role lock - unauthorised use by {msg.author}")
171         await ctx.send(
172             f"{ctx.author.mention}, I have reset {role} to be unmentionable "
173             f"as I detected unauthorised use by {msg.author} (ID: {msg.author.id})."
174         )
175 
176 
177 def setup(bot: Bot) -> None:
178     """Load the Utils cog."""
179     bot.add_cog(Utils(bot))
180
```
--- 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
@@ -1,14 +1,15 @@
+import difflib
 import logging
 import re
 import unicodedata
 from asyncio import TimeoutError, sleep
 from email.parser import HeaderParser
 from io import StringIO
-from typing import Tuple
+from typing import Tuple, Union
 
 from dateutil import relativedelta
 from discord import Colour, Embed, Message, Role
-from discord.ext.commands import Cog, Context, command
+from discord.ext.commands import BadArgument, Cog, Context, command
 
 from bot.bot import Bot
 from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES
@@ -17,6 +18,28 @@
 
 log = logging.getLogger(__name__)
 
+ZEN_OF_PYTHON = """\
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+"""
+
 
 class Utils(Cog):
     """A selection of utilities which don't have a clear category."""
@@ -173,6 +196,67 @@
             f"as I detected unauthorised use by {msg.author} (ID: {msg.author.id})."
         )
 
+    @command()
+    async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None:
+        """
+        Show the Zen of Python.
+
+        Without any arguments, the full Zen will be produced.
+        If an integer is provided, the line with that index will be produced.
+        If a string is provided, the line which matches best will be produced.
+        """
+        embed = Embed(
+            colour=Colour.blurple(),
+            title="The Zen of Python",
+            description=ZEN_OF_PYTHON
+        )
+
+        if search_value is None:
+            embed.title += ", by Tim Peters"
+            await ctx.send(embed=embed)
+            return
+
+        zen_lines = ZEN_OF_PYTHON.splitlines()
+
+        # handle if it's an index int
+        if isinstance(search_value, int):
+            upper_bound = len(zen_lines) - 1
+            lower_bound = -1 * upper_bound
+            if not (lower_bound <= search_value <= upper_bound):
+                raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.")
+
+            embed.title += f" (line {search_value % len(zen_lines)}):"
+            embed.description = zen_lines[search_value]
+            await ctx.send(embed=embed)
+            return
+
+        # handle if it's a search string
+        matcher = difflib.SequenceMatcher(None, search_value.lower())
+
+        best_match = ""
+        match_index = 0
+        best_ratio = 0
+
+        for index, line in enumerate(zen_lines):
+            matcher.set_seq2(line.lower())
+
+            # the match ratio needs to be adjusted because, naturally,
+            # longer lines will have worse ratios than shorter lines when
+            # fuzzy searching for keywords. this seems to work okay.
+            adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio()
+
+            if adjusted_ratio > best_ratio:
+                best_ratio = adjusted_ratio
+                best_match = line
+                match_index = index
+
+        if not best_match:
+            raise BadArgument("I didn't get a match! Please try again with a different search term.")
+
+        embed.title += f" (line {match_index}):"
+        embed.description = best_match
+        await ctx.send(embed=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@@ -1,14 +1,15 @@\n+import difflib\n import logging\n import re\n import unicodedata\n from asyncio import TimeoutError, sleep\n from email.parser import HeaderParser\n from io import StringIO\n-from typing import Tuple\n+from typing import Tuple, Union\n \n from dateutil import relativedelta\n from discord import Colour, Embed, Message, Role\n-from discord.ext.commands import Cog, Context, command\n+from discord.ext.commands import BadArgument, Cog, Context, command\n \n from bot.bot import Bot\n from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES\n@@ -17,6 +18,28 @@\n \n log = logging.getLogger(__name__)\n \n+ZEN_OF_PYTHON = \"\"\"\\\n+Beautiful is better than ugly.\n+Explicit is better than implicit.\n+Simple is better than complex.\n+Complex is better than complicated.\n+Flat is better than nested.\n+Sparse is better than dense.\n+Readability counts.\n+Special cases aren't special enough to break the rules.\n+Although practicality beats purity.\n+Errors should never pass silently.\n+Unless explicitly silenced.\n+In the face of ambiguity, refuse the temptation to guess.\n+There should be one-- and preferably only one --obvious way to do it.\n+Although that way may not be obvious at first unless you're Dutch.\n+Now is better than never.\n+Although never is often better than *right* now.\n+If the implementation is hard to explain, it's a bad idea.\n+If the implementation is easy to explain, it may be a good idea.\n+Namespaces are one honking great idea -- let's do more of those!\n+\"\"\"\n+\n \n class Utils(Cog):\n     \"\"\"A selection of utilities which don't have a clear category.\"\"\"\n@@ -173,6 +196,67 @@\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 \n def setup(bot: Bot) -> None:\n     \"\"\"Load the Utils cog.\"\"\"\n", "issue": "create command for getting parts of the zen of python\nit would be nice to be able to get specific parts of the zen of python from a command. for example,\r\n\r\n`!zen 13` would retrieve the fourteenth line (because we index from zero, obviously).\r\n\r\n`!zen namespaces` would search for the string \"namespaces\", and produce the line which matches best. \r\n\r\n`!zen` without any arguments would still produce the entire thing in the same way that the `zen` tag does.\r\n\r\ni think this is reasonably simple, and could come in handy from time to time. :D\r\n\r\nedit: also, i'd quite like to implement this. seems simple enough while i get used to all of the new changes since last year :D\n", "before_files": [{"content": "import logging\nimport re\nimport unicodedata\nfrom asyncio import TimeoutError, sleep\nfrom email.parser import HeaderParser\nfrom io import StringIO\nfrom typing import Tuple\n\nfrom dateutil import relativedelta\nfrom discord import Colour, Embed, Message, Role\nfrom discord.ext.commands import 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\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\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\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\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Utils cog.\"\"\"\n    bot.add_cog(Utils(bot))\n", "path": "bot/cogs/utils.py"}]}