diff options
| -rw-r--r-- | bot/cogs/eval.py | 4 | ||||
| -rw-r--r-- | bot/cogs/filtering.py | 8 | ||||
| -rw-r--r-- | bot/cogs/moderation/scheduler.py | 77 | ||||
| -rw-r--r-- | bot/cogs/moderation/superstarify.py | 21 | ||||
| -rw-r--r-- | bot/cogs/moderation/utils.py | 12 | ||||
| -rw-r--r-- | bot/interpreter.py | 4 | ||||
| -rw-r--r-- | bot/utils/scheduling.py | 6 |
7 files changed, 102 insertions, 30 deletions
diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 9ce854f2c..00b988dde 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -148,7 +148,7 @@ class CodeEval(Cog): self.env.update(env) # Ignore this code, it works - _code = """ + code_ = """ async def func(): # (None,) -> Any try: with contextlib.redirect_stdout(self.stdout): @@ -162,7 +162,7 @@ async def func(): # (None,) -> Any """.format(textwrap.indent(code, ' ')) try: - exec(_code, self.env) # noqa: B102,S102 + exec(code_, self.env) # noqa: B102,S102 func = self.env['func'] res = await func() diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 4195783f1..1e7521054 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -43,7 +43,7 @@ class Filtering(Cog): def __init__(self, bot: Bot): self.bot = bot - _staff_mistake_str = "If you believe this was a mistake, please let staff know!" + staff_mistake_str = "If you believe this was a mistake, please let staff know!" self.filters = { "filter_zalgo": { "enabled": Filter.filter_zalgo, @@ -53,7 +53,7 @@ class Filtering(Cog): "user_notification": Filter.notify_user_zalgo, "notification_msg": ( "Your post has been removed for abusing Unicode character rendering (aka Zalgo text). " - f"{_staff_mistake_str}" + f"{staff_mistake_str}" ) }, "filter_invites": { @@ -63,7 +63,7 @@ class Filtering(Cog): "content_only": True, "user_notification": Filter.notify_user_invites, "notification_msg": ( - f"Per Rule 6, your invite link has been removed. {_staff_mistake_str}\n\n" + f"Per Rule 6, your invite link has been removed. {staff_mistake_str}\n\n" r"Our server rules can be found here: <https://pythondiscord.com/pages/rules>" ) }, @@ -74,7 +74,7 @@ class Filtering(Cog): "content_only": True, "user_notification": Filter.notify_user_domains, "notification_msg": ( - f"Your URL has been removed because it matched a blacklisted domain. {_staff_mistake_str}" + f"Your URL has been removed because it matched a blacklisted domain. {staff_mistake_str}" ) }, "watch_rich_embeds": { diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 7990df226..49b61f35e 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -39,6 +39,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 +61,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 @@ -78,6 +84,9 @@ class InfractionScheduler(Scheduler): icon = utils.INFRACTION_ICONS[infr_type][0] reason = infraction["reason"] expiry = infraction["expires_at"] + id_ = infraction['id'] + + log.trace(f"Applying {infr_type} infraction #{id_} to {user}.") if expiry: expiry = time.format_infraction(expiry) @@ -111,10 +120,20 @@ class InfractionScheduler(Scheduler): 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,6 +143,7 @@ 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: @@ -136,12 +156,16 @@ class InfractionScheduler(Scheduler): log_content = ctx.author.mention log_title = "failed to apply" + log.warning(f"Failed to apply {infr_type} infraction #{id_} to {user}.") + # 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 +181,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 +199,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 +209,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 +232,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." @@ -227,11 +261,16 @@ class InfractionScheduler(Scheduler): 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 +304,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 +317,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.exception(f"Failed to deactivate infraction #{id_} ({type_})") log_text["Failure"] = f"HTTPException with 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 +355,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 +379,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, ) diff --git a/bot/cogs/moderation/superstarify.py b/bot/cogs/moderation/superstarify.py index c66222e5a..9b3c62403 100644 --- a/bot/cogs/moderation/superstarify.py +++ b/bot/cogs/moderation/superstarify.py @@ -34,8 +34,8 @@ class Superstarify(InfractionScheduler, Cog): return # User didn't change their nickname. Abort! log.trace( - f"{before.display_name} is trying to change their nickname to {after.display_name}. " - "Checking if the user is in superstar-prison..." + f"{before} ({before.display_name}) is trying to change their nickname to " + f"{after.display_name}. Checking if the user is in superstar-prison..." ) active_superstarifies = await self.bot.api_client.get( @@ -48,6 +48,7 @@ class Superstarify(InfractionScheduler, Cog): ) if not active_superstarifies: + log.trace(f"{before} has no active superstar infractions.") return infraction = active_superstarifies[0] @@ -132,15 +133,17 @@ class Superstarify(InfractionScheduler, Cog): # Post the infraction to the API reason = reason or f"old nick: {member.display_name}" infraction = await utils.post_infraction(ctx, member, "superstar", reason, duration) + id_ = infraction["id"] old_nick = member.display_name - forced_nick = self.get_nick(infraction["id"], member.id) + forced_nick = self.get_nick(id_, member.id) expiry_str = format_infraction(infraction["expires_at"]) # Apply the infraction and schedule the expiration task. + log.debug(f"Changing nickname of {member} to {forced_nick}.") self.mod_log.ignore(constants.Event.member_update, member.id) await member.edit(nick=forced_nick, reason=reason) - self.schedule_task(ctx.bot.loop, infraction["id"], infraction) + self.schedule_task(ctx.bot.loop, id_, infraction) # Send a DM to the user to notify them of their new infraction. await utils.notify_infraction( @@ -152,6 +155,7 @@ class Superstarify(InfractionScheduler, Cog): ) # Send an embed with the infraction information to the invoking context. + log.trace(f"Sending superstar #{id_} embed.") embed = Embed( title="Congratulations!", colour=constants.Colours.soft_orange, @@ -167,6 +171,7 @@ class Superstarify(InfractionScheduler, Cog): await ctx.send(embed=embed) # Log to the mod log channel. + log.trace(f"Sending apply mod log for superstar #{id_}.") await self.mod_log.send_log_message( icon_url=utils.INFRACTION_ICONS["superstar"][0], colour=Colour.gold(), @@ -180,7 +185,7 @@ class Superstarify(InfractionScheduler, Cog): Old nickname: `{old_nick}` New nickname: `{forced_nick}` """), - footer=f"ID {infraction['id']}" + footer=f"ID {id_}" ) @command(name="unsuperstarify", aliases=("release_nick", "unstar")) @@ -198,6 +203,10 @@ class Superstarify(InfractionScheduler, Cog): # Don't bother sending a notification if the user left the guild. if not user: + log.debug( + "User left the guild and therefore won't be notified about superstar " + f"{infraction['id']} pardon." + ) return {} # DM the user about the expiration. @@ -216,6 +225,8 @@ class Superstarify(InfractionScheduler, Cog): @staticmethod def get_nick(infraction_id: int, member_id: int) -> str: """Randomly select a nickname from the Superstarify nickname list.""" + log.trace(f"Choosing a random nickname for superstar #{infraction_id}.") + rng = random.Random(str(infraction_id) + str(member_id)) return rng.choice(STAR_NAMES) diff --git a/bot/cogs/moderation/utils.py b/bot/cogs/moderation/utils.py index 9179c0afb..325b9567a 100644 --- a/bot/cogs/moderation/utils.py +++ b/bot/cogs/moderation/utils.py @@ -37,6 +37,8 @@ def proxy_user(user_id: str) -> discord.Object: Used when a Member or User object cannot be resolved. """ + log.trace(f"Attempting to create a proxy user for the user id {user_id}.") + try: user_id = int(user_id) except ValueError: @@ -59,6 +61,8 @@ async def post_infraction( active: bool = True, ) -> t.Optional[dict]: """Posts an infraction to the API.""" + log.trace(f"Posting {infr_type} infraction for {user} to the API.") + payload = { "actor": ctx.message.author.id, "hidden": hidden, @@ -92,6 +96,8 @@ async def post_infraction( async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str) -> bool: """Checks if a user already has an active infraction of the given type.""" + log.trace(f"Checking if {user} has active infractions of type {infr_type}.") + active_infractions = await ctx.bot.api_client.get( 'bot/infractions', params={ @@ -101,12 +107,14 @@ async def has_active_infraction(ctx: Context, user: MemberObject, infr_type: str } ) if active_infractions: + log.trace(f"{user} has active infractions of type {infr_type}.") await ctx.send( f":x: According to my records, this user already has a {infr_type} infraction. " f"See infraction **#{active_infractions[0]['id']}**." ) return True else: + log.trace(f"{user} does not have active infractions of type {infr_type}.") return False @@ -118,6 +126,8 @@ async def notify_infraction( icon_url: str = Icons.token_removed ) -> bool: """DM a user about their new infraction and return True if the DM is successful.""" + log.trace(f"Sending {user} a DM about their {infr_type} infraction.") + embed = discord.Embed( description=textwrap.dedent(f""" **Type:** {infr_type.capitalize()} @@ -146,6 +156,8 @@ async def notify_pardon( icon_url: str = Icons.user_verified ) -> bool: """DM a user about their pardoned infraction and return True if the DM is successful.""" + log.trace(f"Sending {user} a DM about their pardoned infraction.") + embed = discord.Embed( description=content, colour=Colours.soft_green diff --git a/bot/interpreter.py b/bot/interpreter.py index a42b45a2d..76a3fc293 100644 --- a/bot/interpreter.py +++ b/bot/interpreter.py @@ -20,8 +20,8 @@ class Interpreter(InteractiveInterpreter): write_callable = None def __init__(self, bot: Bot): - _locals = {"bot": bot} - super().__init__(_locals) + locals_ = {"bot": bot} + super().__init__(locals_) async def run(self, code: str, ctx: Context, io: StringIO, *args, **kwargs) -> Any: """Execute the provided source code as the bot & return the output.""" diff --git a/bot/utils/scheduling.py b/bot/utils/scheduling.py index 08abd91d7..ee6c0a8e6 100644 --- a/bot/utils/scheduling.py +++ b/bot/utils/scheduling.py @@ -36,11 +36,15 @@ class Scheduler(metaclass=CogABCMeta): `task_data` is passed to `Scheduler._scheduled_expiration` """ if task_id in self.scheduled_tasks: + log.debug( + f"{self.cog_name}: did not schedule task #{task_id}; task was already scheduled." + ) return task: asyncio.Task = create_task(loop, self._scheduled_task(task_data)) self.scheduled_tasks[task_id] = task + log.debug(f"{self.cog_name}: scheduled task #{task_id}.") def cancel_task(self, task_id: str) -> None: """Un-schedules a task.""" @@ -51,7 +55,7 @@ class Scheduler(metaclass=CogABCMeta): return task.cancel() - log.debug(f"{self.cog_name}: Unscheduled {task_id}.") + log.debug(f"{self.cog_name}: unscheduled task #{task_id}.") del self.scheduled_tasks[task_id] |