diff --git a/cogs/moderation.py b/cogs/moderation.py index f8e8ecd..c0d78c7 100644 --- a/cogs/moderation.py +++ b/cogs/moderation.py @@ -164,6 +164,63 @@ async def _post_to_mod_channel(guild: discord.Guild, role: Optional[discord.Role return False +def _is_staff_member(member) -> bool: + """True if the message author is staff (admin/mod permissions).""" + perms = getattr(member, "guild_permissions", None) + if perms is None: + return False + return (perms.administrator or perms.manage_guild or perms.manage_roles + or perms.ban_members or perms.kick_members or perms.manage_messages) + + +async def purge_lounge_for_user(guild: discord.Guild, user: discord.abc.Snowflake, limit: int = 2000) -> int: + """Clear the absolved user's messages plus all staff/bot messages from the lounge. + + Messages from *other* excommunicated (non-staff) users are left untouched. + Returns the number of messages deleted (0 if the channel is missing or the + bot lacks Manage Messages / Read Message History). + """ + data = get_guild_data(guild.id) + ch_id = data.get("xcom_channel_id") + channel = guild.get_channel(ch_id) if ch_id else None + if not channel or not isinstance(channel, discord.TextChannel): + return 0 + + me = guild.me + + def _should_delete(msg: discord.Message) -> bool: + # The absolved user's own messages. + if msg.author.id == user.id: + return True + # Bot / system messages (welcome embeds, notices) count as admin-side. + if me is not None and msg.author.id == me.id: + return True + # Any staff/admin/witness messages. + if _is_staff_member(msg.author): + return True + # Otherwise it belongs to another excommunicated user — keep it. + return False + + try: + # bulk=True deletes messages <14 days old in batches and older ones + # individually; the check filters which messages are removed. + deleted = await channel.purge( + limit=limit, check=_should_delete, bulk=True, + reason=f"XCOM cleanup: removed {user} + staff messages on absolution", + ) + logger.info( + "Purged %d lounge messages (absolved=%s + staff/bot) in guild %s", + len(deleted), user.id, guild.id, + ) + return len(deleted) + except discord.Forbidden: + logger.warning("Missing permission to purge xcom-lounge in guild %s " + "(need Manage Messages + Read Message History)", guild.id) + except discord.HTTPException as err: + logger.warning("Failed to purge xcom-lounge in guild %s: %s", guild.id, err) + return 0 + + def _keyword_list_embed(guild: discord.Guild) -> discord.Embed: """Build an embed listing the guild's auto-score keywords.""" keywords = get_keywords(guild.id) @@ -483,8 +540,17 @@ class ModerationCog(commands.Cog): except discord.HTTPException: pass + # Clear this user's messages plus all staff/bot messages from the lounge, + # leaving other excommunicated users' messages intact. + purged = await purge_lounge_for_user(guild, user) + purge_note = ( + f" Cleared {purged} lounge message(s) (their posts + staff/bot messages)." + if purged else "" + ) + await interaction.followup.send( - f"✅ {user.mention} has been released from XCOM and restored to normal access. The [XCOM] mark remains until an admin removes it.", + f"✅ {user.mention} has been released from XCOM and restored to normal access. " + f"The [XCOM] mark remains until an admin removes it.{purge_note}", ephemeral=True ) @@ -604,8 +670,17 @@ class ModerationCog(commands.Cog): # Optional: remove from isolated list but keep the mark flag remove_isolated_user(guild.id, user.id) + # Clear this user's messages plus all staff/bot messages from the lounge, + # leaving other excommunicated users' messages intact. + purged = await purge_lounge_for_user(guild, user) + purge_note = ( + f" Cleared {purged} lounge message(s) (their posts + staff/bot messages)." + if purged else "" + ) + await interaction.followup.send( - f"✅ {user.mention} has been absolved ({points} points). The [XCOM] mark remains until manually removed.", + f"✅ {user.mention} has been absolved ({points} points). The [XCOM] mark remains until manually removed." + f"{purge_note}", ephemeral=True )