diff options
| -rw-r--r-- | bot/exts/moderation/voice_gate.py | 112 |
1 files changed, 111 insertions, 1 deletions
diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index 198617857..dae19d49e 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -1,6 +1,26 @@ -from discord.ext.commands import Cog +import logging +from contextlib import suppress +from datetime import datetime, timedelta +import discord +from dateutil import parser + +from discord.ext.commands import Cog, Context, command + +from bot.api import ResponseCodeError from bot.bot import Bot +from bot.constants import Channels, Roles, VoiceGate as VoiceGateConf, MODERATION_ROLES, Event +from bot.decorators import has_no_roles, in_whitelist +from bot.exts.moderation.modlog import ModLog + +log = logging.getLogger(__name__) + +# Messages for case when user don't meet with requirements +NOT_ENOUGH_MESSAGES = f"haven't sent at least {VoiceGateConf.minimum_messages} messages" +NOT_ENOUGH_DAYS_AFTER_VERIFICATION = f"haven't been verified for at least {VoiceGateConf.minimum_days_verified} days" +VOICE_BANNED = "are voice banned" + +FAILED_MESSAGE = """{user} you don't meet with our current requirements to pass Voice Gate. You {reasons}.""" class VoiceGate(Cog): @@ -9,6 +29,96 @@ class VoiceGate(Cog): def __init__(self, bot: Bot): self.bot = bot + @property + def mod_log(self) -> ModLog: + """Get the currently loaded ModLog cog instance.""" + return self.bot.get_cog("ModLog") + + @command(aliases=('voiceverify', 'vverify', 'voicev', 'vv')) + @has_no_roles(Roles.voice_verified) + @in_whitelist(channels=(Channels.voice_gate,), redirect=None) + async def voice_verify(self, ctx: Context, *_) -> None: + """ + Apply to be able to use voice within the Discord server. + + In order to use voice you must meet all three of the following criteria: + - You must have over a certain number of messages within the Discord server + - You must have accepted our rules over a certain number of days ago + - You must not be actively banned from using our voice channels + """ + try: + data = await self.bot.api_client.get(f"bot/users/{ctx.author.id}/metricity_data") + except ResponseCodeError as e: + if e.status == 404: + await ctx.send(f":x: {ctx.author.mention} Unable to find Metricity data about you.") + log.info(f"Unable to find Metricity data about {ctx.author} ({ctx.author.id})") + else: + log.warning(f"Got response code {e.status} while trying to get {ctx.author.id} metricity data.") + await ctx.send(f":x: Got unexpected response from site. Please let us know about this.") + return + + # Pre-parse this for better code style + data["verified_at"] = parser.isoparse(data["verified_at"]) + + failed = False + failed_reasons = [] + + if data["verified_at"] > datetime.utcnow() - timedelta(days=VoiceGateConf.minimum_days_verified): + failed_reasons.append(NOT_ENOUGH_DAYS_AFTER_VERIFICATION) + failed = True + self.bot.stats.incr("voice_gate.failed.verified_at") + if data["total_messages"] < VoiceGateConf.minimum_messages: + failed_reasons.append(NOT_ENOUGH_MESSAGES) + failed = True + self.bot.stats.incr("voice_gate.failed.total_messages") + if data["voice_banned"]: + failed_reasons.append(VOICE_BANNED) + failed = True + self.bot.stats.incr("voice_gate.failed.voice_banned") + + if failed: + if len(failed_reasons) > 1: + reasons = f"{', '.join(failed_reasons[:-1])} and {failed_reasons[-1]}" + else: + reasons = failed_reasons[0] + + await ctx.send( + FAILED_MESSAGE.format( + user=ctx.author.mention, + reasons=reasons + ) + ) + return + + self.mod_log.ignore(Event.member_update, ctx.author.id) + await ctx.author.add_roles(discord.Object(Roles.voice_verified), reason="Voice Gate passed") + await ctx.author.send( + ":tada: Congratulations! You are now Voice Verified and have access to PyDis Voice Channels." + ) + self.bot.stats.incr("voice_gate.passed") + + @Cog.listener() + async def on_message(self, message: discord.Message) -> None: + """Delete all non-staff messages from voice gate channel that don't invoke voice verify command.""" + # Check is channel voice gate + if message.channel.id != Channels.voice_gate: + return + + # When it's bot sent message, delete it after some time + if message.author.bot: + with suppress(discord.NotFound): + await message.delete(delay=VoiceGateConf.bot_message_delete_delay) + return + + # Then check is member moderator+, because we don't want to delete their messages. + if any(role.id in MODERATION_ROLES for role in message.author.roles): + log.trace(f"Excluding moderator message {message.id} from deletion in #{message.channel}.") + return + + self.mod_log.ignore(Event.message_delete, message.id) + with suppress(discord.NotFound): + await message.delete() + def setup(bot: Bot) -> None: """Loads the VoiceGate cog.""" |