We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Implement a search command for !otn
With hundreds of off-topic names in our list, looking for one by clicking through the paginator with the bot is tedious.

Let's have a `!otn search <name>` command!

#### Implementation Ideas
- Use the text search functionality in postgres 
- Fuzzy search (`fuzzystrmatch` maybe?)
- Ranked list based on similarity to query

--- 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/off_topic_names.py`
Content:
```
1 import asyncio
2 import logging
3 from datetime import datetime, timedelta
4 
5 from discord import Colour, Embed
6 from discord.ext.commands import BadArgument, Bot, Cog, Context, Converter, group
7 
8 from bot.constants import Channels, MODERATION_ROLES
9 from bot.decorators import with_role
10 from bot.pagination import LinePaginator
11 
12 
13 CHANNELS = (Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2)
14 log = logging.getLogger(__name__)
15 
16 
17 class OffTopicName(Converter):
18     """A converter that ensures an added off-topic name is valid."""
19 
20     @staticmethod
21     async def convert(ctx: Context, argument: str):
22         allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-"
23 
24         if not (2 <= len(argument) <= 96):
25             raise BadArgument("Channel name must be between 2 and 96 chars long")
26 
27         elif not all(c.isalnum() or c in allowed_characters for c in argument):
28             raise BadArgument(
29                 "Channel name must only consist of "
30                 "alphanumeric characters, minus signs or apostrophes."
31             )
32 
33         # Replace invalid characters with unicode alternatives.
34         table = str.maketrans(
35             allowed_characters, '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ？’’-'
36         )
37         return argument.translate(table)
38 
39 
40 async def update_names(bot: Bot):
41     """
42     The background updater task that performs a channel name update daily.
43 
44     Args:
45         bot (Bot):
46             The running bot instance, used for fetching data from the
47             website via the bot's `api_client`.
48     """
49 
50     while True:
51         # Since we truncate the compute timedelta to seconds, we add one second to ensure
52         # we go past midnight in the `seconds_to_sleep` set below.
53         today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)
54         next_midnight = today_at_midnight + timedelta(days=1)
55         seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds + 1
56         await asyncio.sleep(seconds_to_sleep)
57 
58         channel_0_name, channel_1_name, channel_2_name = await bot.api_client.get(
59             'bot/off-topic-channel-names', params={'random_items': 3}
60         )
61         channel_0, channel_1, channel_2 = (bot.get_channel(channel_id) for channel_id in CHANNELS)
62 
63         await channel_0.edit(name=f'ot0-{channel_0_name}')
64         await channel_1.edit(name=f'ot1-{channel_1_name}')
65         await channel_2.edit(name=f'ot2-{channel_2_name}')
66         log.debug(
67             "Updated off-topic channel names to"
68             f" {channel_0_name}, {channel_1_name} and {channel_2_name}"
69         )
70 
71 
72 class OffTopicNames(Cog):
73     """Commands related to managing the off-topic category channel names."""
74 
75     def __init__(self, bot: Bot):
76         self.bot = bot
77         self.updater_task = None
78 
79     def cog_unload(self):
80         if self.updater_task is not None:
81             self.updater_task.cancel()
82 
83     @Cog.listener()
84     async def on_ready(self):
85         if self.updater_task is None:
86             coro = update_names(self.bot)
87             self.updater_task = self.bot.loop.create_task(coro)
88 
89     @group(name='otname', aliases=('otnames', 'otn'), invoke_without_command=True)
90     @with_role(*MODERATION_ROLES)
91     async def otname_group(self, ctx):
92         """Add or list items from the off-topic channel name rotation."""
93 
94         await ctx.invoke(self.bot.get_command("help"), "otname")
95 
96     @otname_group.command(name='add', aliases=('a',))
97     @with_role(*MODERATION_ROLES)
98     async def add_command(self, ctx, *names: OffTopicName):
99         """Adds a new off-topic name to the rotation."""
100         # Chain multiple words to a single one
101         name = "-".join(names)
102 
103         await self.bot.api_client.post(f'bot/off-topic-channel-names', params={'name': name})
104         log.info(
105             f"{ctx.author.name}#{ctx.author.discriminator}"
106             f" added the off-topic channel name '{name}"
107         )
108         await ctx.send(f":ok_hand: Added `{name}` to the names list.")
109 
110     @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))
111     @with_role(*MODERATION_ROLES)
112     async def delete_command(self, ctx, *names: OffTopicName):
113         """Removes a off-topic name from the rotation."""
114         # Chain multiple words to a single one
115         name = "-".join(names)
116 
117         await self.bot.api_client.delete(f'bot/off-topic-channel-names/{name}')
118         log.info(
119             f"{ctx.author.name}#{ctx.author.discriminator}"
120             f" deleted the off-topic channel name '{name}"
121         )
122         await ctx.send(f":ok_hand: Removed `{name}` from the names list.")
123 
124     @otname_group.command(name='list', aliases=('l',))
125     @with_role(*MODERATION_ROLES)
126     async def list_command(self, ctx):
127         """
128         Lists all currently known off-topic channel names in a paginator.
129         Restricted to Moderator and above to not spoil the surprise.
130         """
131 
132         result = await self.bot.api_client.get('bot/off-topic-channel-names')
133         lines = sorted(f"• {name}" for name in result)
134         embed = Embed(
135             title=f"Known off-topic names (`{len(result)}` total)",
136             colour=Colour.blue()
137         )
138         if result:
139             await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)
140         else:
141             embed.description = "Hmmm, seems like there's nothing here yet."
142             await ctx.send(embed=embed)
143 
144 
145 def setup(bot: Bot):
146     bot.add_cog(OffTopicNames(bot))
147     log.info("Cog loaded: OffTopicNames")
148
```
--- 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/off_topic_names.py b/bot/cogs/off_topic_names.py
--- a/bot/cogs/off_topic_names.py
+++ b/bot/cogs/off_topic_names.py
@@ -1,4 +1,5 @@
 import asyncio
+import difflib
 import logging
 from datetime import datetime, timedelta
 
@@ -141,6 +142,27 @@
             embed.description = "Hmmm, seems like there's nothing here yet."
             await ctx.send(embed=embed)
 
+    @otname_group.command(name='search', aliases=('s',))
+    @with_role(*MODERATION_ROLES)
+    async def search_command(self, ctx, *, query: str):
+        """
+        Search for an off-topic name.
+        """
+
+        result = await self.bot.api_client.get('bot/off-topic-channel-names')
+        matches = difflib.get_close_matches(query, result, n=10, cutoff=0.35)
+        lines = sorted(f"• {name}" for name in matches)
+        embed = Embed(
+            title=f"Query results",
+            colour=Colour.blue()
+        )
+
+        if matches:
+            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)
+        else:
+            embed.description = "Nothing found."
+            await ctx.send(embed=embed)
+
 
 def setup(bot: Bot):
     bot.add_cog(OffTopicNames(bot))

{"golden_diff": "diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py\n--- a/bot/cogs/off_topic_names.py\n+++ b/bot/cogs/off_topic_names.py\n@@ -1,4 +1,5 @@\n import asyncio\n+import difflib\n import logging\n from datetime import datetime, timedelta\n \n@@ -141,6 +142,27 @@\n             embed.description = \"Hmmm, seems like there's nothing here yet.\"\n             await ctx.send(embed=embed)\n \n+    @otname_group.command(name='search', aliases=('s',))\n+    @with_role(*MODERATION_ROLES)\n+    async def search_command(self, ctx, *, query: str):\n+        \"\"\"\n+        Search for an off-topic name.\n+        \"\"\"\n+\n+        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n+        matches = difflib.get_close_matches(query, result, n=10, cutoff=0.35)\n+        lines = sorted(f\"\u2022 {name}\" for name in matches)\n+        embed = Embed(\n+            title=f\"Query results\",\n+            colour=Colour.blue()\n+        )\n+\n+        if matches:\n+            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\n+        else:\n+            embed.description = \"Nothing found.\"\n+            await ctx.send(embed=embed)\n+\n \n def setup(bot: Bot):\n     bot.add_cog(OffTopicNames(bot))\n", "issue": "Implement a search command for !otn\nWith hundreds of off-topic names in our list, looking for one by clicking through the paginator with the bot is tedious.\r\n\r\nLet's have a `!otn search <name>` command!\r\n\r\n#### Implementation Ideas\r\n- Use the text search functionality in postgres \r\n- Fuzzy search (`fuzzystrmatch` maybe?)\r\n- Ranked list based on similarity to query\n", "before_files": [{"content": "import asyncio\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom discord import Colour, Embed\nfrom discord.ext.commands import BadArgument, Bot, Cog, Context, Converter, group\n\nfrom bot.constants import Channels, MODERATION_ROLES\nfrom bot.decorators import with_role\nfrom bot.pagination import LinePaginator\n\n\nCHANNELS = (Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2)\nlog = logging.getLogger(__name__)\n\n\nclass OffTopicName(Converter):\n    \"\"\"A converter that ensures an added off-topic name is valid.\"\"\"\n\n    @staticmethod\n    async def convert(ctx: Context, argument: str):\n        allowed_characters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-\"\n\n        if not (2 <= len(argument) <= 96):\n            raise BadArgument(\"Channel name must be between 2 and 96 chars long\")\n\n        elif not all(c.isalnum() or c in allowed_characters for c in argument):\n            raise BadArgument(\n                \"Channel name must only consist of \"\n                \"alphanumeric characters, minus signs or apostrophes.\"\n            )\n\n        # Replace invalid characters with unicode alternatives.\n        table = str.maketrans(\n            allowed_characters, '\ud835\udda0\ud835\udda1\ud835\udda2\ud835\udda3\ud835\udda4\ud835\udda5\ud835\udda6\ud835\udda7\ud835\udda8\ud835\udda9\ud835\uddaa\ud835\uddab\ud835\uddac\ud835\uddad\ud835\uddae\ud835\uddaf\ud835\uddb0\ud835\uddb1\ud835\uddb2\ud835\uddb3\ud835\uddb4\ud835\uddb5\ud835\uddb6\ud835\uddb7\ud835\uddb8\ud835\uddb9\u01c3\uff1f\u2019\u2019-'\n        )\n        return argument.translate(table)\n\n\nasync def update_names(bot: Bot):\n    \"\"\"\n    The background updater task that performs a channel name update daily.\n\n    Args:\n        bot (Bot):\n            The running bot instance, used for fetching data from the\n            website via the bot's `api_client`.\n    \"\"\"\n\n    while True:\n        # Since we truncate the compute timedelta to seconds, we add one second to ensure\n        # we go past midnight in the `seconds_to_sleep` set below.\n        today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)\n        next_midnight = today_at_midnight + timedelta(days=1)\n        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds + 1\n        await asyncio.sleep(seconds_to_sleep)\n\n        channel_0_name, channel_1_name, channel_2_name = await bot.api_client.get(\n            'bot/off-topic-channel-names', params={'random_items': 3}\n        )\n        channel_0, channel_1, channel_2 = (bot.get_channel(channel_id) for channel_id in CHANNELS)\n\n        await channel_0.edit(name=f'ot0-{channel_0_name}')\n        await channel_1.edit(name=f'ot1-{channel_1_name}')\n        await channel_2.edit(name=f'ot2-{channel_2_name}')\n        log.debug(\n            \"Updated off-topic channel names to\"\n            f\" {channel_0_name}, {channel_1_name} and {channel_2_name}\"\n        )\n\n\nclass OffTopicNames(Cog):\n    \"\"\"Commands related to managing the off-topic category channel names.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.updater_task = None\n\n    def cog_unload(self):\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    @Cog.listener()\n    async def on_ready(self):\n        if self.updater_task is None:\n            coro = update_names(self.bot)\n            self.updater_task = self.bot.loop.create_task(coro)\n\n    @group(name='otname', aliases=('otnames', 'otn'), invoke_without_command=True)\n    @with_role(*MODERATION_ROLES)\n    async def otname_group(self, ctx):\n        \"\"\"Add or list items from the off-topic channel name rotation.\"\"\"\n\n        await ctx.invoke(self.bot.get_command(\"help\"), \"otname\")\n\n    @otname_group.command(name='add', aliases=('a',))\n    @with_role(*MODERATION_ROLES)\n    async def add_command(self, ctx, *names: OffTopicName):\n        \"\"\"Adds a new off-topic name to the rotation.\"\"\"\n        # Chain multiple words to a single one\n        name = \"-\".join(names)\n\n        await self.bot.api_client.post(f'bot/off-topic-channel-names', params={'name': name})\n        log.info(\n            f\"{ctx.author.name}#{ctx.author.discriminator}\"\n            f\" added the off-topic channel name '{name}\"\n        )\n        await ctx.send(f\":ok_hand: Added `{name}` to the names list.\")\n\n    @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))\n    @with_role(*MODERATION_ROLES)\n    async def delete_command(self, ctx, *names: OffTopicName):\n        \"\"\"Removes a off-topic name from the rotation.\"\"\"\n        # Chain multiple words to a single one\n        name = \"-\".join(names)\n\n        await self.bot.api_client.delete(f'bot/off-topic-channel-names/{name}')\n        log.info(\n            f\"{ctx.author.name}#{ctx.author.discriminator}\"\n            f\" deleted the off-topic channel name '{name}\"\n        )\n        await ctx.send(f\":ok_hand: Removed `{name}` from the names list.\")\n\n    @otname_group.command(name='list', aliases=('l',))\n    @with_role(*MODERATION_ROLES)\n    async def list_command(self, ctx):\n        \"\"\"\n        Lists all currently known off-topic channel names in a paginator.\n        Restricted to Moderator and above to not spoil the surprise.\n        \"\"\"\n\n        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n        lines = sorted(f\"\u2022 {name}\" for name in result)\n        embed = Embed(\n            title=f\"Known off-topic names (`{len(result)}` total)\",\n            colour=Colour.blue()\n        )\n        if result:\n            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\n        else:\n            embed.description = \"Hmmm, seems like there's nothing here yet.\"\n            await ctx.send(embed=embed)\n\n\ndef setup(bot: Bot):\n    bot.add_cog(OffTopicNames(bot))\n    log.info(\"Cog loaded: OffTopicNames\")\n", "path": "bot/cogs/off_topic_names.py"}], "after_files": [{"content": "import asyncio\nimport difflib\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom discord import Colour, Embed\nfrom discord.ext.commands import BadArgument, Bot, Cog, Context, Converter, group\n\nfrom bot.constants import Channels, MODERATION_ROLES\nfrom bot.decorators import with_role\nfrom bot.pagination import LinePaginator\n\n\nCHANNELS = (Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2)\nlog = logging.getLogger(__name__)\n\n\nclass OffTopicName(Converter):\n    \"\"\"A converter that ensures an added off-topic name is valid.\"\"\"\n\n    @staticmethod\n    async def convert(ctx: Context, argument: str):\n        allowed_characters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-\"\n\n        if not (2 <= len(argument) <= 96):\n            raise BadArgument(\"Channel name must be between 2 and 96 chars long\")\n\n        elif not all(c.isalnum() or c in allowed_characters for c in argument):\n            raise BadArgument(\n                \"Channel name must only consist of \"\n                \"alphanumeric characters, minus signs or apostrophes.\"\n            )\n\n        # Replace invalid characters with unicode alternatives.\n        table = str.maketrans(\n            allowed_characters, '\ud835\udda0\ud835\udda1\ud835\udda2\ud835\udda3\ud835\udda4\ud835\udda5\ud835\udda6\ud835\udda7\ud835\udda8\ud835\udda9\ud835\uddaa\ud835\uddab\ud835\uddac\ud835\uddad\ud835\uddae\ud835\uddaf\ud835\uddb0\ud835\uddb1\ud835\uddb2\ud835\uddb3\ud835\uddb4\ud835\uddb5\ud835\uddb6\ud835\uddb7\ud835\uddb8\ud835\uddb9\u01c3\uff1f\u2019\u2019-'\n        )\n        return argument.translate(table)\n\n\nasync def update_names(bot: Bot):\n    \"\"\"\n    The background updater task that performs a channel name update daily.\n\n    Args:\n        bot (Bot):\n            The running bot instance, used for fetching data from the\n            website via the bot's `api_client`.\n    \"\"\"\n\n    while True:\n        # Since we truncate the compute timedelta to seconds, we add one second to ensure\n        # we go past midnight in the `seconds_to_sleep` set below.\n        today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)\n        next_midnight = today_at_midnight + timedelta(days=1)\n        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds + 1\n        await asyncio.sleep(seconds_to_sleep)\n\n        channel_0_name, channel_1_name, channel_2_name = await bot.api_client.get(\n            'bot/off-topic-channel-names', params={'random_items': 3}\n        )\n        channel_0, channel_1, channel_2 = (bot.get_channel(channel_id) for channel_id in CHANNELS)\n\n        await channel_0.edit(name=f'ot0-{channel_0_name}')\n        await channel_1.edit(name=f'ot1-{channel_1_name}')\n        await channel_2.edit(name=f'ot2-{channel_2_name}')\n        log.debug(\n            \"Updated off-topic channel names to\"\n            f\" {channel_0_name}, {channel_1_name} and {channel_2_name}\"\n        )\n\n\nclass OffTopicNames(Cog):\n    \"\"\"Commands related to managing the off-topic category channel names.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.updater_task = None\n\n    def cog_unload(self):\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    @Cog.listener()\n    async def on_ready(self):\n        if self.updater_task is None:\n            coro = update_names(self.bot)\n            self.updater_task = self.bot.loop.create_task(coro)\n\n    @group(name='otname', aliases=('otnames', 'otn'), invoke_without_command=True)\n    @with_role(*MODERATION_ROLES)\n    async def otname_group(self, ctx):\n        \"\"\"Add or list items from the off-topic channel name rotation.\"\"\"\n\n        await ctx.invoke(self.bot.get_command(\"help\"), \"otname\")\n\n    @otname_group.command(name='add', aliases=('a',))\n    @with_role(*MODERATION_ROLES)\n    async def add_command(self, ctx, *names: OffTopicName):\n        \"\"\"Adds a new off-topic name to the rotation.\"\"\"\n        # Chain multiple words to a single one\n        name = \"-\".join(names)\n\n        await self.bot.api_client.post(f'bot/off-topic-channel-names', params={'name': name})\n        log.info(\n            f\"{ctx.author.name}#{ctx.author.discriminator}\"\n            f\" added the off-topic channel name '{name}\"\n        )\n        await ctx.send(f\":ok_hand: Added `{name}` to the names list.\")\n\n    @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))\n    @with_role(*MODERATION_ROLES)\n    async def delete_command(self, ctx, *names: OffTopicName):\n        \"\"\"Removes a off-topic name from the rotation.\"\"\"\n        # Chain multiple words to a single one\n        name = \"-\".join(names)\n\n        await self.bot.api_client.delete(f'bot/off-topic-channel-names/{name}')\n        log.info(\n            f\"{ctx.author.name}#{ctx.author.discriminator}\"\n            f\" deleted the off-topic channel name '{name}\"\n        )\n        await ctx.send(f\":ok_hand: Removed `{name}` from the names list.\")\n\n    @otname_group.command(name='list', aliases=('l',))\n    @with_role(*MODERATION_ROLES)\n    async def list_command(self, ctx):\n        \"\"\"\n        Lists all currently known off-topic channel names in a paginator.\n        Restricted to Moderator and above to not spoil the surprise.\n        \"\"\"\n\n        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n        lines = sorted(f\"\u2022 {name}\" for name in result)\n        embed = Embed(\n            title=f\"Known off-topic names (`{len(result)}` total)\",\n            colour=Colour.blue()\n        )\n        if result:\n            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\n        else:\n            embed.description = \"Hmmm, seems like there's nothing here yet.\"\n            await ctx.send(embed=embed)\n\n    @otname_group.command(name='search', aliases=('s',))\n    @with_role(*MODERATION_ROLES)\n    async def search_command(self, ctx, *, query: str):\n        \"\"\"\n        Search for an off-topic name.\n        \"\"\"\n\n        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n        matches = difflib.get_close_matches(query, result, n=10, cutoff=0.35)\n        lines = sorted(f\"\u2022 {name}\" for name in matches)\n        embed = Embed(\n            title=f\"Query results\",\n            colour=Colour.blue()\n        )\n\n        if matches:\n            await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\n        else:\n            embed.description = \"Nothing found.\"\n            await ctx.send(embed=embed)\n\n\ndef setup(bot: Bot):\n    bot.add_cog(OffTopicNames(bot))\n    log.info(\"Cog loaded: OffTopicNames\")\n", "path": "bot/cogs/off_topic_names.py"}]}