We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
The off-topic channel name updating task fails on non-success API response
The background task we create to update off-topic names at midnight UTC fails if it receives a non-success API response. The reason is that our `bot.api_client` will raise the `bot.api.ResponseCodeError` exception on non-success response status codes. This means that the off-topic channel names won't be updated again until either the bot is restarted or the task is started manually again by an admin.

The relevant lines of code:
https://github.com/python-discord/bot/blob/e70c96248bd7b548412811a4f1ffe88bed41f815/bot/cogs/off_topic_names.py#L59-L61

To handle it, we could simply include a `try-except` block and log the exception in the `except` block. I'm not sure if we want to log the entire exception, since the exception text could be a [massive HTML-response generated by cloudflare](https://paste.pythondiscord.com/ohibicedif). Logging the failure with the response code should generally give us enough to determine the cause of the failure.

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

{"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@@ -6,6 +6,7 @@\n from discord import Colour, Embed\n from discord.ext.commands import BadArgument, Bot, Cog, Context, Converter, group\n \n+from bot.api import ResponseCodeError\n from bot.constants import Channels, MODERATION_ROLES\n from bot.decorators import with_role\n from bot.pagination import LinePaginator\n@@ -49,9 +50,13 @@\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+        try:\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+        except ResponseCodeError as e:\n+            log.error(f\"Failed to get new off topic channel names: code {e.response.status}\")\n+            continue\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", "issue": "The off-topic channel name updating task fails on non-success API response\nThe background task we create to update off-topic names at midnight UTC fails if it receives a non-success API response. The reason is that our `bot.api_client` will raise the `bot.api.ResponseCodeError` exception on non-success response status codes. This means that the off-topic channel names won't be updated again until either the bot is restarted or the task is started manually again by an admin.\r\n\r\nThe relevant lines of code:\r\nhttps://github.com/python-discord/bot/blob/e70c96248bd7b548412811a4f1ffe88bed41f815/bot/cogs/off_topic_names.py#L59-L61\r\n\r\nTo handle it, we could simply include a `try-except` block and log the exception in the `except` block. I'm not sure if we want to log the entire exception, since the exception text could be a [massive HTML-response generated by cloudflare](https://paste.pythondiscord.com/ohibicedif). Logging the failure with the response code should generally give us enough to determine the cause of the failure.\n", "before_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) -> str:\n        \"\"\"Attempt to replace any invalid characters with their approximate Unicode equivalent.\"\"\"\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) -> None:\n    \"\"\"Background updater task that performs the daily channel name update.\"\"\"\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) -> None:\n        \"\"\"Cancel any running updater tasks on cog unload.\"\"\"\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    @Cog.listener()\n    async def on_ready(self) -> None:\n        \"\"\"Start off-topic channel updating event loop if it hasn't already started.\"\"\"\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: Context) -> None:\n        \"\"\"Add or list items from the off-topic channel name rotation.\"\"\"\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: Context, *names: OffTopicName) -> None:\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: Context, *names: OffTopicName) -> None:\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: Context) -> None:\n        \"\"\"\n        Lists all currently known off-topic channel names in a paginator.\n\n        Restricted to Moderator and above to not spoil the surprise.\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: Context, *, query: OffTopicName) -> None:\n        \"\"\"Search for an off-topic name.\"\"\"\n        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n        in_matches = {name for name in result if query in name}\n        close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70)\n        lines = sorted(f\"\u2022 {name}\" for name in in_matches.union(close_matches))\n        embed = Embed(\n            title=f\"Query results\",\n            colour=Colour.blue()\n        )\n\n        if lines:\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) -> None:\n    \"\"\"Off topic names cog load.\"\"\"\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.api import ResponseCodeError\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) -> str:\n        \"\"\"Attempt to replace any invalid characters with their approximate Unicode equivalent.\"\"\"\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) -> None:\n    \"\"\"Background updater task that performs the daily channel name update.\"\"\"\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        try:\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        except ResponseCodeError as e:\n            log.error(f\"Failed to get new off topic channel names: code {e.response.status}\")\n            continue\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) -> None:\n        \"\"\"Cancel any running updater tasks on cog unload.\"\"\"\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    @Cog.listener()\n    async def on_ready(self) -> None:\n        \"\"\"Start off-topic channel updating event loop if it hasn't already started.\"\"\"\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: Context) -> None:\n        \"\"\"Add or list items from the off-topic channel name rotation.\"\"\"\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: Context, *names: OffTopicName) -> None:\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: Context, *names: OffTopicName) -> None:\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: Context) -> None:\n        \"\"\"\n        Lists all currently known off-topic channel names in a paginator.\n\n        Restricted to Moderator and above to not spoil the surprise.\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: Context, *, query: OffTopicName) -> None:\n        \"\"\"Search for an off-topic name.\"\"\"\n        result = await self.bot.api_client.get('bot/off-topic-channel-names')\n        in_matches = {name for name in result if query in name}\n        close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70)\n        lines = sorted(f\"\u2022 {name}\" for name in in_matches.union(close_matches))\n        embed = Embed(\n            title=f\"Query results\",\n            colour=Colour.blue()\n        )\n\n        if lines:\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) -> None:\n    \"\"\"Off topic names cog load.\"\"\"\n    bot.add_cog(OffTopicNames(bot))\n    log.info(\"Cog loaded: OffTopicNames\")\n", "path": "bot/cogs/off_topic_names.py"}]}