aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Dennis Pham <[email protected]>2021-01-10 19:51:25 -0500
committerGravatar GitHub <[email protected]>2021-01-10 19:51:25 -0500
commitbfa9359593e97f31a55e56172d99250bb8c18c4a (patch)
tree5e25c933f0311dd7554a305e8e307786dae94eb0
parentMake sure that users without the Developers role can use tag. (diff)
parentAnnihilate all traces of Developer and Unverified roles (diff)
Merge pull request #1350 from python-discord/mbaruh/developerectomy
Annihilate all traces of Developer and Unverified roles
-rw-r--r--bot/constants.py13
-rw-r--r--bot/exts/backend/error_handler.py10
-rw-r--r--bot/exts/moderation/silence.py2
-rw-r--r--bot/exts/moderation/verification.py675
-rw-r--r--bot/exts/utils/jams.py4
-rw-r--r--bot/rules/burst_shared.py11
-rw-r--r--config-default.yml15
-rw-r--r--tests/bot/exts/utils/test_jams.py4
8 files changed, 14 insertions, 720 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 6bfda160b..d813046ab 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -434,7 +434,6 @@ class Channels(metaclass=YAMLGetter):
talent_pool: int
user_event_announcements: int
user_log: int
- verification: int
voice_chat: int
voice_gate: int
voice_log: int
@@ -471,8 +470,6 @@ class Roles(metaclass=YAMLGetter):
python_community: int
sprinters: int
team_leaders: int
- unverified: int
- verified: int # This is the Developers role on PyDis, here named verified for readability reasons.
voice_verified: int
@@ -594,16 +591,6 @@ class PythonNews(metaclass=YAMLGetter):
webhook: int
-class Verification(metaclass=YAMLGetter):
- section = "verification"
-
- unverified_after: int
- kicked_after: int
- reminder_frequency: int
- bot_message_delete_delay: int
- kick_confirmation_threshold: float
-
-
class VoiceGate(metaclass=YAMLGetter):
section = "voice_gate"
diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py
index c643d346e..5b5840858 100644
--- a/bot/exts/backend/error_handler.py
+++ b/bot/exts/backend/error_handler.py
@@ -8,7 +8,7 @@ from sentry_sdk import push_scope
from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.constants import Channels, Colours
+from bot.constants import Colours
from bot.converters import TagNameConverter
from bot.errors import LockedResourceError
from bot.utils.checks import InWhitelistCheckFailure
@@ -47,7 +47,6 @@ class ErrorHandler(Cog):
* If CommandNotFound is raised when invoking the tag (determined by the presence of the
`invoked_from_error_handler` attribute), this error is treated as being unexpected
and therefore sends an error message
- * Commands in the verification channel are ignored
2. UserInputError: see `handle_user_input_error`
3. CheckFailure: see `handle_check_failure`
4. CommandOnCooldown: send an error message in the invoking context
@@ -63,10 +62,9 @@ class ErrorHandler(Cog):
if isinstance(e, errors.CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"):
if await self.try_silence(ctx):
return
- if ctx.channel.id != Channels.verification:
- # Try to look for a tag with the command's name
- await self.try_get_tag(ctx)
- return # Exit early to avoid logging.
+ # Try to look for a tag with the command's name
+ await self.try_get_tag(ctx)
+ return # Exit early to avoid logging.
elif isinstance(e, errors.UserInputError):
await self.handle_user_input_error(ctx, e)
elif isinstance(e, errors.CheckFailure):
diff --git a/bot/exts/moderation/silence.py b/bot/exts/moderation/silence.py
index a942d5294..2a7ca932e 100644
--- a/bot/exts/moderation/silence.py
+++ b/bot/exts/moderation/silence.py
@@ -72,7 +72,7 @@ class SilenceNotifier(tasks.Loop):
class Silence(commands.Cog):
- """Commands for stopping channel messages for `verified` role in a channel."""
+ """Commands for stopping channel messages for `everyone` role in a channel."""
# Maps muted channel IDs to their previous overwrites for send_message and add_reactions.
# Overwrites are stored as JSON.
diff --git a/bot/exts/moderation/verification.py b/bot/exts/moderation/verification.py
index ce91dcb15..2a24c8ec6 100644
--- a/bot/exts/moderation/verification.py
+++ b/bot/exts/moderation/verification.py
@@ -1,27 +1,18 @@
-import asyncio
import logging
import typing as t
-from contextlib import suppress
-from datetime import datetime, timedelta
import discord
-from async_rediscache import RedisCache
-from discord.ext import tasks
-from discord.ext.commands import Cog, Context, command, group, has_any_role
-from discord.utils import snowflake_time
+from discord.ext.commands import Cog, Context, command, has_any_role
from bot import constants
-from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.decorators import has_no_roles, in_whitelist
-from bot.exts.moderation.modlog import ModLog
-from bot.utils.checks import InWhitelistCheckFailure, has_no_roles_check
-from bot.utils.messages import format_user
+from bot.decorators import in_whitelist
+from bot.utils.checks import InWhitelistCheckFailure
log = logging.getLogger(__name__)
# Sent via DMs once user joins the guild
-ON_JOIN_MESSAGE = f"""
+ON_JOIN_MESSAGE = """
Welcome to Python Discord!
To show you what kind of community we are, we've created this video:
@@ -29,32 +20,9 @@ https://youtu.be/ZH26PuX3re0
As a new user, you have read-only access to a few select channels to give you a taste of what our server is like. \
In order to see the rest of the channels and to send messages, you first have to accept our rules.
-
-Please visit <#{constants.Channels.verification}> to get started. Thank you!
"""
-# Sent via DMs once user verifies
VERIFIED_MESSAGE = f"""
-Thanks for verifying yourself!
-
-For your records, these are the documents you accepted:
-
-`1)` Our rules, here: <https://pythondiscord.com/pages/rules>
-`2)` Our privacy policy, here: <https://pythondiscord.com/pages/privacy> - you can find information on how to have \
-your information removed here as well.
-
-Feel free to review them at any point!
-
-Additionally, if you'd like to receive notifications for the announcements \
-we post in <#{constants.Channels.announcements}>
-from time to time, you can send `!subscribe` to <#{constants.Channels.bot_commands}> at any time \
-to assign yourself the **Announcements** role. We'll mention this role every time we make an announcement.
-
-If you'd like to unsubscribe from the announcement notifications, simply send `!unsubscribe` to \
-<#{constants.Channels.bot_commands}>.
-"""
-
-ALTERNATE_VERIFIED_MESSAGE = f"""
You are now verified!
You can find a copy of our rules for reference at <https://pythondiscord.com/pages/rules>.
@@ -71,61 +39,6 @@ To introduce you to our community, we've made the following video:
https://youtu.be/ZH26PuX3re0
"""
-# Sent via DMs to users kicked for failing to verify
-KICKED_MESSAGE = f"""
-Hi! You have been automatically kicked from Python Discord as you have failed to accept our rules \
-within `{constants.Verification.kicked_after}` days. If this was an accident, please feel free to join us again!
-
-{constants.Guild.invite}
-"""
-
-# Sent periodically in the verification channel
-REMINDER_MESSAGE = f"""
-<@&{constants.Roles.unverified}>
-
-Welcome to Python Discord! Please read the documents mentioned above and type `!accept` to gain permissions \
-to send messages in the community!
-
-You will be kicked if you don't verify within `{constants.Verification.kicked_after}` days.
-""".strip()
-
-# An async function taking a Member param
-Request = t.Callable[[discord.Member], t.Awaitable]
-
-
-class StopExecution(Exception):
- """Signals that a task should halt immediately & alert admins."""
-
- def __init__(self, reason: discord.HTTPException) -> None:
- super().__init__()
- self.reason = reason
-
-
-class Limit(t.NamedTuple):
- """Composition over config for throttling requests."""
-
- batch_size: int # Amount of requests after which to pause
- sleep_secs: int # Sleep this many seconds after each batch
-
-
-def mention_role(role_id: int) -> discord.AllowedMentions:
- """Construct an allowed mentions instance that allows pinging `role_id`."""
- return discord.AllowedMentions(roles=[discord.Object(role_id)])
-
-
-def is_verified(member: discord.Member) -> bool:
- """
- Check whether `member` is considered verified.
-
- Members are considered verified if they have at least 1 role other than
- the default role (@everyone) and the @Unverified role.
- """
- unverified_roles = {
- member.guild.get_role(constants.Roles.unverified),
- member.guild.default_role,
- }
- return len(set(member.roles) - unverified_roles) > 0
-
async def safe_dm(coro: t.Coroutine) -> None:
"""
@@ -150,410 +63,16 @@ class Verification(Cog):
"""
User verification and role management.
- There are two internal tasks in this cog:
-
- * `update_unverified_members`
- * Unverified members are given the @Unverified role after configured `unverified_after` days
- * Unverified members are kicked after configured `kicked_after` days
- * `ping_unverified`
- * Periodically ping the @Unverified role in the verification channel
-
Statistics are collected in the 'verification.' namespace.
- Moderators+ can use the `verification` command group to start or stop both internal
- tasks, if necessary. Settings are persisted in Redis across sessions.
-
- Additionally, this cog offers the !accept, !subscribe and !unsubscribe commands,
- and keeps the verification channel clean by deleting messages.
+ Additionally, this cog offers the !subscribe and !unsubscribe commands,
"""
- # Persist task settings & last sent `REMINDER_MESSAGE` id
- # RedisCache[
- # "tasks_running": int (0 or 1),
- # "last_reminder": int (discord.Message.id),
- # ]
- task_cache = RedisCache()
-
def __init__(self, bot: Bot) -> None:
"""Start internal tasks."""
self.bot = bot
- self.bot.loop.create_task(self._maybe_start_tasks())
-
self.pending_members = set()
- def cog_unload(self) -> None:
- """
- Cancel internal tasks.
-
- This is necessary, as tasks are not automatically cancelled on cog unload.
- """
- self._stop_tasks(gracefully=False)
-
- @property
- def mod_log(self) -> ModLog:
- """Get currently loaded ModLog cog instance."""
- return self.bot.get_cog("ModLog")
-
- async def _maybe_start_tasks(self) -> None:
- """
- Poll Redis to check whether internal tasks should start.
-
- Redis must be interfaced with from an async function.
- """
- log.trace("Checking whether background tasks should begin")
- setting: t.Optional[int] = await self.task_cache.get("tasks_running") # This can be None if never set
-
- if setting:
- log.trace("Background tasks will be started")
- self.update_unverified_members.start()
- self.ping_unverified.start()
-
- def _stop_tasks(self, *, gracefully: bool) -> None:
- """
- Stop the update users & ping @Unverified tasks.
-
- If `gracefully` is True, the tasks will be able to finish their current iteration.
- Otherwise, they are cancelled immediately.
- """
- log.info(f"Stopping internal tasks ({gracefully=})")
- if gracefully:
- self.update_unverified_members.stop()
- self.ping_unverified.stop()
- else:
- self.update_unverified_members.cancel()
- self.ping_unverified.cancel()
-
- # region: automatically update unverified users
-
- async def _verify_kick(self, n_members: int) -> bool:
- """
- Determine whether `n_members` is a reasonable amount of members to kick.
-
- First, `n_members` is checked against the size of the PyDis guild. If `n_members` are
- more than the configured `kick_confirmation_threshold` of the guild, the operation
- must be confirmed by staff in #core-dev. Otherwise, the operation is seen as safe.
- """
- log.debug(f"Checking whether {n_members} members are safe to kick")
-
- await self.bot.wait_until_guild_available() # Ensure cache is populated before we grab the guild
- pydis = self.bot.get_guild(constants.Guild.id)
-
- percentage = n_members / len(pydis.members)
- if percentage < constants.Verification.kick_confirmation_threshold:
- log.debug(f"Kicking {percentage:.2%} of the guild's population is seen as safe")
- return True
-
- # Since `n_members` is a suspiciously large number, we will ask for confirmation
- log.debug("Amount of users is too large, requesting staff confirmation")
-
- core_dev_channel = pydis.get_channel(constants.Channels.dev_core)
- core_dev_ping = f"<@&{constants.Roles.core_developers}>"
-
- confirmation_msg = await core_dev_channel.send(
- f"{core_dev_ping} Verification determined that `{n_members}` members should be kicked as they haven't "
- f"verified in `{constants.Verification.kicked_after}` days. This is `{percentage:.2%}` of the guild's "
- f"population. Proceed?",
- allowed_mentions=mention_role(constants.Roles.core_developers),
- )
-
- options = (constants.Emojis.incident_actioned, constants.Emojis.incident_unactioned)
- for option in options:
- await confirmation_msg.add_reaction(option)
-
- core_dev_ids = [member.id for member in pydis.get_role(constants.Roles.core_developers).members]
-
- def check(reaction: discord.Reaction, user: discord.User) -> bool:
- """Check whether `reaction` is a valid reaction to `confirmation_msg`."""
- return (
- reaction.message.id == confirmation_msg.id # Reacted to `confirmation_msg`
- and str(reaction.emoji) in options # With one of `options`
- and user.id in core_dev_ids # By a core developer
- )
-
- timeout = 60 * 5 # Seconds, i.e. 5 minutes
- try:
- choice, _ = await self.bot.wait_for("reaction_add", check=check, timeout=timeout)
- except asyncio.TimeoutError:
- log.debug("Staff prompt not answered, aborting operation")
- return False
- finally:
- with suppress(discord.HTTPException):
- await confirmation_msg.clear_reactions()
-
- result = str(choice) == constants.Emojis.incident_actioned
- log.debug(f"Received answer: {choice}, result: {result}")
-
- # Edit the prompt message to reflect the final choice
- if result is True:
- result_msg = f":ok_hand: {core_dev_ping} Request to kick `{n_members}` members was authorized!"
- else:
- result_msg = f":warning: {core_dev_ping} Request to kick `{n_members}` members was denied!"
-
- with suppress(discord.HTTPException):
- await confirmation_msg.edit(content=result_msg)
-
- return result
-
- async def _alert_admins(self, exception: discord.HTTPException) -> None:
- """
- Ping @Admins with information about `exception`.
-
- This is used when a critical `exception` caused a verification task to abort.
- """
- await self.bot.wait_until_guild_available()
- log.info(f"Sending admin alert regarding exception: {exception}")
-
- admins_channel = self.bot.get_guild(constants.Guild.id).get_channel(constants.Channels.admins)
- ping = f"<@&{constants.Roles.admins}>"
-
- await admins_channel.send(
- f"{ping} Aborted updating unverified users due to the following exception:\n"
- f"```{exception}```\n"
- f"Internal tasks will be stopped.",
- allowed_mentions=mention_role(constants.Roles.admins),
- )
-
- async def _send_requests(self, members: t.Collection[discord.Member], request: Request, limit: Limit) -> int:
- """
- Pass `members` one by one to `request` handling Discord exceptions.
-
- This coroutine serves as a generic `request` executor for kicking members and adding
- roles, as it allows us to define the error handling logic in one place only.
-
- Any `request` has the ability to completely abort the execution by raising `StopExecution`.
- In such a case, the @Admins will be alerted of the reason attribute.
-
- To avoid rate-limits, pass a `limit` configuring the batch size and the amount of seconds
- to sleep between batches.
-
- Returns the amount of successful requests. Failed requests are logged at info level.
- """
- log.trace(f"Sending {len(members)} requests")
- n_success, bad_statuses = 0, set()
-
- for progress, member in enumerate(members, start=1):
- if is_verified(member): # Member could have verified in the meantime
- continue
- try:
- await request(member)
- except StopExecution as stop_execution:
- await self._alert_admins(stop_execution.reason)
- await self.task_cache.set("tasks_running", 0)
- self._stop_tasks(gracefully=True) # Gracefully finish current iteration, then stop
- break
- except discord.HTTPException as http_exc:
- bad_statuses.add(http_exc.status)
- else:
- n_success += 1
-
- if progress % limit.batch_size == 0:
- log.trace(f"Processed {progress} requests, pausing for {limit.sleep_secs} seconds")
- await asyncio.sleep(limit.sleep_secs)
-
- if bad_statuses:
- log.info(f"Failed to send {len(members) - n_success} requests due to following statuses: {bad_statuses}")
-
- return n_success
-
- async def _add_kick_note(self, member: discord.Member) -> None:
- """
- Post a note regarding `member` being kicked to site.
-
- Allows keeping track of kicked members for auditing purposes.
- """
- payload = {
- "active": False,
- "actor": self.bot.user.id, # Bot actions this autonomously
- "expires_at": None,
- "hidden": True,
- "reason": "Verification kick",
- "type": "note",
- "user": member.id,
- }
-
- log.trace(f"Posting kick note for member {member} ({member.id})")
- try:
- await self.bot.api_client.post("bot/infractions", json=payload)
- except ResponseCodeError as api_exc:
- log.warning("Failed to post kick note", exc_info=api_exc)
-
- async def _kick_members(self, members: t.Collection[discord.Member]) -> int:
- """
- Kick `members` from the PyDis guild.
-
- Due to strict ratelimits on sending messages (120 requests / 60 secs), we sleep for a second
- after each 2 requests to allow breathing room for other features.
-
- Note that this is a potentially destructive operation. Returns the amount of successful requests.
- """
- log.info(f"Kicking {len(members)} members (not verified after {constants.Verification.kicked_after} days)")
-
- async def kick_request(member: discord.Member) -> None:
- """Send `KICKED_MESSAGE` to `member` and kick them from the guild."""
- try:
- await safe_dm(member.send(KICKED_MESSAGE)) # Suppress disabled DMs
- except discord.HTTPException as suspicious_exception:
- raise StopExecution(reason=suspicious_exception)
- await member.kick(reason=f"User has not verified in {constants.Verification.kicked_after} days")
- await self._add_kick_note(member)
-
- n_kicked = await self._send_requests(members, kick_request, Limit(batch_size=2, sleep_secs=1))
- self.bot.stats.incr("verification.kicked", count=n_kicked)
-
- return n_kicked
-
- async def _give_role(self, members: t.Collection[discord.Member], role: discord.Role) -> int:
- """
- Give `role` to all `members`.
-
- We pause for a second after batches of 25 requests to ensure ratelimits aren't exceeded.
-
- Returns the amount of successful requests.
- """
- log.info(
- f"Assigning {role} role to {len(members)} members (not verified "
- f"after {constants.Verification.unverified_after} days)"
- )
-
- async def role_request(member: discord.Member) -> None:
- """Add `role` to `member`."""
- await member.add_roles(role, reason=f"Not verified after {constants.Verification.unverified_after} days")
-
- return await self._send_requests(members, role_request, Limit(batch_size=25, sleep_secs=1))
-
- async def _check_members(self) -> t.Tuple[t.Set[discord.Member], t.Set[discord.Member]]:
- """
- Check in on the verification status of PyDis members.
-
- This coroutine finds two sets of users:
- * Not verified after configured `unverified_after` days, should be given the @Unverified role
- * Not verified after configured `kicked_after` days, should be kicked from the guild
-
- These sets are always disjoint, i.e. share no common members.
- """
- await self.bot.wait_until_guild_available() # Ensure cache is ready
- pydis = self.bot.get_guild(constants.Guild.id)
-
- unverified = pydis.get_role(constants.Roles.unverified)
- current_dt = datetime.utcnow() # Discord timestamps are UTC
-
- # Users to be given the @Unverified role, and those to be kicked, these should be entirely disjoint
- for_role, for_kick = set(), set()
-
- log.debug("Checking verification status of guild members")
- for member in pydis.members:
-
- # Skip verified members, bots, and members for which we do not know their join date,
- # this should be extremely rare but docs mention that it can happen
- if is_verified(member) or member.bot or member.joined_at is None:
- continue
-
- # At this point, we know that `member` is an unverified user, and we will decide what
- # to do with them based on time passed since their join date
- since_join = current_dt - member.joined_at
-
- if since_join > timedelta(days=constants.Verification.kicked_after):
- for_kick.add(member) # User should be removed from the guild
-
- elif (
- since_join > timedelta(days=constants.Verification.unverified_after)
- and unverified not in member.roles
- ):
- for_role.add(member) # User should be given the @Unverified role
-
- log.debug(f"Found {len(for_role)} users for {unverified} role, {len(for_kick)} users to be kicked")
- return for_role, for_kick
-
- @tasks.loop(minutes=30)
- async def update_unverified_members(self) -> None:
- """
- Periodically call `_check_members` and update unverified members accordingly.
-
- After each run, a summary will be sent to the modlog channel. If a suspiciously high
- amount of members to be kicked is found, the operation is guarded by `_verify_kick`.
- """
- log.info("Updating unverified guild members")
-
- await self.bot.wait_until_guild_available()
- unverified = self.bot.get_guild(constants.Guild.id).get_role(constants.Roles.unverified)
-
- for_role, for_kick = await self._check_members()
-
- if not for_role:
- role_report = f"Found no users to be assigned the {unverified.mention} role."
- else:
- n_roles = await self._give_role(for_role, unverified)
- role_report = f"Assigned {unverified.mention} role to `{n_roles}`/`{len(for_role)}` members."
-
- if not for_kick:
- kick_report = "Found no users to be kicked."
- elif not await self._verify_kick(len(for_kick)):
- kick_report = f"Not authorized to kick `{len(for_kick)}` members."
- else:
- n_kicks = await self._kick_members(for_kick)
- kick_report = f"Kicked `{n_kicks}`/`{len(for_kick)}` members from the guild."
-
- await self.mod_log.send_log_message(
- icon_url=self.bot.user.avatar_url,
- colour=discord.Colour.blurple(),
- title="Verification system",
- text=f"{kick_report}\n{role_report}",
- )
-
- # endregion
- # region: periodically ping @Unverified
-
- @tasks.loop(hours=constants.Verification.reminder_frequency)
- async def ping_unverified(self) -> None:
- """
- Delete latest `REMINDER_MESSAGE` and send it again.
-
- This utilizes RedisCache to persist the latest reminder message id.
- """
- await self.bot.wait_until_guild_available()
- verification = self.bot.get_guild(constants.Guild.id).get_channel(constants.Channels.verification)
-
- last_reminder: t.Optional[int] = await self.task_cache.get("last_reminder")
-
- if last_reminder is not None:
- log.trace(f"Found verification reminder message in cache, deleting: {last_reminder}")
-
- with suppress(discord.HTTPException): # If something goes wrong, just ignore it
- await self.bot.http.delete_message(verification.id, last_reminder)
-
- log.trace("Sending verification reminder")
- new_reminder = await verification.send(
- REMINDER_MESSAGE, allowed_mentions=mention_role(constants.Roles.unverified),
- )
-
- await self.task_cache.set("last_reminder", new_reminder.id)
-
- @ping_unverified.before_loop
- async def _before_first_ping(self) -> None:
- """
- Sleep until `REMINDER_MESSAGE` should be sent again.
-
- If latest reminder is not cached, exit instantly. Otherwise, wait wait until the
- configured `reminder_frequency` has passed.
- """
- last_reminder: t.Optional[int] = await self.task_cache.get("last_reminder")
-
- if last_reminder is None:
- log.trace("Latest verification reminder message not cached, task will not wait")
- return
-
- # Convert cached message id into a timestamp
- time_since = datetime.utcnow() - snowflake_time(last_reminder)
- log.trace(f"Time since latest verification reminder: {time_since}")
-
- to_sleep = timedelta(hours=constants.Verification.reminder_frequency) - time_since
- log.trace(f"Time to sleep until next ping: {to_sleep}")
-
- # Delta can be negative if `reminder_frequency` has already passed
- secs = max(to_sleep.total_seconds(), 0)
- await asyncio.sleep(secs)
-
- # endregion
# region: listeners
@Cog.listener()
@@ -586,183 +105,12 @@ class Verification(Cog):
# and has gone through the alternate gating system we should send
# our alternate welcome DM which includes info such as our welcome
# video.
- await safe_dm(after.send(ALTERNATE_VERIFIED_MESSAGE))
+ await safe_dm(after.send(VERIFIED_MESSAGE))
except discord.HTTPException:
log.exception("DM dispatch failed on unexpected error code")
- @Cog.listener()
- async def on_message(self, message: discord.Message) -> None:
- """Check new message event for messages to the checkpoint channel & process."""
- if message.channel.id != constants.Channels.verification:
- return # Only listen for #checkpoint messages
-
- if message.content == REMINDER_MESSAGE:
- return # Ignore bots own verification reminder
-
- if message.author.bot:
- # They're a bot, delete their message after the delay.
- await message.delete(delay=constants.Verification.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:
- log.debug(
- f"{message.author} mentioned one or more users "
- f"and/or roles in {message.channel.name}"
- )
-
- embed_text = (
- f"{format_user(message.author)} sent a message in "
- f"{message.channel.mention} that contained user and/or role mentions."
- f"\n\n**Original message:**\n>>> {message.content}"
- )
-
- # Send pretty mod log embed to mod-alerts
- await self.mod_log.send_log_message(
- icon_url=constants.Icons.filtering,
- colour=discord.Colour(constants.Colours.soft_red),
- title=f"User/Role mentioned in {message.channel.name}",
- text=embed_text,
- thumbnail=message.author.avatar_url_as(static_format="png"),
- channel_id=constants.Channels.mod_alerts,
- )
-
- ctx: Context = await self.bot.get_context(message)
- if ctx.command is not None and ctx.command.name == "accept":
- return
-
- if any(r.id == constants.Roles.verified for r in ctx.author.roles):
- log.info(
- f"{ctx.author} posted '{ctx.message.content}' "
- "in the verification channel, but is already verified."
- )
- return
-
- log.debug(
- f"{ctx.author} posted '{ctx.message.content}' in the verification "
- "channel. We are providing instructions how to verify."
- )
- await ctx.send(
- f"{ctx.author.mention} Please type `!accept` to verify that you accept our rules, "
- f"and gain access to the rest of the server.",
- delete_after=20
- )
-
- log.trace(f"Deleting the message posted by {ctx.author}")
- with suppress(discord.NotFound):
- await ctx.message.delete()
-
# endregion
- # region: task management commands
-
- @has_any_role(*constants.MODERATION_ROLES)
- @group(name="verification")
- async def verification_group(self, ctx: Context) -> None:
- """Manage internal verification tasks."""
- if ctx.invoked_subcommand is None:
- await ctx.send_help(ctx.command)
-
- @verification_group.command(name="status")
- async def status_cmd(self, ctx: Context) -> None:
- """Check whether verification tasks are running."""
- log.trace("Checking status of verification tasks")
-
- if self.update_unverified_members.is_running():
- update_status = f"{constants.Emojis.incident_actioned} Member update task is running."
- else:
- update_status = f"{constants.Emojis.incident_unactioned} Member update task is **not** running."
-
- mention = f"<@&{constants.Roles.unverified}>"
- if self.ping_unverified.is_running():
- ping_status = f"{constants.Emojis.incident_actioned} Ping {mention} task is running."
- else:
- ping_status = f"{constants.Emojis.incident_unactioned} Ping {mention} task is **not** running."
-
- embed = discord.Embed(
- title="Verification system",
- description=f"{update_status}\n{ping_status}",
- colour=discord.Colour.blurple(),
- )
- await ctx.send(embed=embed)
-
- @verification_group.command(name="start")
- async def start_cmd(self, ctx: Context) -> None:
- """Start verification tasks if they are not already running."""
- log.info("Starting verification tasks")
-
- if not self.update_unverified_members.is_running():
- self.update_unverified_members.start()
-
- if not self.ping_unverified.is_running():
- self.ping_unverified.start()
-
- await self.task_cache.set("tasks_running", 1)
-
- colour = discord.Colour.blurple()
- await ctx.send(embed=discord.Embed(title="Verification system", description="Done. :ok_hand:", colour=colour))
-
- @verification_group.command(name="stop", aliases=["kill"])
- async def stop_cmd(self, ctx: Context) -> None:
- """Stop verification tasks."""
- log.info("Stopping verification tasks")
-
- self._stop_tasks(gracefully=False)
- await self.task_cache.set("tasks_running", 0)
-
- colour = discord.Colour.blurple()
- await ctx.send(embed=discord.Embed(title="Verification system", description="Tasks canceled.", colour=colour))
-
- # endregion
- # region: accept and subscribe commands
-
- def _bump_verified_stats(self, verified_member: discord.Member) -> None:
- """
- Increment verification stats for `verified_member`.
-
- Each member falls into one of the three categories:
- * Verified within 24 hours after joining
- * Does not have @Unverified role yet
- * Does have @Unverified role
-
- Stats for member kicking are handled separately.
- """
- if verified_member.joined_at is None: # Docs mention this can happen
- return
-
- if (datetime.utcnow() - verified_member.joined_at) < timedelta(hours=24):
- category = "accepted_on_day_one"
- elif constants.Roles.unverified not in [role.id for role in verified_member.roles]:
- category = "accepted_before_unverified"
- else:
- category = "accepted_after_unverified"
-
- log.trace(f"Bumping verification stats in category: {category}")
- self.bot.stats.incr(f"verification.{category}")
-
- @command(name='accept', aliases=('verified', 'accepted'), hidden=True)
- @has_no_roles(constants.Roles.verified)
- @in_whitelist(channels=(constants.Channels.verification,))
- async def accept_command(self, ctx: Context, *_) -> None: # We don't actually care about the args
- """Accept our rules and gain access to the rest of the server."""
- log.debug(f"{ctx.author} called !accept. Assigning the 'Developer' role.")
- await ctx.author.add_roles(discord.Object(constants.Roles.verified), reason="Accepted the rules")
-
- self._bump_verified_stats(ctx.author) # This checks for @Unverified so make sure it's not yet removed
-
- if constants.Roles.unverified in [role.id for role in ctx.author.roles]:
- log.debug(f"Removing Unverified role from: {ctx.author}")
- await ctx.author.remove_roles(discord.Object(constants.Roles.unverified))
-
- try:
- await safe_dm(ctx.author.send(VERIFIED_MESSAGE))
- except discord.HTTPException:
- log.exception(f"Sending welcome message failed for {ctx.author}.")
- finally:
- log.trace(f"Deleting accept message by {ctx.author}.")
- with suppress(discord.NotFound):
- self.mod_log.ignore(constants.Event.message_delete, ctx.message.id)
- await ctx.message.delete()
+ # region: subscribe commands
@command(name='subscribe')
@in_whitelist(channels=(constants.Channels.bot_commands,))
@@ -823,15 +171,6 @@ class Verification(Cog):
if isinstance(error, InWhitelistCheckFailure):
error.handled = True
- @staticmethod
- async def bot_check(ctx: Context) -> bool:
- """Block any command within the verification channel that is not !accept."""
- is_verification = ctx.channel.id == constants.Channels.verification
- if is_verification and await has_no_roles_check(ctx, *constants.MODERATION_ROLES):
- return ctx.command.name == "accept"
- else:
- return True
-
@command(name='verify')
@has_any_role(*constants.MODERATION_ROLES)
async def perform_manual_verification(self, ctx: Context, user: discord.Member) -> None:
diff --git a/bot/exts/utils/jams.py b/bot/exts/utils/jams.py
index 1c0988343..98fbcb303 100644
--- a/bot/exts/utils/jams.py
+++ b/bot/exts/utils/jams.py
@@ -93,10 +93,6 @@ class CodeJams(commands.Cog):
connect=True
),
guild.default_role: PermissionOverwrite(read_messages=False, connect=False),
- guild.get_role(Roles.verified): PermissionOverwrite(
- read_messages=False,
- connect=False
- )
}
# Rest of members should just have read_messages
diff --git a/bot/rules/burst_shared.py b/bot/rules/burst_shared.py
index 0e66df69c..bbe9271b3 100644
--- a/bot/rules/burst_shared.py
+++ b/bot/rules/burst_shared.py
@@ -2,20 +2,11 @@ from typing import Dict, Iterable, List, Optional, Tuple
from discord import Member, Message
-from bot.constants import Channels
-
async def apply(
last_message: Message, recent_messages: List[Message], config: Dict[str, int]
) -> Optional[Tuple[str, Iterable[Member], Iterable[Message]]]:
- """
- Detects repeated messages sent by multiple users.
-
- This filter never triggers in the verification channel.
- """
- if last_message.channel.id == Channels.verification:
- return
-
+ """Detects repeated messages sent by multiple users."""
total_recent = len(recent_messages)
if total_recent > config['max']:
diff --git a/config-default.yml b/config-default.yml
index e713a59d2..175460a31 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -173,7 +173,6 @@ guild:
# Special
bot_commands: &BOT_CMD 267659945086812160
esoteric: 470884583684964352
- verification: 352442727016693763
voice_gate: 764802555427029012
# Staff
@@ -244,8 +243,6 @@ guild:
python_community: &PY_COMMUNITY_ROLE 458226413825294336
sprinters: &SPRINTERS 758422482289426471
- unverified: 739794855945044069
- verified: 352427296948486144 # @Developers on PyDis
voice_verified: 764802720779337729
# Staff
@@ -514,18 +511,6 @@ python_news:
webhook: *PYNEWS_WEBHOOK
-verification:
- unverified_after: 3 # Days after which non-Developers receive the @Unverified role
- kicked_after: 30 # Days after which non-Developers get kicked from the guild
- reminder_frequency: 28 # Hours between @Unverified pings
- bot_message_delete_delay: 10 # Seconds before deleting bots response in #verification
-
- # Number in range [0, 1] determining the percentage of unverified users that are safe
- # to be kicked from the guild in one batch, any larger amount will require staff confirmation,
- # set this to 0 to require explicit approval for batches of any size
- kick_confirmation_threshold: 0.01 # 1%
-
-
voice_gate:
minimum_days_member: 3 # How many days the user must have been a member for
minimum_messages: 50 # How many messages a user must have to be eligible for voice
diff --git a/tests/bot/exts/utils/test_jams.py b/tests/bot/exts/utils/test_jams.py
index 45e7b5b51..85d6a1173 100644
--- a/tests/bot/exts/utils/test_jams.py
+++ b/tests/bot/exts/utils/test_jams.py
@@ -118,11 +118,9 @@ class JamCreateTeamTests(unittest.IsolatedAsyncioTestCase):
self.assertTrue(overwrites[member].read_messages)
self.assertTrue(overwrites[member].connect)
- # Everyone and verified role overwrite
+ # Everyone role overwrite
self.assertFalse(overwrites[self.guild.default_role].read_messages)
self.assertFalse(overwrites[self.guild.default_role].connect)
- self.assertFalse(overwrites[self.guild.get_role(Roles.verified)].read_messages)
- self.assertFalse(overwrites[self.guild.get_role(Roles.verified)].connect)
async def test_team_channels_creation(self):
"""Should create new voice and text channel for team."""