We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Mods+ should be permitted to use any command in #checkpoint
Currently the bot will ignore all commands inside #checkpoint. However, there are cases where this is not ideal, for example if we want to clean the channel up with the !clean command.

Write a fix that allows moderators, admins and owners to use any command in this channel, but still ignores everything but `!accept` from anyone else.
Make the bot delete any message it posts in #checkpoint after a while
Currently, messages posted in #checkpoint by the bot will usually be posted with a `delete_after` of 10 seconds or so, so they will automatically be removed. This helps the channel stay clean so that the important messages at the top of it will stay there. The bot also automatically deletes any messages posted in this channel by other users.

However, when the bot posts messages in this channel that are not related to the Verification cog, they will never get deleted. For example, if a user in this channel triggers the spam filter, the bot will post a message about muting the user which will never be deleted.

We want this channel to stay clean, so let's implement some logics that ensure that **any message posted by the bot** in this channel will be deleted after a short time, like 10 seconds.

--- 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/verification.py`
Content:
```
1 import logging
2 from datetime import datetime
3 
4 from discord import Colour, Message, NotFound, Object
5 from discord.ext import tasks
6 from discord.ext.commands import Cog, Context, command
7 
8 from bot.bot import Bot
9 from bot.cogs.moderation import ModLog
10 from bot.constants import (
11     Bot as BotConfig,
12     Channels, Colours, Event,
13     Filter, Icons, Roles
14 )
15 from bot.decorators import InChannelCheckFailure, in_channel, without_role
16 
17 log = logging.getLogger(__name__)
18 
19 WELCOME_MESSAGE = f"""
20 Hello! Welcome to the server, and thanks for verifying yourself!
21 
22 For your records, these are the documents you accepted:
23 
24 `1)` Our rules, here: <https://pythondiscord.com/pages/rules>
25 `2)` Our privacy policy, here: <https://pythondiscord.com/pages/privacy> - you can find information on how to have \
26 your information removed here as well.
27 
28 Feel free to review them at any point!
29 
30 Additionally, if you'd like to receive notifications for the announcements we post in <#{Channels.announcements}> \
31 from time to time, you can send `!subscribe` to <#{Channels.bot}> at any time to assign yourself the \
32 **Announcements** role. We'll mention this role every time we make an announcement.
33 
34 If you'd like to unsubscribe from the announcement notifications, simply send `!unsubscribe` to <#{Channels.bot}>.
35 """
36 
37 PERIODIC_PING = (
38     f"@everyone To verify that you have read our rules, please type `{BotConfig.prefix}accept`."
39     f" If you encounter any problems during the verification process, ping the <@&{Roles.admin}> role in this channel."
40 )
41 
42 
43 class Verification(Cog):
44     """User verification and role self-management."""
45 
46     def __init__(self, bot: Bot):
47         self.bot = bot
48         self.periodic_ping.start()
49 
50     @property
51     def mod_log(self) -> ModLog:
52         """Get currently loaded ModLog cog instance."""
53         return self.bot.get_cog("ModLog")
54 
55     @Cog.listener()
56     async def on_message(self, message: Message) -> None:
57         """Check new message event for messages to the checkpoint channel & process."""
58         if message.author.bot:
59             return  # They're a bot, ignore
60 
61         if message.channel.id != Channels.verification:
62             return  # Only listen for #checkpoint messages
63 
64         # if a user mentions a role or guild member
65         # alert the mods in mod-alerts channel
66         if message.mentions or message.role_mentions:
67             log.debug(
68                 f"{message.author} mentioned one or more users "
69                 f"and/or roles in {message.channel.name}"
70             )
71 
72             embed_text = (
73                 f"{message.author.mention} sent a message in "
74                 f"{message.channel.mention} that contained user and/or role mentions."
75                 f"\n\n**Original message:**\n>>> {message.content}"
76             )
77 
78             # Send pretty mod log embed to mod-alerts
79             await self.mod_log.send_log_message(
80                 icon_url=Icons.filtering,
81                 colour=Colour(Colours.soft_red),
82                 title=f"User/Role mentioned in {message.channel.name}",
83                 text=embed_text,
84                 thumbnail=message.author.avatar_url_as(static_format="png"),
85                 channel_id=Channels.mod_alerts,
86                 ping_everyone=Filter.ping_everyone,
87             )
88 
89         ctx = await self.bot.get_context(message)  # type: Context
90 
91         if ctx.command is not None and ctx.command.name == "accept":
92             return  # They used the accept command
93 
94         for role in ctx.author.roles:
95             if role.id == Roles.verified:
96                 log.warning(f"{ctx.author} posted '{ctx.message.content}' "
97                             "in the verification channel, but is already verified.")
98                 return  # They're already verified
99 
100         log.debug(f"{ctx.author} posted '{ctx.message.content}' in the verification "
101                   "channel. We are providing instructions how to verify.")
102         await ctx.send(
103             f"{ctx.author.mention} Please type `!accept` to verify that you accept our rules, "
104             f"and gain access to the rest of the server.",
105             delete_after=20
106         )
107 
108         log.trace(f"Deleting the message posted by {ctx.author}")
109 
110         try:
111             await ctx.message.delete()
112         except NotFound:
113             log.trace("No message found, it must have been deleted by another bot.")
114 
115     @command(name='accept', aliases=('verify', 'verified', 'accepted'), hidden=True)
116     @without_role(Roles.verified)
117     @in_channel(Channels.verification)
118     async def accept_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args
119         """Accept our rules and gain access to the rest of the server."""
120         log.debug(f"{ctx.author} called !accept. Assigning the 'Developer' role.")
121         await ctx.author.add_roles(Object(Roles.verified), reason="Accepted the rules")
122         try:
123             await ctx.author.send(WELCOME_MESSAGE)
124         except Exception:
125             # Catch the exception, in case they have DMs off or something
126             log.exception(f"Unable to send welcome message to user {ctx.author}.")
127 
128         log.trace(f"Deleting the message posted by {ctx.author}.")
129 
130         try:
131             self.mod_log.ignore(Event.message_delete, ctx.message.id)
132             await ctx.message.delete()
133         except NotFound:
134             log.trace("No message found, it must have been deleted by another bot.")
135 
136     @command(name='subscribe')
137     @in_channel(Channels.bot)
138     async def subscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args
139         """Subscribe to announcement notifications by assigning yourself the role."""
140         has_role = False
141 
142         for role in ctx.author.roles:
143             if role.id == Roles.announcements:
144                 has_role = True
145                 break
146 
147         if has_role:
148             await ctx.send(f"{ctx.author.mention} You're already subscribed!")
149             return
150 
151         log.debug(f"{ctx.author} called !subscribe. Assigning the 'Announcements' role.")
152         await ctx.author.add_roles(Object(Roles.announcements), reason="Subscribed to announcements")
153 
154         log.trace(f"Deleting the message posted by {ctx.author}.")
155 
156         await ctx.send(
157             f"{ctx.author.mention} Subscribed to <#{Channels.announcements}> notifications.",
158         )
159 
160     @command(name='unsubscribe')
161     @in_channel(Channels.bot)
162     async def unsubscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args
163         """Unsubscribe from announcement notifications by removing the role from yourself."""
164         has_role = False
165 
166         for role in ctx.author.roles:
167             if role.id == Roles.announcements:
168                 has_role = True
169                 break
170 
171         if not has_role:
172             await ctx.send(f"{ctx.author.mention} You're already unsubscribed!")
173             return
174 
175         log.debug(f"{ctx.author} called !unsubscribe. Removing the 'Announcements' role.")
176         await ctx.author.remove_roles(Object(Roles.announcements), reason="Unsubscribed from announcements")
177 
178         log.trace(f"Deleting the message posted by {ctx.author}.")
179 
180         await ctx.send(
181             f"{ctx.author.mention} Unsubscribed from <#{Channels.announcements}> notifications."
182         )
183 
184     # This cannot be static (must have a __func__ attribute).
185     async def cog_command_error(self, ctx: Context, error: Exception) -> None:
186         """Check for & ignore any InChannelCheckFailure."""
187         if isinstance(error, InChannelCheckFailure):
188             error.handled = True
189 
190     @staticmethod
191     def bot_check(ctx: Context) -> bool:
192         """Block any command within the verification channel that is not !accept."""
193         if ctx.channel.id == Channels.verification:
194             return ctx.command.name == "accept"
195         else:
196             return True
197 
198     @tasks.loop(hours=12)
199     async def periodic_ping(self) -> None:
200         """Every week, mention @everyone to remind them to verify."""
201         messages = self.bot.get_channel(Channels.verification).history(limit=10)
202         need_to_post = True  # True if a new message needs to be sent.
203 
204         async for message in messages:
205             if message.author == self.bot.user and message.content == PERIODIC_PING:
206                 delta = datetime.utcnow() - message.created_at  # Time since last message.
207                 if delta.days >= 7:  # Message is older than a week.
208                     await message.delete()
209                 else:
210                     need_to_post = False
211 
212                 break
213 
214         if need_to_post:
215             await self.bot.get_channel(Channels.verification).send(PERIODIC_PING)
216 
217     @periodic_ping.before_loop
218     async def before_ping(self) -> None:
219         """Only start the loop when the bot is ready."""
220         await self.bot.wait_until_ready()
221 
222     def cog_unload(self) -> None:
223         """Cancel the periodic ping task when the cog is unloaded."""
224         self.periodic_ping.cancel()
225 
226 
227 def setup(bot: Bot) -> None:
228     """Load the Verification cog."""
229     bot.add_cog(Verification(bot))
230
```
--- 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/verification.py b/bot/cogs/verification.py
--- a/bot/cogs/verification.py
+++ b/bot/cogs/verification.py
@@ -10,9 +10,10 @@
 from bot.constants import (
     Bot as BotConfig,
     Channels, Colours, Event,
-    Filter, Icons, Roles
+    Filter, Icons, MODERATION_ROLES, Roles
 )
 from bot.decorators import InChannelCheckFailure, in_channel, without_role
+from bot.utils.checks import without_role_check
 
 log = logging.getLogger(__name__)
 
@@ -38,6 +39,7 @@
     f"@everyone To verify that you have read our rules, please type `{BotConfig.prefix}accept`."
     f" If you encounter any problems during the verification process, ping the <@&{Roles.admin}> role in this channel."
 )
+BOT_MESSAGE_DELETE_DELAY = 10
 
 
 class Verification(Cog):
@@ -55,12 +57,16 @@
     @Cog.listener()
     async def on_message(self, message: Message) -> None:
         """Check new message event for messages to the checkpoint channel & process."""
-        if message.author.bot:
-            return  # They're a bot, ignore
-
         if message.channel.id != Channels.verification:
             return  # Only listen for #checkpoint messages
 
+        if message.author.bot:
+            # They're a bot, delete their message after the delay.
+            # But not the periodic ping; we like that one.
+            if message.content != PERIODIC_PING:
+                await message.delete(delay=BOT_MESSAGE_DELETE_DELAY)
+            return
+
         # if a user mentions a role or guild member
         # alert the mods in mod-alerts channel
         if message.mentions or message.role_mentions:
@@ -190,7 +196,7 @@
     @staticmethod
     def bot_check(ctx: Context) -> bool:
         """Block any command within the verification channel that is not !accept."""
-        if ctx.channel.id == Channels.verification:
+        if ctx.channel.id == Channels.verification and without_role_check(ctx, *MODERATION_ROLES):
             return ctx.command.name == "accept"
         else:
             return True

{"golden_diff": "diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py\n--- a/bot/cogs/verification.py\n+++ b/bot/cogs/verification.py\n@@ -10,9 +10,10 @@\n from bot.constants import (\n     Bot as BotConfig,\n     Channels, Colours, Event,\n-    Filter, Icons, Roles\n+    Filter, Icons, MODERATION_ROLES, Roles\n )\n from bot.decorators import InChannelCheckFailure, in_channel, without_role\n+from bot.utils.checks import without_role_check\n \n log = logging.getLogger(__name__)\n \n@@ -38,6 +39,7 @@\n     f\"@everyone To verify that you have read our rules, please type `{BotConfig.prefix}accept`.\"\n     f\" If you encounter any problems during the verification process, ping the <@&{Roles.admin}> role in this channel.\"\n )\n+BOT_MESSAGE_DELETE_DELAY = 10\n \n \n class Verification(Cog):\n@@ -55,12 +57,16 @@\n     @Cog.listener()\n     async def on_message(self, message: Message) -> None:\n         \"\"\"Check new message event for messages to the checkpoint channel & process.\"\"\"\n-        if message.author.bot:\n-            return  # They're a bot, ignore\n-\n         if message.channel.id != Channels.verification:\n             return  # Only listen for #checkpoint messages\n \n+        if message.author.bot:\n+            # They're a bot, delete their message after the delay.\n+            # But not the periodic ping; we like that one.\n+            if message.content != PERIODIC_PING:\n+                await message.delete(delay=BOT_MESSAGE_DELETE_DELAY)\n+            return\n+\n         # if a user mentions a role or guild member\n         # alert the mods in mod-alerts channel\n         if message.mentions or message.role_mentions:\n@@ -190,7 +196,7 @@\n     @staticmethod\n     def bot_check(ctx: Context) -> bool:\n         \"\"\"Block any command within the verification channel that is not !accept.\"\"\"\n-        if ctx.channel.id == Channels.verification:\n+        if ctx.channel.id == Channels.verification and without_role_check(ctx, *MODERATION_ROLES):\n             return ctx.command.name == \"accept\"\n         else:\n             return True\n", "issue": "Mods+ should be permitted to use any command in #checkpoint\nCurrently the bot will ignore all commands inside #checkpoint. However, there are cases where this is not ideal, for example if we want to clean the channel up with the !clean command.\r\n\r\nWrite a fix that allows moderators, admins and owners to use any command in this channel, but still ignores everything but `!accept` from anyone else.\nMake the bot delete any message it posts in #checkpoint after a while\nCurrently, messages posted in #checkpoint by the bot will usually be posted with a `delete_after` of 10 seconds or so, so they will automatically be removed. This helps the channel stay clean so that the important messages at the top of it will stay there. The bot also automatically deletes any messages posted in this channel by other users.\r\n\r\nHowever, when the bot posts messages in this channel that are not related to the Verification cog, they will never get deleted. For example, if a user in this channel triggers the spam filter, the bot will post a message about muting the user which will never be deleted.\r\n\r\nWe want this channel to stay clean, so let's implement some logics that ensure that **any message posted by the bot** in this channel will be deleted after a short time, like 10 seconds.\n", "before_files": [{"content": "import logging\nfrom datetime import datetime\n\nfrom discord import Colour, Message, NotFound, Object\nfrom discord.ext import tasks\nfrom discord.ext.commands import Cog, Context, command\n\nfrom bot.bot import Bot\nfrom bot.cogs.moderation import ModLog\nfrom bot.constants import (\n    Bot as BotConfig,\n    Channels, Colours, Event,\n    Filter, Icons, Roles\n)\nfrom bot.decorators import InChannelCheckFailure, in_channel, without_role\n\nlog = logging.getLogger(__name__)\n\nWELCOME_MESSAGE = f\"\"\"\nHello! Welcome to the server, and thanks for verifying yourself!\n\nFor your records, these are the documents you accepted:\n\n`1)` Our rules, here: <https://pythondiscord.com/pages/rules>\n`2)` Our privacy policy, here: <https://pythondiscord.com/pages/privacy> - you can find information on how to have \\\nyour information removed here as well.\n\nFeel free to review them at any point!\n\nAdditionally, if you'd like to receive notifications for the announcements we post in <#{Channels.announcements}> \\\nfrom time to time, you can send `!subscribe` to <#{Channels.bot}> at any time to assign yourself the \\\n**Announcements** role. We'll mention this role every time we make an announcement.\n\nIf you'd like to unsubscribe from the announcement notifications, simply send `!unsubscribe` to <#{Channels.bot}>.\n\"\"\"\n\nPERIODIC_PING = (\n    f\"@everyone To verify that you have read our rules, please type `{BotConfig.prefix}accept`.\"\n    f\" If you encounter any problems during the verification process, ping the <@&{Roles.admin}> role in this channel.\"\n)\n\n\nclass Verification(Cog):\n    \"\"\"User verification and role self-management.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.periodic_ping.start()\n\n    @property\n    def mod_log(self) -> ModLog:\n        \"\"\"Get currently loaded ModLog cog instance.\"\"\"\n        return self.bot.get_cog(\"ModLog\")\n\n    @Cog.listener()\n    async def on_message(self, message: Message) -> None:\n        \"\"\"Check new message event for messages to the checkpoint channel & process.\"\"\"\n        if message.author.bot:\n            return  # They're a bot, ignore\n\n        if message.channel.id != Channels.verification:\n            return  # Only listen for #checkpoint messages\n\n        # if a user mentions a role or guild member\n        # alert the mods in mod-alerts channel\n        if message.mentions or message.role_mentions:\n            log.debug(\n                f\"{message.author} mentioned one or more users \"\n                f\"and/or roles in {message.channel.name}\"\n            )\n\n            embed_text = (\n                f\"{message.author.mention} sent a message in \"\n                f\"{message.channel.mention} that contained user and/or role mentions.\"\n                f\"\\n\\n**Original message:**\\n>>> {message.content}\"\n            )\n\n            # Send pretty mod log embed to mod-alerts\n            await self.mod_log.send_log_message(\n                icon_url=Icons.filtering,\n                colour=Colour(Colours.soft_red),\n                title=f\"User/Role mentioned in {message.channel.name}\",\n                text=embed_text,\n                thumbnail=message.author.avatar_url_as(static_format=\"png\"),\n                channel_id=Channels.mod_alerts,\n                ping_everyone=Filter.ping_everyone,\n            )\n\n        ctx = await self.bot.get_context(message)  # type: Context\n\n        if ctx.command is not None and ctx.command.name == \"accept\":\n            return  # They used the accept command\n\n        for role in ctx.author.roles:\n            if role.id == Roles.verified:\n                log.warning(f\"{ctx.author} posted '{ctx.message.content}' \"\n                            \"in the verification channel, but is already verified.\")\n                return  # They're already verified\n\n        log.debug(f\"{ctx.author} posted '{ctx.message.content}' in the verification \"\n                  \"channel. We are providing instructions how to verify.\")\n        await ctx.send(\n            f\"{ctx.author.mention} Please type `!accept` to verify that you accept our rules, \"\n            f\"and gain access to the rest of the server.\",\n            delete_after=20\n        )\n\n        log.trace(f\"Deleting the message posted by {ctx.author}\")\n\n        try:\n            await ctx.message.delete()\n        except NotFound:\n            log.trace(\"No message found, it must have been deleted by another bot.\")\n\n    @command(name='accept', aliases=('verify', 'verified', 'accepted'), hidden=True)\n    @without_role(Roles.verified)\n    @in_channel(Channels.verification)\n    async def accept_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Accept our rules and gain access to the rest of the server.\"\"\"\n        log.debug(f\"{ctx.author} called !accept. Assigning the 'Developer' role.\")\n        await ctx.author.add_roles(Object(Roles.verified), reason=\"Accepted the rules\")\n        try:\n            await ctx.author.send(WELCOME_MESSAGE)\n        except Exception:\n            # Catch the exception, in case they have DMs off or something\n            log.exception(f\"Unable to send welcome message to user {ctx.author}.\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        try:\n            self.mod_log.ignore(Event.message_delete, ctx.message.id)\n            await ctx.message.delete()\n        except NotFound:\n            log.trace(\"No message found, it must have been deleted by another bot.\")\n\n    @command(name='subscribe')\n    @in_channel(Channels.bot)\n    async def subscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Subscribe to announcement notifications by assigning yourself the role.\"\"\"\n        has_role = False\n\n        for role in ctx.author.roles:\n            if role.id == Roles.announcements:\n                has_role = True\n                break\n\n        if has_role:\n            await ctx.send(f\"{ctx.author.mention} You're already subscribed!\")\n            return\n\n        log.debug(f\"{ctx.author} called !subscribe. Assigning the 'Announcements' role.\")\n        await ctx.author.add_roles(Object(Roles.announcements), reason=\"Subscribed to announcements\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        await ctx.send(\n            f\"{ctx.author.mention} Subscribed to <#{Channels.announcements}> notifications.\",\n        )\n\n    @command(name='unsubscribe')\n    @in_channel(Channels.bot)\n    async def unsubscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Unsubscribe from announcement notifications by removing the role from yourself.\"\"\"\n        has_role = False\n\n        for role in ctx.author.roles:\n            if role.id == Roles.announcements:\n                has_role = True\n                break\n\n        if not has_role:\n            await ctx.send(f\"{ctx.author.mention} You're already unsubscribed!\")\n            return\n\n        log.debug(f\"{ctx.author} called !unsubscribe. Removing the 'Announcements' role.\")\n        await ctx.author.remove_roles(Object(Roles.announcements), reason=\"Unsubscribed from announcements\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        await ctx.send(\n            f\"{ctx.author.mention} Unsubscribed from <#{Channels.announcements}> notifications.\"\n        )\n\n    # This cannot be static (must have a __func__ attribute).\n    async def cog_command_error(self, ctx: Context, error: Exception) -> None:\n        \"\"\"Check for & ignore any InChannelCheckFailure.\"\"\"\n        if isinstance(error, InChannelCheckFailure):\n            error.handled = True\n\n    @staticmethod\n    def bot_check(ctx: Context) -> bool:\n        \"\"\"Block any command within the verification channel that is not !accept.\"\"\"\n        if ctx.channel.id == Channels.verification:\n            return ctx.command.name == \"accept\"\n        else:\n            return True\n\n    @tasks.loop(hours=12)\n    async def periodic_ping(self) -> None:\n        \"\"\"Every week, mention @everyone to remind them to verify.\"\"\"\n        messages = self.bot.get_channel(Channels.verification).history(limit=10)\n        need_to_post = True  # True if a new message needs to be sent.\n\n        async for message in messages:\n            if message.author == self.bot.user and message.content == PERIODIC_PING:\n                delta = datetime.utcnow() - message.created_at  # Time since last message.\n                if delta.days >= 7:  # Message is older than a week.\n                    await message.delete()\n                else:\n                    need_to_post = False\n\n                break\n\n        if need_to_post:\n            await self.bot.get_channel(Channels.verification).send(PERIODIC_PING)\n\n    @periodic_ping.before_loop\n    async def before_ping(self) -> None:\n        \"\"\"Only start the loop when the bot is ready.\"\"\"\n        await self.bot.wait_until_ready()\n\n    def cog_unload(self) -> None:\n        \"\"\"Cancel the periodic ping task when the cog is unloaded.\"\"\"\n        self.periodic_ping.cancel()\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Verification cog.\"\"\"\n    bot.add_cog(Verification(bot))\n", "path": "bot/cogs/verification.py"}], "after_files": [{"content": "import logging\nfrom datetime import datetime\n\nfrom discord import Colour, Message, NotFound, Object\nfrom discord.ext import tasks\nfrom discord.ext.commands import Cog, Context, command\n\nfrom bot.bot import Bot\nfrom bot.cogs.moderation import ModLog\nfrom bot.constants import (\n    Bot as BotConfig,\n    Channels, Colours, Event,\n    Filter, Icons, MODERATION_ROLES, Roles\n)\nfrom bot.decorators import InChannelCheckFailure, in_channel, without_role\nfrom bot.utils.checks import without_role_check\n\nlog = logging.getLogger(__name__)\n\nWELCOME_MESSAGE = f\"\"\"\nHello! Welcome to the server, and thanks for verifying yourself!\n\nFor your records, these are the documents you accepted:\n\n`1)` Our rules, here: <https://pythondiscord.com/pages/rules>\n`2)` Our privacy policy, here: <https://pythondiscord.com/pages/privacy> - you can find information on how to have \\\nyour information removed here as well.\n\nFeel free to review them at any point!\n\nAdditionally, if you'd like to receive notifications for the announcements we post in <#{Channels.announcements}> \\\nfrom time to time, you can send `!subscribe` to <#{Channels.bot}> at any time to assign yourself the \\\n**Announcements** role. We'll mention this role every time we make an announcement.\n\nIf you'd like to unsubscribe from the announcement notifications, simply send `!unsubscribe` to <#{Channels.bot}>.\n\"\"\"\n\nPERIODIC_PING = (\n    f\"@everyone To verify that you have read our rules, please type `{BotConfig.prefix}accept`.\"\n    f\" If you encounter any problems during the verification process, ping the <@&{Roles.admin}> role in this channel.\"\n)\nBOT_MESSAGE_DELETE_DELAY = 10\n\n\nclass Verification(Cog):\n    \"\"\"User verification and role self-management.\"\"\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n        self.periodic_ping.start()\n\n    @property\n    def mod_log(self) -> ModLog:\n        \"\"\"Get currently loaded ModLog cog instance.\"\"\"\n        return self.bot.get_cog(\"ModLog\")\n\n    @Cog.listener()\n    async def on_message(self, message: Message) -> None:\n        \"\"\"Check new message event for messages to the checkpoint channel & process.\"\"\"\n        if message.channel.id != Channels.verification:\n            return  # Only listen for #checkpoint messages\n\n        if message.author.bot:\n            # They're a bot, delete their message after the delay.\n            # But not the periodic ping; we like that one.\n            if message.content != PERIODIC_PING:\n                await message.delete(delay=BOT_MESSAGE_DELETE_DELAY)\n            return\n\n        # if a user mentions a role or guild member\n        # alert the mods in mod-alerts channel\n        if message.mentions or message.role_mentions:\n            log.debug(\n                f\"{message.author} mentioned one or more users \"\n                f\"and/or roles in {message.channel.name}\"\n            )\n\n            embed_text = (\n                f\"{message.author.mention} sent a message in \"\n                f\"{message.channel.mention} that contained user and/or role mentions.\"\n                f\"\\n\\n**Original message:**\\n>>> {message.content}\"\n            )\n\n            # Send pretty mod log embed to mod-alerts\n            await self.mod_log.send_log_message(\n                icon_url=Icons.filtering,\n                colour=Colour(Colours.soft_red),\n                title=f\"User/Role mentioned in {message.channel.name}\",\n                text=embed_text,\n                thumbnail=message.author.avatar_url_as(static_format=\"png\"),\n                channel_id=Channels.mod_alerts,\n                ping_everyone=Filter.ping_everyone,\n            )\n\n        ctx = await self.bot.get_context(message)  # type: Context\n\n        if ctx.command is not None and ctx.command.name == \"accept\":\n            return  # They used the accept command\n\n        for role in ctx.author.roles:\n            if role.id == Roles.verified:\n                log.warning(f\"{ctx.author} posted '{ctx.message.content}' \"\n                            \"in the verification channel, but is already verified.\")\n                return  # They're already verified\n\n        log.debug(f\"{ctx.author} posted '{ctx.message.content}' in the verification \"\n                  \"channel. We are providing instructions how to verify.\")\n        await ctx.send(\n            f\"{ctx.author.mention} Please type `!accept` to verify that you accept our rules, \"\n            f\"and gain access to the rest of the server.\",\n            delete_after=20\n        )\n\n        log.trace(f\"Deleting the message posted by {ctx.author}\")\n\n        try:\n            await ctx.message.delete()\n        except NotFound:\n            log.trace(\"No message found, it must have been deleted by another bot.\")\n\n    @command(name='accept', aliases=('verify', 'verified', 'accepted'), hidden=True)\n    @without_role(Roles.verified)\n    @in_channel(Channels.verification)\n    async def accept_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Accept our rules and gain access to the rest of the server.\"\"\"\n        log.debug(f\"{ctx.author} called !accept. Assigning the 'Developer' role.\")\n        await ctx.author.add_roles(Object(Roles.verified), reason=\"Accepted the rules\")\n        try:\n            await ctx.author.send(WELCOME_MESSAGE)\n        except Exception:\n            # Catch the exception, in case they have DMs off or something\n            log.exception(f\"Unable to send welcome message to user {ctx.author}.\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        try:\n            self.mod_log.ignore(Event.message_delete, ctx.message.id)\n            await ctx.message.delete()\n        except NotFound:\n            log.trace(\"No message found, it must have been deleted by another bot.\")\n\n    @command(name='subscribe')\n    @in_channel(Channels.bot)\n    async def subscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Subscribe to announcement notifications by assigning yourself the role.\"\"\"\n        has_role = False\n\n        for role in ctx.author.roles:\n            if role.id == Roles.announcements:\n                has_role = True\n                break\n\n        if has_role:\n            await ctx.send(f\"{ctx.author.mention} You're already subscribed!\")\n            return\n\n        log.debug(f\"{ctx.author} called !subscribe. Assigning the 'Announcements' role.\")\n        await ctx.author.add_roles(Object(Roles.announcements), reason=\"Subscribed to announcements\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        await ctx.send(\n            f\"{ctx.author.mention} Subscribed to <#{Channels.announcements}> notifications.\",\n        )\n\n    @command(name='unsubscribe')\n    @in_channel(Channels.bot)\n    async def unsubscribe_command(self, ctx: Context, *_) -> None:  # We don't actually care about the args\n        \"\"\"Unsubscribe from announcement notifications by removing the role from yourself.\"\"\"\n        has_role = False\n\n        for role in ctx.author.roles:\n            if role.id == Roles.announcements:\n                has_role = True\n                break\n\n        if not has_role:\n            await ctx.send(f\"{ctx.author.mention} You're already unsubscribed!\")\n            return\n\n        log.debug(f\"{ctx.author} called !unsubscribe. Removing the 'Announcements' role.\")\n        await ctx.author.remove_roles(Object(Roles.announcements), reason=\"Unsubscribed from announcements\")\n\n        log.trace(f\"Deleting the message posted by {ctx.author}.\")\n\n        await ctx.send(\n            f\"{ctx.author.mention} Unsubscribed from <#{Channels.announcements}> notifications.\"\n        )\n\n    # This cannot be static (must have a __func__ attribute).\n    async def cog_command_error(self, ctx: Context, error: Exception) -> None:\n        \"\"\"Check for & ignore any InChannelCheckFailure.\"\"\"\n        if isinstance(error, InChannelCheckFailure):\n            error.handled = True\n\n    @staticmethod\n    def bot_check(ctx: Context) -> bool:\n        \"\"\"Block any command within the verification channel that is not !accept.\"\"\"\n        if ctx.channel.id == Channels.verification and without_role_check(ctx, *MODERATION_ROLES):\n            return ctx.command.name == \"accept\"\n        else:\n            return True\n\n    @tasks.loop(hours=12)\n    async def periodic_ping(self) -> None:\n        \"\"\"Every week, mention @everyone to remind them to verify.\"\"\"\n        messages = self.bot.get_channel(Channels.verification).history(limit=10)\n        need_to_post = True  # True if a new message needs to be sent.\n\n        async for message in messages:\n            if message.author == self.bot.user and message.content == PERIODIC_PING:\n                delta = datetime.utcnow() - message.created_at  # Time since last message.\n                if delta.days >= 7:  # Message is older than a week.\n                    await message.delete()\n                else:\n                    need_to_post = False\n\n                break\n\n        if need_to_post:\n            await self.bot.get_channel(Channels.verification).send(PERIODIC_PING)\n\n    @periodic_ping.before_loop\n    async def before_ping(self) -> None:\n        \"\"\"Only start the loop when the bot is ready.\"\"\"\n        await self.bot.wait_until_ready()\n\n    def cog_unload(self) -> None:\n        \"\"\"Cancel the periodic ping task when the cog is unloaded.\"\"\"\n        self.periodic_ping.cancel()\n\n\ndef setup(bot: Bot) -> None:\n    \"\"\"Load the Verification cog.\"\"\"\n    bot.add_cog(Verification(bot))\n", "path": "bot/cogs/verification.py"}]}