Files
Breehavior-Monitor/cogs/sentiment/actions.py
AJ Isaacs bf32a9536a feat: add server rule violation detection and compress prompts
- LLM now evaluates messages against numbered server rules and reports
  violated_rules in analysis output
- Warnings and mutes cite the specific rule(s) broken
- Rules extracted to prompts/rules.txt for prompt injection
- Personality prompts moved to prompts/personalities/ and compressed
  (~63% reduction across all prompt files)
- All prompt files tightened: removed redundancy, consolidated Do NOT
  sections, trimmed examples while preserving behavioral instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:14:35 -05:00

150 lines
5.2 KiB
Python

import asyncio
import logging
from datetime import timedelta
import discord
from cogs.sentiment.log_utils import log_action
from cogs.sentiment.state import save_user_state
logger = logging.getLogger("bcs.sentiment")
async def mute_user(
bot, message: discord.Message, score: float,
categories: list[str], db_message_id: int | None, dirty_users: set[int],
violated_rules: list[int] | None = None, rules_config: dict | None = None,
):
member = message.author
if not isinstance(member, discord.Member):
return
if not message.guild.me.guild_permissions.moderate_members:
logger.warning("Missing moderate_members permission, cannot mute.")
return
offense_num = bot.drama_tracker.record_offense(member.id)
timeout_config = bot.config.get("timeouts", {})
escalation = timeout_config.get("escalation_minutes", [5, 15, 30, 60])
idx = min(offense_num - 1, len(escalation) - 1)
duration_minutes = escalation[idx]
try:
await member.timeout(
timedelta(minutes=duration_minutes),
reason=f"BCS auto-mute: drama score {score:.2f}",
)
except discord.Forbidden:
logger.warning("Cannot timeout %s — role hierarchy issue.", member)
return
except discord.HTTPException as e:
logger.error("Failed to timeout %s: %s", member, e)
return
messages_config = bot.config.get("messages", {})
cat_str = ", ".join(c for c in categories if c != "none") or "general negativity"
# Build rule citation text
rules_text = ""
if violated_rules and rules_config:
rule_lines = [f"Rule {r}: {rules_config[r]}" for r in violated_rules if r in rules_config]
if rule_lines:
rules_text = "\n".join(rule_lines)
description = messages_config.get("mute_description", "").format(
username=member.display_name,
duration=f"{duration_minutes} minutes",
score=f"{score:.2f}",
categories=cat_str,
)
if rules_text:
description += f"\n\nRules violated:\n{rules_text}"
embed = discord.Embed(
title=messages_config.get("mute_title", "BREEHAVIOR ALERT"),
description=description,
color=discord.Color.red(),
)
embed.set_footer(
text=f"Offense #{offense_num} | Timeout: {duration_minutes}m"
)
await message.channel.send(embed=embed)
rules_log = f" | Rules: {','.join(str(r) for r in violated_rules)}" if violated_rules else ""
await log_action(
message.guild,
f"**MUTE** | {member.mention} | Score: {score:.2f} | "
f"Duration: {duration_minutes}m | Offense #{offense_num} | "
f"Categories: {cat_str}{rules_log}",
)
logger.info(
"Muted %s for %d minutes (offense #%d, score %.2f, rules=%s)",
member, duration_minutes, offense_num, score,
violated_rules or [],
)
rules_detail = f" rules={','.join(str(r) for r in violated_rules)}" if violated_rules else ""
asyncio.create_task(bot.db.save_action(
guild_id=message.guild.id,
user_id=member.id,
username=member.display_name,
action_type="mute",
message_id=db_message_id,
details=f"duration={duration_minutes}m offense={offense_num} score={score:.2f} categories={cat_str}{rules_detail}",
))
save_user_state(bot, dirty_users, member.id)
async def warn_user(
bot, message: discord.Message, score: float,
db_message_id: int | None, dirty_users: set[int],
violated_rules: list[int] | None = None, rules_config: dict | None = None,
):
timeout_config = bot.config.get("timeouts", {})
cooldown = timeout_config.get("warning_cooldown_minutes", 5)
if not bot.drama_tracker.can_warn(message.author.id, cooldown):
return
bot.drama_tracker.record_warning(message.author.id)
try:
await message.add_reaction("\u26a0\ufe0f")
except discord.HTTPException:
pass
messages_config = bot.config.get("messages", {})
warning_text = messages_config.get(
"warning",
"Easy there, {username}. The Breehavior Monitor is watching.",
).format(username=message.author.display_name)
# Append rule citation if rules were violated
if violated_rules and rules_config:
rule_lines = [f"Rule {r}: {rules_config[r]}" for r in violated_rules if r in rules_config]
if rule_lines:
warning_text += "\n" + " | ".join(rule_lines)
await message.channel.send(warning_text)
rules_log = f" | Rules: {','.join(str(r) for r in violated_rules)}" if violated_rules else ""
await log_action(
message.guild,
f"**WARNING** | {message.author.mention} | Score: {score:.2f}{rules_log}",
)
logger.info("Warned %s (score %.2f, rules=%s)", message.author, score, violated_rules or [])
rules_detail = f" rules={','.join(str(r) for r in violated_rules)}" if violated_rules else ""
asyncio.create_task(bot.db.save_action(
guild_id=message.guild.id,
user_id=message.author.id,
username=message.author.display_name,
action_type="warning",
message_id=db_message_id,
details=f"score={score:.2f}{rules_detail}",
))
save_user_state(bot, dirty_users, message.author.id)