We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bot changing off-topic channel names more than once per daily cycle
The bot should change the channel names of the three off-topic channels only once after hitting UTC midnight. However, we've noticed that it may attempt to set new channel names far more often than that. The root of the cause is still unknown, but after looking at the recent audit log screenshotted by Scragly, I've come up with a conjecture to what's happening.

If you take a look at the logs below, then you'll notice that most of the channel name changes actually happen just before the whole hour (9:59 in Scragly's local time). My guess is that the sleep duration, which is set based on "the start of the current day" + "1 day time delta", is off by one second. The odd thing is that it's not obvious to me from the code why this happens.

However, if this is true, then it would explain the current behavior: The background task is triggered at 23:59:59 (UTC), it will cycle the channel names, and, **as we're still on the same day**, calculate a sleep second delta based on the previous midnight (which will be 0, since it's trying to find the seconds to 23:59:59, which it already is), so it will async.sleep for 0 seconds and run itself again.

The fact that it doesn't trigger more often is then caused because it needs to make API calls and has other `await` points in the execution. 

Since this behavior very much looks like what Discord would call "API Abuse", I think it's important to put in a bug fix as quickly as possible. Since the only thing I can think of is the off-by-one error described above, my proposal is aim for 1 minute past midnight, so we never run into "we're still on the same day" issues again:

```py
        today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)
        next_midnight = today_at_midnight + timedelta(days=1, minutes=1)
        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds
        await asyncio.sleep(seconds_to_sleep)
```

If that doesn't fix it, we need to investigate further, but I think this will work.

---

**Edit: some additional confirmation of the off-by-one-second error:**
```
Apr 15 08:08:35 pd.beardfist.com Bot: |       bot.cogs.off_topic_names |    DEBUG | update_names: seconds to sleep 24
```

I'd set the name change for `08:09:00`, but sleeping for 24 seconds at `08:08:35` will cause it to "wake up" at `08:08:59`.

---

![Audit Log Screenshot showing channel name changes](https://cdn.discordapp.com/attachments/365960823622991872/567137248630407194/unknown.png)





--- 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, Context, Converter, group
7 
8 from bot.constants import Channels, Keys, MODERATION_ROLES, URLs
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 = ("-", "’", "'", "`")
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         elif not argument.islower():
34             raise BadArgument("Channel name must be lowercase")
35 
36         # Replace some unusable apostrophe-like characters with "’".
37         return argument.replace("'", "’").replace("`", "’")
38 
39 
40 async def update_names(bot: Bot, headers: dict):
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 `http_session`.
48     """
49 
50     while True:
51         today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)
52         next_midnight = today_at_midnight + timedelta(days=1)
53         seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds
54         await asyncio.sleep(seconds_to_sleep)
55 
56         response = await bot.http_session.get(
57             f'{URLs.site_off_topic_names_api}?random_items=3',
58             headers=headers
59         )
60         channel_0_name, channel_1_name, channel_2_name = await response.json()
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:
73     """Commands related to managing the off-topic category channel names."""
74 
75     def __init__(self, bot: Bot):
76         self.bot = bot
77         self.headers = {"X-API-KEY": Keys.site_api}
78         self.updater_task = None
79 
80     def __cleanup(self):
81         if self.updater_task is not None:
82             self.updater_task.cancel()
83 
84     async def on_ready(self):
85         if self.updater_task is None:
86             coro = update_names(self.bot, self.headers)
87             self.updater_task = await 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, name: OffTopicName):
99         """Adds a new off-topic name to the rotation."""
100 
101         result = await self.bot.http_session.post(
102             URLs.site_off_topic_names_api,
103             headers=self.headers,
104             params={'name': name}
105         )
106 
107         response = await result.json()
108 
109         if result.status == 200:
110             log.info(
111                 f"{ctx.author.name}#{ctx.author.discriminator}"
112                 f" added the off-topic channel name '{name}"
113             )
114             await ctx.send(":ok_hand:")
115         else:
116             error_reason = response.get('message', "No reason provided.")
117             await ctx.send(f":warning: got non-200 from the API: {error_reason}")
118 
119     @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))
120     @with_role(*MODERATION_ROLES)
121     async def delete_command(self, ctx, name: OffTopicName):
122         """Removes a off-topic name from the rotation."""
123 
124         result = await self.bot.http_session.delete(
125             URLs.site_off_topic_names_api,
126             headers=self.headers,
127             params={'name': name}
128         )
129 
130         response = await result.json()
131 
132         if result.status == 200:
133             if response['deleted'] == 0:
134                 await ctx.send(f":warning: No name matching `{name}` was found in the database.")
135             else:
136                 log.info(
137                     f"{ctx.author.name}#{ctx.author.discriminator}"
138                     f" deleted the off-topic channel name '{name}"
139                 )
140                 await ctx.send(":ok_hand:")
141         else:
142             error_reason = response.get('message', "No reason provided.")
143             await ctx.send(f":warning: got non-200 from the API: {error_reason}")
144 
145     @otname_group.command(name='list', aliases=('l',))
146     @with_role(*MODERATION_ROLES)
147     async def list_command(self, ctx):
148         """
149         Lists all currently known off-topic channel names in a paginator.
150         Restricted to Moderator and above to not spoil the surprise.
151         """
152 
153         result = await self.bot.http_session.get(
154             URLs.site_off_topic_names_api,
155             headers=self.headers
156         )
157         response = await result.json()
158         lines = sorted(f"• {name}" for name in response)
159 
160         embed = Embed(
161             title=f"Known off-topic names (`{len(response)}` total)",
162             colour=Colour.blue()
163         )
164         await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)
165 
166 
167 def setup(bot: Bot):
168     bot.add_cog(OffTopicNames(bot))
169     log.info("Cog loaded: OffTopicNames")
170
```
--- 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
@@ -48,9 +48,11 @@
     """
 
     while True:
+        # Since we truncate the compute timedelta to seconds, we add one second to ensure
+        # we go past midnight in the `seconds_to_sleep` set below.
         today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)
         next_midnight = today_at_midnight + timedelta(days=1)
