Add switchable bot modes: default, chatty, and roast

Adds a server-wide mode system with /bcs-mode command.
- Default: current hall-monitor behavior unchanged
- Chatty: friendly chat participant with proactive replies (~10% chance)
- Roast: savage roast mode with proactive replies
- Chatty/roast use relaxed moderation thresholds
- 5-message cooldown between proactive replies per channel
- Bot status updates to reflect active mode
- /bcs-status shows current mode and effective thresholds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 08:59:51 -05:00
parent 3f56982a83
commit 13a2030021
7 changed files with 262 additions and 12 deletions

View File

@@ -109,6 +109,24 @@ class CommandsCog(commands.Cog):
title="BCS Status",
color=discord.Color.green() if enabled else discord.Color.greyple(),
)
mode_config = self.bot.get_mode_config()
mode_label = mode_config.get("label", self.bot.current_mode)
moderation_level = mode_config.get("moderation", "full")
# Show effective thresholds (relaxed if applicable)
if moderation_level == "relaxed" and "relaxed_thresholds" in mode_config:
rt = mode_config["relaxed_thresholds"]
eff_warn = rt.get("warning_threshold", 0.80)
eff_mute = rt.get("mute_threshold", 0.85)
else:
eff_warn = sentiment.get("warning_threshold", 0.6)
eff_mute = sentiment.get("mute_threshold", 0.75)
embed.add_field(
name="Mode",
value=f"{mode_label} ({moderation_level})",
inline=True,
)
embed.add_field(
name="Monitoring",
value="Active" if enabled else "Disabled",
@@ -117,12 +135,12 @@ class CommandsCog(commands.Cog):
embed.add_field(name="Channels", value=ch_text, inline=True)
embed.add_field(
name="Warning Threshold",
value=str(sentiment.get("warning_threshold", 0.6)),
value=str(eff_warn),
inline=True,
)
embed.add_field(
name="Mute Threshold",
value=str(sentiment.get("mute_threshold", 0.75)),
value=str(eff_mute),
inline=True,
)
embed.add_field(
@@ -503,6 +521,86 @@ class CommandsCog(commands.Cog):
f"Notes cleared for {user.display_name}.", ephemeral=True
)
@app_commands.command(
name="bcs-mode",
description="Switch the bot's personality mode.",
)
@app_commands.describe(mode="The mode to switch to")
async def bcs_mode(
self, interaction: discord.Interaction, mode: str | None = None,
):
modes_config = self.bot.config.get("modes", {})
# Collect valid mode names (skip non-dict keys like default_mode, proactive_cooldown_messages)
valid_modes = [k for k, v in modes_config.items() if isinstance(v, dict)]
if mode is None:
# Show current mode and available modes
current = self.bot.current_mode
current_config = self.bot.get_mode_config()
lines = [f"**Current mode:** {current_config.get('label', current)}"]
lines.append(f"*{current_config.get('description', '')}*\n")
lines.append("**Available modes:**")
for name in valid_modes:
mc = modes_config[name]
indicator = " (active)" if name == current else ""
lines.append(f"- `{name}` — {mc.get('label', name)}: {mc.get('description', '')}{indicator}")
await interaction.response.send_message("\n".join(lines), ephemeral=True)
return
mode = mode.lower()
if mode not in valid_modes:
await interaction.response.send_message(
f"Unknown mode `{mode}`. Available: {', '.join(f'`{m}`' for m in valid_modes)}",
ephemeral=True,
)
return
old_mode = self.bot.current_mode
self.bot.current_mode = mode
new_config = self.bot.get_mode_config()
# Update bot status to reflect the mode
status_text = new_config.get("description", "Monitoring vibes...")
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching, name=status_text
)
)
await interaction.response.send_message(
f"Mode switched: **{modes_config.get(old_mode, {}).get('label', old_mode)}** "
f"-> **{new_config.get('label', mode)}**\n"
f"*{new_config.get('description', '')}*"
)
# Log mode change
log_channel = discord.utils.get(interaction.guild.text_channels, name="bcs-log")
if log_channel:
try:
await log_channel.send(
f"**MODE CHANGE** | {interaction.user.mention} switched mode: "
f"**{old_mode}** -> **{mode}**"
)
except discord.HTTPException:
pass
logger.info(
"Mode changed from %s to %s by %s",
old_mode, mode, interaction.user.display_name,
)
@bcs_mode.autocomplete("mode")
async def _mode_autocomplete(
self, interaction: discord.Interaction, current: str,
) -> list[app_commands.Choice[str]]:
modes_config = self.bot.config.get("modes", {})
valid_modes = [k for k, v in modes_config.items() if isinstance(v, dict)]
return [
app_commands.Choice(name=modes_config[m].get("label", m), value=m)
for m in valid_modes
if current.lower() in m.lower()
][:25]
@staticmethod
def _score_bar(score: float) -> str:
filled = round(score * 10)