patched vow system
This commit is contained in:
@@ -30,6 +30,8 @@ from utils.config import (
|
||||
from cogs.moderation import (
|
||||
get_or_create_xcom_role,
|
||||
get_or_create_xcom_channel,
|
||||
get_or_create_xcom_mod_channel,
|
||||
_post_to_mod_channel,
|
||||
VOTE_XCOM_THRESHOLD,
|
||||
VOTE_RESET_DAYS,
|
||||
)
|
||||
@@ -157,9 +159,9 @@ class ExcommunicadoBot(commands.Bot):
|
||||
result = add_keyword_points(
|
||||
message.guild.id, author.id, matched_weight, reset_days=VOTE_RESET_DAYS
|
||||
)
|
||||
print(
|
||||
f"[KEYWORD] {author} matched {matched_terms} (+{matched_weight}) "
|
||||
f"-> score {result['score']} in {message.guild.name}"
|
||||
logger.info(
|
||||
"Keyword match: user=%s terms=%s (+%d) -> score=%d guild=%s",
|
||||
author, matched_terms, matched_weight, result["score"], message.guild.name,
|
||||
)
|
||||
me = message.guild.me
|
||||
can_manage = me is not None and author.top_role < me.top_role
|
||||
@@ -178,10 +180,15 @@ class ExcommunicadoBot(commands.Bot):
|
||||
timestamp=now,
|
||||
)
|
||||
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)
|
||||
logger.warning(
|
||||
"Keyword auto-XCOM: user=%s guild=%s score=%d",
|
||||
author, message.guild.name, result["score"],
|
||||
)
|
||||
return
|
||||
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.append(now)
|
||||
|
||||
+119
-12
@@ -9,6 +9,8 @@ Commands:
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
@@ -28,6 +30,8 @@ from utils.config import (
|
||||
get_keywords,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("excommunicado.moderation")
|
||||
|
||||
# --- Community vote configuration ---
|
||||
VOTE_XCOM_THRESHOLD = 10 # net score required to auto-XCOM a user
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
"""Build an embed listing the guild's auto-score keywords."""
|
||||
keywords = get_keywords(guild.id)
|
||||
@@ -274,6 +351,10 @@ class ModerationCog(commands.Cog):
|
||||
guild = interaction.guild
|
||||
role = await get_or_create_xcom_role(guild)
|
||||
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(
|
||||
title="🪐 XCOM System Ready",
|
||||
@@ -281,10 +362,23 @@ class ModerationCog(commands.Cog):
|
||||
color=discord.Color.orange()
|
||||
)
|
||||
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(
|
||||
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
|
||||
)
|
||||
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
|
||||
data = get_guild_data(guild.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]:
|
||||
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)
|
||||
return
|
||||
|
||||
channel = guild.get_channel(xcom_channel_id) if xcom_channel_id else None
|
||||
if channel:
|
||||
vow_embed = discord.Embed(
|
||||
title="📜 XCOM Vow Submitted",
|
||||
description=f"**{interaction.user.mention}** has made the following vow:\n\n> {vow}",
|
||||
color=discord.Color.dark_red()
|
||||
role = guild.get_role(xcom_role_id)
|
||||
vow_embed = discord.Embed(
|
||||
title="📜 XCOM Vow Submitted",
|
||||
description=f"**{interaction.user.mention}** ({interaction.user.id}) has made the following vow:\n\n> {vow}",
|
||||
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:
|
||||
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.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="Distinct voters", value=str(unique_voters), inline=True)
|
||||
await _post_to_mod_channel(guild, role, log_embed)
|
||||
if hasattr(self.bot, "log_action"):
|
||||
await self.bot.log_action(guild, log_embed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user