-        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds
+        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds + 1
         await asyncio.sleep(seconds_to_sleep)
 
         response = await bot.http_session.get(

{"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@@ -48,9 +48,11 @@\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\n+        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds + 1\n         await asyncio.sleep(seconds_to_sleep)\n \n         response = await bot.http_session.get(\n", "issue": "Bot changing off-topic channel names more than once per daily cycle\nThe bot should change the channel names of the three off-topic channels only once after hitting UTC midnight. However, we've noticed that it may attempt to set new channel names far more often than that. The root of the cause is still unknown, but after looking at the recent audit log screenshotted by Scragly, I've come up with a conjecture to what's happening.\r\n\r\nIf you take a look at the logs below, then you'll notice that most of the channel name changes actually happen just before the whole hour (9:59 in Scragly's local time). My guess is that the sleep duration, which is set based on \"the start of the current day\" + \"1 day time delta\", is off by one second. The odd thing is that it's not obvious to me from the code why this happens.\r\n\r\nHowever, if this is true, then it would explain the current behavior: The background task is triggered at 23:59:59 (UTC), it will cycle the channel names, and, **as we're still on the same day**, calculate a sleep second delta based on the previous midnight (which will be 0, since it's trying to find the seconds to 23:59:59, which it already is), so it will async.sleep for 0 seconds and run itself again.\r\n\r\nThe fact that it doesn't trigger more often is then caused because it needs to make API calls and has other `await` points in the execution. \r\n\r\nSince this behavior very much looks like what Discord would call \"API Abuse\", I think it's important to put in a bug fix as quickly as possible. Since the only thing I can think of is the off-by-one error described above, my proposal is aim for 1 minute past midnight, so we never run into \"we're still on the same day\" issues again:\r\n\r\n```py\r\n        today_at_midnight = datetime.utcnow().replace(microsecond=0, second=0, minute=0, hour=0)\r\n        next_midnight = today_at_midnight + timedelta(days=1, minutes=1)\r\n        seconds_to_sleep = (next_midnight - datetime.utcnow()).seconds\r\n        await asyncio.sleep(seconds_to_sleep)\r\n```\r\n\r\nIf that doesn't fix it, we need to investigate further, but I think this will work.\r\n\r\n---\r\n\r\n**Edit: some additional confirmation of the off-by-one-second error:**\r\n```\r\nApr 15 08:08:35 pd.beardfist.com Bot: |       bot.cogs.off_topic_names |    DEBUG | update_names: seconds to sleep 24\r\n```\r\n\r\nI'd set the name change for `08:09:00`, but sleeping for 24 seconds at `08:08:35` will cause it to \"wake up\" at `08:08:59`.\r\n\r\n---\r\n\r\n![Audit Log Screenshot showing channel name changes](https://cdn.discordapp.com/attachments/365960823622991872/567137248630407194/unknown.png)\r\n\r\n\r\n\r\n\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, Context, Converter, group\n\nfrom bot.constants import Channels, Keys, MODERATION_ROLES, URLs\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 = (\"-\", \"\u2019\", \"'\", \"`\")\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        elif not argument.islower():\n            raise BadArgument(\"Channel name must be lowercase\")\n\n        # Replace some unusable apostrophe-like characters with \"\u2019\".\n        return argument.replace(\"'\", \"\u2019\").replace(\"`\", \"\u2019\")\n\n\nasync def update_names(bot: Bot, headers: dict):\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 `http_session`.\n    \"\"\"\n\n    while True:\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\n        await asyncio.sleep(seconds_to_sleep)\n\n        response = await bot.http_session.get(\n            f'{URLs.site_off_topic_names_api}?random_items=3',\n            headers=headers\n        )\n        channel_0_name, channel_1_name, channel_2_name = await response.json()\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:\n    \"\"\"Commands related to managing the off-topic category channel names.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.headers = {\"X-API-KEY\": Keys.site_api}\n        self.updater_task = None\n\n    def __cleanup(self):\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    async def on_ready(self):\n        if self.updater_task is None:\n            coro = update_names(self.bot, self.headers)\n            self.updater_task = await 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, name: OffTopicName):\n        \"\"\"Adds a new off-topic name to the rotation.\"\"\"\n\n        result = await self.bot.http_session.post(\n            URLs.site_off_topic_names_api,\n            headers=self.headers,\n            params={'name': name}\n        )\n\n        response = await result.json()\n\n        if result.status == 200:\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(\":ok_hand:\")\n        else:\n            error_reason = response.get('message', \"No reason provided.\")\n            await ctx.send(f\":warning: got non-200 from the API: {error_reason}\")\n\n    @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))\n    @with_role(*MODERATION_ROLES)\n    async def delete_command(self, ctx, name: OffTopicName):\n        \"\"\"Removes a off-topic name from the rotation.\"\"\"\n\n        result = await self.bot.http_session.delete(\n            URLs.site_off_topic_names_api,\n            headers=self.headers,\n            params={'name': name}\n        )\n\n        response = await result.json()\n\n        if result.status == 200:\n            if response['deleted'] == 0:\n                await ctx.send(f\":warning: No name matching `{name}` was found in the database.\")\n            else:\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(\":ok_hand:\")\n        else:\n            error_reason = response.get('message', \"No reason provided.\")\n            await ctx.send(f\":warning: got non-200 from the API: {error_reason}\")\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.http_session.get(\n            URLs.site_off_topic_names_api,\n            headers=self.headers\n        )\n        response = await result.json()\n        lines = sorted(f\"\u2022 {name}\" for name in response)\n\n        embed = Embed(\n            title=f\"Known off-topic names (`{len(response)}` total)\",\n            colour=Colour.blue()\n        )\n        await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\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 logging\nfrom datetime import datetime, timedelta\n\nfrom discord import Colour, Embed\nfrom discord.ext.commands import BadArgument, Bot, Context, Converter, group\n\nfrom bot.constants import Channels, Keys, MODERATION_ROLES, URLs\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 = (\"-\", \"\u2019\", \"'\", \"`\")\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        elif not argument.islower():\n            raise BadArgument(\"Channel name must be lowercase\")\n\n        # Replace some unusable apostrophe-like characters with \"\u2019\".\n        return argument.replace(\"'\", \"\u2019\").replace(\"`\", \"\u2019\")\n\n\nasync def update_names(bot: Bot, headers: dict):\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 `http_session`.\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        response = await bot.http_session.get(\n            f'{URLs.site_off_topic_names_api}?random_items=3',\n            headers=headers\n        )\n        channel_0_name, channel_1_name, channel_2_name = await response.json()\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:\n    \"\"\"Commands related to managing the off-topic category channel names.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.headers = {\"X-API-KEY\": Keys.site_api}\n        self.updater_task = None\n\n    def __cleanup(self):\n        if self.updater_task is not None:\n            self.updater_task.cancel()\n\n    async def on_ready(self):\n        if self.updater_task is None:\n            coro = update_names(self.bot, self.headers)\n            self.updater_task = await 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, name: OffTopicName):\n        \"\"\"Adds a new off-topic name to the rotation.\"\"\"\n\n        result = await self.bot.http_session.post(\n            URLs.site_off_topic_names_api,\n            headers=self.headers,\n            params={'name': name}\n        )\n\n        response = await result.json()\n\n        if result.status == 200:\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(\":ok_hand:\")\n        else:\n            error_reason = response.get('message', \"No reason provided.\")\n            await ctx.send(f\":warning: got non-200 from the API: {error_reason}\")\n\n    @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd'))\n    @with_role(*MODERATION_ROLES)\n    async def delete_command(self, ctx, name: OffTopicName):\n        \"\"\"Removes a off-topic name from the rotation.\"\"\"\n\n        result = await self.bot.http_session.delete(\n            URLs.site_off_topic_names_api,\n            headers=self.headers,\n            params={'name': name}\n        )\n\n        response = await result.json()\n\n        if result.status == 200:\n            if response['deleted'] == 0:\n                await ctx.send(f\":warning: No name matching `{name}` was found in the database.\")\n            else:\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(\":ok_hand:\")\n        else:\n            error_reason = response.get('message', \"No reason provided.\")\n            await ctx.send(f\":warning: got non-200 from the API: {error_reason}\")\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.http_session.get(\n            URLs.site_off_topic_names_api,\n            headers=self.headers\n        )\n        response = await result.json()\n        lines = sorted(f\"\u2022 {name}\" for name in response)\n\n        embed = Embed(\n            title=f\"Known off-topic names (`{len(response)}` total)\",\n            colour=Colour.blue()\n        )\n        await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)\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"}]}