diff options
Diffstat (limited to 'bot/cogs/moderation/scheduler.py')
-rw-r--r-- | bot/cogs/moderation/scheduler.py | 96 |
1 files changed, 71 insertions, 25 deletions
diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 7990df226..01e4b1fe7 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -7,10 +7,11 @@ from gettext import ngettext import dateutil.parser import discord -from discord.ext.commands import Bot, Context +from discord.ext.commands import Context from bot import constants from bot.api import ResponseCodeError +from bot.bot import Bot from bot.constants import Colours, STAFF_CHANNELS from bot.utils import time from bot.utils.scheduling import Scheduler @@ -39,6 +40,8 @@ class InfractionScheduler(Scheduler): """Schedule expiration for previous infractions.""" await self.bot.wait_until_ready() + log.trace(f"Rescheduling infractions for {self.__class__.__name__}.") + infractions = await self.bot.api_client.get( 'bot/infractions', params={'active': 'true'} @@ -59,6 +62,10 @@ class InfractionScheduler(Scheduler): # Mark as inactive if less than a minute remains. if delta < 60: + log.info( + "Infraction will be deactivated instead of re-applied " + "because less than 1 minute remains." + ) await self.deactivate_infraction(infraction) return @@ -77,10 +84,10 @@ class InfractionScheduler(Scheduler): infr_type = infraction["type"] icon = utils.INFRACTION_ICONS[infr_type][0] reason = infraction["reason"] - expiry = infraction["expires_at"] + expiry = time.format_infraction_with_duration(infraction["expires_at"]) + id_ = infraction['id'] - if expiry: - expiry = time.format_infraction(expiry) + log.trace(f"Applying {infr_type} infraction #{id_} to {user}.") # Default values for the confirmation message and mod log. confirm_msg = f":ok_hand: applied" @@ -107,14 +114,24 @@ class InfractionScheduler(Scheduler): dm_result = ":incoming_envelope: " dm_log_text = "\nDM: Sent" else: + dm_result = f"{constants.Emojis.failmail} " dm_log_text = "\nDM: **Failed**" - log_content = ctx.author.mention if infraction["actor"] == self.bot.user.id: + log.trace( + f"Infraction #{id_} actor is bot; including the reason in the confirmation message." + ) + end_msg = f" (reason: {infraction['reason']})" elif ctx.channel.id not in STAFF_CHANNELS: + log.trace( + f"Infraction #{id_} context is not in a staff channel; omitting infraction count." + ) + end_msg = "" else: + log.trace(f"Fetching total infraction count for {user}.") + infractions = await self.bot.api_client.get( "bot/infractions", params={"user__id": str(user.id)} @@ -124,24 +141,33 @@ class InfractionScheduler(Scheduler): # Execute the necessary actions to apply the infraction on Discord. if action_coro: + log.trace(f"Awaiting the infraction #{id_} application action coroutine.") try: await action_coro if expiry: # Schedule the expiration of the infraction. self.schedule_task(ctx.bot.loop, infraction["id"], infraction) - except discord.Forbidden: + except discord.HTTPException as e: # Accordingly display that applying the infraction failed. confirm_msg = f":x: failed to apply" expiry_msg = "" log_content = ctx.author.mention log_title = "failed to apply" + log_msg = f"Failed to apply {infr_type} infraction #{id_} to {user}" + if isinstance(e, discord.Forbidden): + log.warning(f"{log_msg}: bot lacks permissions.") + else: + log.exception(log_msg) + # Send a confirmation message to the invoking context. + log.trace(f"Sending infraction #{id_} confirmation message.") await ctx.send( f"{dm_result}{confirm_msg} **{infr_type}** to {user.mention}{expiry_msg}{end_msg}." ) # Send a log message to the mod log. + log.trace(f"Sending apply mod log for infraction #{id_}.") await self.mod_log.send_log_message( icon_url=icon, colour=Colours.soft_red, @@ -157,9 +183,14 @@ class InfractionScheduler(Scheduler): footer=f"ID {infraction['id']}" ) + log.info(f"Applied {infr_type} infraction #{id_} to {user}.") + async def pardon_infraction(self, ctx: Context, infr_type: str, user: MemberObject) -> None: """Prematurely end an infraction for a user and log the action in the mod log.""" + log.trace(f"Pardoning {infr_type} infraction for {user}.") + # Check the current active infraction + log.trace(f"Fetching active {infr_type} infractions for {user}.") response = await self.bot.api_client.get( 'bot/infractions', params={ @@ -170,6 +201,7 @@ class InfractionScheduler(Scheduler): ) if not response: + log.debug(f"No active {infr_type} infraction found for {user}.") await ctx.send(f":x: There's no active {infr_type} infraction for user {user.mention}.") return @@ -179,12 +211,16 @@ class InfractionScheduler(Scheduler): log_text["Member"] = f"{user.mention}(`{user.id}`)" log_text["Actor"] = str(ctx.message.author) log_content = None - footer = f"ID: {response[0]['id']}" + id_ = response[0]['id'] + footer = f"ID: {id_}" # If multiple active infractions were found, mark them as inactive in the database # and cancel their expiration tasks. if len(response) > 1: - log.warning(f"Found more than one active {infr_type} infraction for user {user.id}") + log.warning( + f"Found more than one active {infr_type} infraction for user {user.id}; " + "deactivating the extra active infractions too." + ) footer = f"Infraction IDs: {', '.join(str(infr['id']) for infr in response)}" @@ -198,15 +234,15 @@ class InfractionScheduler(Scheduler): # 1. Discord cannot store multiple active bans or assign multiples of the same role # 2. It would send a pardon DM for each active infraction, which is redundant for infraction in response[1:]: - _id = infraction['id'] + id_ = infraction['id'] try: # Mark infraction as inactive in the database. await self.bot.api_client.patch( - f"bot/infractions/{_id}", + f"bot/infractions/{id_}", json={"active": False} ) except ResponseCodeError: - log.exception(f"Failed to deactivate infraction #{_id} ({infr_type})") + log.exception(f"Failed to deactivate infraction #{id_} ({infr_type})") # This is simpler and cleaner than trying to concatenate all the errors. log_text["Failure"] = "See bot's logs for details." @@ -219,19 +255,23 @@ class InfractionScheduler(Scheduler): if log_text.get("DM") == "Sent": dm_emoji = ":incoming_envelope: " elif "DM" in log_text: - # Mention the actor because the DM failed to send. - log_content = ctx.author.mention + dm_emoji = f"{constants.Emojis.failmail} " # Accordingly display whether the pardon failed. if "Failure" in log_text: confirm_msg = ":x: failed to pardon" log_title = "pardon failed" log_content = ctx.author.mention + + log.warning(f"Failed to pardon {infr_type} infraction #{id_} for {user}.") else: confirm_msg = f":ok_hand: pardoned" log_title = "pardoned" + log.info(f"Pardoned {infr_type} infraction #{id_} for {user}.") + # Send a confirmation message to the invoking context. + log.trace(f"Sending infraction #{id_} pardon confirmation message.") await ctx.send( f"{dm_emoji}{confirm_msg} infraction **{infr_type}** for {user.mention}. " f"{log_text.get('Failure', '')}" @@ -265,10 +305,10 @@ class InfractionScheduler(Scheduler): guild = self.bot.get_guild(constants.Guild.id) mod_role = guild.get_role(constants.Roles.moderator) user_id = infraction["user"] - _type = infraction["type"] - _id = infraction["id"] + type_ = infraction["type"] + id_ = infraction["id"] - log.debug(f"Marking infraction #{_id} as inactive (expired).") + log.info(f"Marking infraction #{id_} as inactive (expired).") log_content = None log_text = { @@ -278,24 +318,28 @@ class InfractionScheduler(Scheduler): } try: + log.trace("Awaiting the pardon action coroutine.") returned_log = await self._pardon_action(infraction) + if returned_log is not None: log_text = {**log_text, **returned_log} # Merge the logs together else: raise ValueError( - f"Attempted to deactivate an unsupported infraction #{_id} ({_type})!" + f"Attempted to deactivate an unsupported infraction #{id_} ({type_})!" ) except discord.Forbidden: - log.warning(f"Failed to deactivate infraction #{_id} ({_type}): bot lacks permissions") + log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions.") log_text["Failure"] = f"The bot lacks permissions to do this (role hierarchy?)" log_content = mod_role.mention except discord.HTTPException as e: - log.exception(f"Failed to deactivate infraction #{_id} ({_type})") - log_text["Failure"] = f"HTTPException with code {e.code}." + log.exception(f"Failed to deactivate infraction #{id_} ({type_})") + log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}." log_content = mod_role.mention # Check if the user is currently being watched by Big Brother. try: + log.trace(f"Determining if user {user_id} is currently being watched by Big Brother.") + active_watch = await self.bot.api_client.get( "bot/infractions", params={ @@ -312,12 +356,13 @@ class InfractionScheduler(Scheduler): try: # Mark infraction as inactive in the database. + log.trace(f"Marking infraction #{id_} as inactive in the database.") await self.bot.api_client.patch( - f"bot/infractions/{_id}", + f"bot/infractions/{id_}", json={"active": False} ) except ResponseCodeError as e: - log.exception(f"Failed to deactivate infraction #{_id} ({_type})") + log.exception(f"Failed to deactivate infraction #{id_} ({type_})") log_line = f"API request failed with code {e.status}." log_content = mod_role.mention @@ -335,12 +380,13 @@ class InfractionScheduler(Scheduler): if send_log: log_title = f"expiration failed" if "Failure" in log_text else "expired" + log.trace(f"Sending deactivation mod log for infraction #{id_}.") await self.mod_log.send_log_message( - icon_url=utils.INFRACTION_ICONS[_type][1], + icon_url=utils.INFRACTION_ICONS[type_][1], colour=Colours.soft_green, - title=f"Infraction {log_title}: {_type}", + title=f"Infraction {log_title}: {type_}", text="\n".join(f"{k}: {v}" for k, v in log_text.items()), - footer=f"ID: {_id}", + footer=f"ID: {id_}", content=log_content, ) |