patched vow system

This commit is contained in:
Subinacls
2026-06-09 07:39:43 -04:00
parent 154735e133
commit 3034a474be
2 changed files with 130 additions and 16 deletions
+11 -4
View File
@@ -30,6 +30,8 @@ from utils.config import (
from cogs.moderation import ( from cogs.moderation import (
get_or_create_xcom_role, get_or_create_xcom_role,
get_or_create_xcom_channel, get_or_create_xcom_channel,
get_or_create_xcom_mod_channel,
_post_to_mod_channel,
VOTE_XCOM_THRESHOLD, VOTE_XCOM_THRESHOLD,
VOTE_RESET_DAYS, VOTE_RESET_DAYS,
) )
@@ -157,9 +159,9 @@ class ExcommunicadoBot(commands.Bot):
result = add_keyword_points( result = add_keyword_points(
message.guild.id, author.id, matched_weight, reset_days=VOTE_RESET_DAYS message.guild.id, author.id, matched_weight, reset_days=VOTE_RESET_DAYS
) )
print( logger.info(
f"[KEYWORD] {author} matched {matched_terms} (+{matched_weight}) " "Keyword match: user=%s terms=%s (+%d) -> score=%d guild=%s",
f"-> score {result['score']} in {message.guild.name}" author, matched_terms, matched_weight, result["score"], message.guild.name,
) )
me = message.guild.me me = message.guild.me
can_manage = me is not None and author.top_role < me.top_role can_manage = me is not None and author.top_role < me.top_role
@@ -178,10 +180,15 @@ class ExcommunicadoBot(commands.Bot):
timestamp=now, timestamp=now,
) )
log_embed.add_field(name="Matched", value=", ".join(matched_terms[:10]) or "", inline=False) log_embed.add_field(name="Matched", value=", ".join(matched_terms[:10]) or "", inline=False)
await _post_to_mod_channel(message.guild, None, log_embed)
await self.log_action(message.guild, log_embed) await self.log_action(message.guild, log_embed)
logger.warning(
"Keyword auto-XCOM: user=%s guild=%s score=%d",
author, message.guild.name, result["score"],
)
return return
except Exception as kw_err: except Exception as kw_err:
print(f"[KEYWORD] Auto-XCOM failed: {kw_err}") logger.exception("Keyword auto-XCOM failed: %s", kw_err)
times = USER_MESSAGE_TIMES[message.author.id] times = USER_MESSAGE_TIMES[message.author.id]
times.append(now) times.append(now)
+117 -10
View File
@@ -9,6 +9,8 @@ Commands:
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import Optional
import logging
import discord import discord
from discord.ext import commands from discord.ext import commands
from discord import app_commands from discord import app_commands
@@ -28,6 +30,8 @@ from utils.config import (
get_keywords, get_keywords,
) )
logger = logging.getLogger("excommunicado.moderation")
# --- Community vote configuration --- # --- Community vote configuration ---
VOTE_XCOM_THRESHOLD = 10 # net score required to auto-XCOM a user VOTE_XCOM_THRESHOLD = 10 # net score required to auto-XCOM a user
VOTE_ADMIN_WEIGHT = 2 # admins / moderators count double VOTE_ADMIN_WEIGHT = 2 # admins / moderators count double
@@ -87,6 +91,79 @@ async def get_or_create_xcom_channel(guild: discord.Guild, role: discord.Role) -
return channel return channel
def _staff_roles(guild: discord.Guild):
"""Yield roles that should be treated as moderation staff.
A role counts as staff if it grants administrator or any common
moderation permission. Used to auto-grant access to the private
xcom-mod channel.
"""
for r in guild.roles:
p = r.permissions
if (p.administrator or p.manage_guild or p.manage_roles
or p.ban_members or p.kick_members or p.manage_messages):
yield r
async def get_or_create_xcom_mod_channel(guild: discord.Guild, role: Optional[discord.Role] = None) -> discord.TextChannel:
"""Get or create the private 'xcom-mod' staff-only review channel.
This channel is hidden from everyone (including the Excommunicado role) and
is where the bot relays vows, vote/keyword auto-XCOM alerts, and other
moderation notices. Isolated users can never see it; the bot posts on their
behalf, so they never need write access here.
"""
if role is None:
role = await get_or_create_xcom_role(guild)
data = get_guild_data(guild.id)
ch_id = data.get("xcom_mod_channel_id")
if ch_id:
channel = guild.get_channel(ch_id)
if channel and isinstance(channel, discord.TextChannel):
return channel
overwrites = {
# Hidden from regular members...
guild.default_role: discord.PermissionOverwrite(view_channel=False),
# ...and explicitly hidden from isolated users.
role: discord.PermissionOverwrite(view_channel=False),
# The bot must be able to post relayed vows and alerts here.
guild.me: discord.PermissionOverwrite(
view_channel=True, send_messages=True, embed_links=True, read_message_history=True
),
}
# Grant staff roles read/write access.
for staff_role in _staff_roles(guild):
overwrites[staff_role] = discord.PermissionOverwrite(
view_channel=True, send_messages=True, read_message_history=True, embed_links=True
)
channel = await guild.create_text_channel(
name="xcom-mod",
overwrites=overwrites,
topic="Private staff channel: XCOM vows, auto-XCOM alerts, and moderation review. Not visible to isolated users.",
reason="Creating private staff review channel for Excommunicado",
)
update_guild_data(guild.id, "xcom_mod_channel_id", channel.id)
return channel
async def _post_to_mod_channel(guild: discord.Guild, role: Optional[discord.Role], embed: discord.Embed) -> bool:
"""Relay an embed to the private xcom-mod channel, creating it if needed.
Returns True if the message was delivered.
"""
try:
channel = await get_or_create_xcom_mod_channel(guild, role)
await channel.send(embed=embed)
return True
except discord.Forbidden:
logger.warning("Missing permission to create/post in xcom-mod for guild %s", guild.id)
except discord.HTTPException as err:
logger.warning("Failed to post to xcom-mod for guild %s: %s", guild.id, err)
return False
def _keyword_list_embed(guild: discord.Guild) -> discord.Embed: def _keyword_list_embed(guild: discord.Guild) -> discord.Embed:
"""Build an embed listing the guild's auto-score keywords.""" """Build an embed listing the guild's auto-score keywords."""
keywords = get_keywords(guild.id) keywords = get_keywords(guild.id)
@@ -274,6 +351,10 @@ class ModerationCog(commands.Cog):
guild = interaction.guild guild = interaction.guild
role = await get_or_create_xcom_role(guild) role = await get_or_create_xcom_role(guild)
channel = await get_or_create_xcom_channel(guild, role) channel = await get_or_create_xcom_channel(guild, role)
try:
mod_channel = await get_or_create_xcom_mod_channel(guild, role)
except discord.Forbidden:
mod_channel = None
embed = discord.Embed( embed = discord.Embed(
title="🪐 XCOM System Ready", title="🪐 XCOM System Ready",
@@ -281,10 +362,23 @@ class ModerationCog(commands.Cog):
color=discord.Color.orange() color=discord.Color.orange()
) )
embed.add_field(name="Role Created", value=f"{role.mention} (ID: {role.id})", inline=True) embed.add_field(name="Role Created", value=f"{role.mention} (ID: {role.id})", inline=True)
embed.add_field(name="Channel Created", value=f"{channel.mention}", inline=True) embed.add_field(name="Lounge Channel", value=f"{channel.mention}", inline=True)
embed.add_field(
name="Staff Review Channel",
value=(mod_channel.mention if mod_channel
else "⚠️ Could not create `xcom-mod` (missing Manage Channels permission)."),
inline=True,
)
embed.add_field( embed.add_field(
name="⚠️ Stealth Mode Active", name="⚠️ Stealth Mode Active",
value="The **Excommunicado** role is now only a marker + grants access to the shared moderated room.\n\n**Do NOT** deny View/Send on public channels (user must keep seeing the normal server).\nNormal members cannot see xcom-lounge (already configured).\n\nStaff members you want to 'share the room' with the isolated user can be given the Excommunicado role manually.", value=(
"The **Excommunicado** role is now only a marker + grants access to the shared moderated room.\n\n"
"**Do NOT** deny View/Send on public channels (user must keep seeing the normal server).\n"
"Normal members cannot see xcom-lounge (already configured).\n\n"
"Staff members you want to 'share the room' with the isolated user can be given the Excommunicado role manually.\n\n"
"🔒 **xcom-mod** is a private staff channel (hidden from members and isolated users). "
"The bot relays vows and auto-XCOM alerts there — isolated users never see it."
),
inline=False inline=False
) )
embed.set_footer(text="After setup, use /xcom @user to excommunicate offenders. They will not realize they have been moved.") embed.set_footer(text="After setup, use /xcom @user to excommunicate offenders. They will not realize they have been moved.")
@@ -417,7 +511,6 @@ class ModerationCog(commands.Cog):
guild = interaction.guild guild = interaction.guild
data = get_guild_data(guild.id) data = get_guild_data(guild.id)
xcom_role_id = data.get("xcom_role_id") xcom_role_id = data.get("xcom_role_id")
xcom_channel_id = data.get("xcom_channel_id")
if not xcom_role_id or xcom_role_id not in [r.id for r in interaction.user.roles]: if not xcom_role_id or xcom_role_id not in [r.id for r in interaction.user.roles]:
await interaction.response.send_message("You must be under XCOM to submit a vow.", ephemeral=True) await interaction.response.send_message("You must be under XCOM to submit a vow.", ephemeral=True)
@@ -427,17 +520,30 @@ class ModerationCog(commands.Cog):
await interaction.response.send_message("Vow is too long (max 200 characters).", ephemeral=True) await interaction.response.send_message("Vow is too long (max 200 characters).", ephemeral=True)
return return
channel = guild.get_channel(xcom_channel_id) if xcom_channel_id else None role = guild.get_role(xcom_role_id)
if channel:
vow_embed = discord.Embed( vow_embed = discord.Embed(
title="📜 XCOM Vow Submitted", title="📜 XCOM Vow Submitted",
description=f"**{interaction.user.mention}** has made the following vow:\n\n> {vow}", description=f"**{interaction.user.mention}** ({interaction.user.id}) has made the following vow:\n\n> {vow}",
color=discord.Color.dark_red() color=discord.Color.dark_red(),
timestamp=datetime.now(timezone.utc),
)
vow_embed.set_footer(text="Relayed by the bot to staff — the user cannot see this channel.")
# The bot relays the vow into the private staff channel, so the isolated
# user never needs (or gets) access to xcom-mod themselves.
delivered = await _post_to_mod_channel(guild, role, vow_embed) if role else False
if delivered:
logger.info("Vow relayed to xcom-mod: guild=%s user=%s", guild.id, interaction.user.id)
await interaction.response.send_message(
"Your vow has been submitted to staff for review.", ephemeral=True
) )
await channel.send(embed=vow_embed)
await interaction.response.send_message("Your vow has been submitted to staff.", ephemeral=True)
else: else:
await interaction.response.send_message("XCOM lounge not found.", ephemeral=True) await interaction.response.send_message(
"Could not deliver your vow — staff have been unable to set up the review channel. "
"Please try again later.",
ephemeral=True,
)
@app_commands.command(name="xcom-witness", description="Add a staff member to the xcom-lounge so they can support a specific user") @app_commands.command(name="xcom-witness", description="Add a staff member to the xcom-lounge so they can support a specific user")
@app_commands.checks.has_permissions(manage_roles=True) @app_commands.checks.has_permissions(manage_roles=True)
@@ -618,6 +724,7 @@ class ModerationCog(commands.Cog):
) )
log_embed.add_field(name="Final score", value=str(score), inline=True) log_embed.add_field(name="Final score", value=str(score), inline=True)
log_embed.add_field(name="Distinct voters", value=str(unique_voters), inline=True) log_embed.add_field(name="Distinct voters", value=str(unique_voters), inline=True)
await _post_to_mod_channel(guild, role, log_embed)
if hasattr(self.bot, "log_action"): if hasattr(self.bot, "log_action"):
await self.bot.log_action(guild, log_embed) await self.bot.log_action(guild, log_embed)