From ff8c0084d437d56d55f0a22a7f8614dd4031d080 Mon Sep 17 00:00:00 2001 From: momothereal Date: Sat, 21 Jul 2018 15:24:36 -0400 Subject: Add expiration scheduler + startup, tempmute command --- bot/cogs/moderation.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 1349d8607..48857faf3 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -1,8 +1,11 @@ +import asyncio +import datetime import logging from discord import Guild, Member, User from discord.ext.commands import Bot, Context, command +from bot import constants from bot.constants import Keys, Roles, URLs from bot.decorators import with_role @@ -18,6 +21,19 @@ class Moderation: self.bot = bot self.headers = {"X-API-KEY": Keys.site_api} + async def on_ready(self): + # Schedule expiration for previous infractions + response = await self.bot.http_session.get( + URLs.site_infractions, + params={"active": "true"}, + headers=self.headers + ) + infraction_list = await response.json() + loop = asyncio.get_event_loop() + for infraction_object in infraction_list: + if infraction_object["expires_at"] is not None: + loop.create_task(self._scheduled_expiration(infraction_object)) + @with_role(Roles.admin, Roles.owner, Roles.moderator) @command(name="moderation.warn") async def warn(self, ctx: Context, user: User, reason: str = None): @@ -40,7 +56,7 @@ class Moderation: ) except Exception: log.exception("There was an error adding an infraction.") - await ctx.send("There was an error adding the infraction.") + await ctx.send(":x: There was an error adding the infraction.") return if reason is None: @@ -71,11 +87,11 @@ class Moderation: ) except Exception: log.exception("There was an error adding an infraction.") - await ctx.send("There was an error adding the infraction.") + await ctx.send(":x: There was an error adding the infraction.") return guild: Guild = ctx.guild - await guild.ban(user, reason=reason, delete_message_days=0) + await guild.ban(user, reason=reason) if reason is None: result_message = f":ok_hand: permanently banned {user.mention}." @@ -86,12 +102,11 @@ class Moderation: @with_role(Roles.admin, Roles.owner, Roles.moderator) @command(name="moderation.mute") - async def mute(self, ctx: Context, user: Member, reason: str): + async def mute(self, ctx: Context, user: Member, reason: str = None): """ Create a permanent mute infraction in the database for a user. :param user: Accepts user mention, ID, etc. :param reason: Wrap in quotes to make reason larger than one word. - :param duration: Accepts #d, #h, #m, and #s. """ try: await self.bot.http_session.post( @@ -106,7 +121,7 @@ class Moderation: ) except Exception: log.exception("There was an error adding an infraction.") - await ctx.send("There was an error adding the infraction.") + await ctx.send(":x: There was an error adding the infraction.") return await user.edit(reason=reason, mute=True) @@ -118,6 +133,95 @@ class Moderation: await ctx.send(result_message) + @with_role(Roles.admin, Roles.owner, Roles.moderator) + @command(name="moderation.tempmute") + async def tempmute(self, ctx: Context, user: Member, duration: str, reason: str = None): + """ + Create a temporary mute infraction in the database for a user. + :param user: Accepts user mention, ID, etc. + :param duration: The duration for the temporary mute infraction + :param reason: Wrap in quotes to make reason larger than one word. + """ + try: + response = await self.bot.http_session.post( + URLs.site_infractions, + headers=self.headers, + json={ + "type": "mute", + "reason": reason, + "duration": duration, + "user_id": str(user.id), + "actor_id": str(ctx.message.author.id) + } + ) + except Exception: + log.exception("There was an error adding an infraction.") + await ctx.send(":x: There was an error adding the infraction.") + return + + response_object = await response.json() + if "error_code" in response_object: + # something went wrong + await ctx.send(f":x: There was an error adding the infraction: {response_object['error_message']}") + return + + await user.edit(reason=reason, mute=True) + + infraction_object = response_object["infraction"] + infraction_expiration = infraction_object["expires_at"] + + loop = asyncio.get_event_loop() + loop.create_task(self._scheduled_expiration(infraction_object)) + + if reason is None: + result_message = f":ok_hand: muted {user.mention} until {infraction_expiration}." + else: + result_message = f":ok_hand: muted {user.mention} until {infraction_expiration} ({reason})." + + await ctx.send(result_message) + + async def _scheduled_expiration(self, infraction_object): + guild: Guild = self.bot.get_guild(constants.Guild.id) + infraction_id = infraction_object["id"] + infraction_type = infraction_object["type"] + + # transform expiration to delay in seconds + expiration_datetime = parse_rfc1123(infraction_object["expires_at"]) + delay = expiration_datetime - datetime.datetime.now(tz=datetime.timezone.utc) + delay_seconds = delay.total_seconds() + + if delay_seconds > 1.0: + log.debug(f"Scheduling expiration for infraction {infraction_id} in {delay_seconds} seconds") + await asyncio.sleep(delay_seconds) + + log.debug(f"Marking infraction {infraction_id} as inactive (expired).") + log.debug(infraction_object) + user_id = infraction_object["user"]["user_id"] + + if infraction_type == "mute": + member: Member = guild.get_member(user_id) + if member: + await member.edit(mute=False) + elif infraction_type == "ban": + user: User = self.bot.get_user(user_id) + await guild.unban(user) + + await self.bot.http_session.patch( + URLs.site_infractions, + headers=self.headers, + json={ + "id": infraction_id, + "active": False + } + ) + + +RFC1123_FORMAT = "%a, %d %b %Y %H:%M:%S GMT" + + +def parse_rfc1123(time_str): + return datetime.datetime.strptime(time_str, RFC1123_FORMAT).replace(tzinfo=datetime.timezone.utc) + def setup(bot): bot.add_cog(Moderation(bot)) -- cgit v1.2.3