feat: move user aliases from config to DB with /bcs-alias command
Aliases now stored in UserState table instead of config.yaml. Adds Aliases column (NVARCHAR 500), loads on startup, persists via flush. New /bcs-alias slash command (view/set/clear) for managing nicknames. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -250,6 +250,7 @@ class CommandsCog(commands.Cog):
|
||||
off_topic_count=user_data.off_topic_count,
|
||||
baseline_coherence=user_data.baseline_coherence,
|
||||
user_notes=user_data.notes or None,
|
||||
aliases=",".join(user_data.aliases) if user_data.aliases else None,
|
||||
))
|
||||
status = "now immune" if is_immune else "no longer immune"
|
||||
await interaction.response.send_message(
|
||||
@@ -501,6 +502,7 @@ class CommandsCog(commands.Cog):
|
||||
off_topic_count=user_data.off_topic_count,
|
||||
baseline_coherence=user_data.baseline_coherence,
|
||||
user_notes=user_data.notes or None,
|
||||
aliases=",".join(user_data.aliases) if user_data.aliases else None,
|
||||
))
|
||||
await interaction.response.send_message(
|
||||
f"Note added for {user.display_name}.", ephemeral=True
|
||||
@@ -516,11 +518,86 @@ class CommandsCog(commands.Cog):
|
||||
off_topic_count=user_data.off_topic_count,
|
||||
baseline_coherence=user_data.baseline_coherence,
|
||||
user_notes=None,
|
||||
aliases=",".join(user_data.aliases) if user_data.aliases else None,
|
||||
))
|
||||
await interaction.response.send_message(
|
||||
f"Notes cleared for {user.display_name}.", ephemeral=True
|
||||
)
|
||||
|
||||
@app_commands.command(
|
||||
name="bcs-alias",
|
||||
description="Manage nicknames/aliases for a user. (Admin only)",
|
||||
)
|
||||
@app_commands.default_permissions(administrator=True)
|
||||
@app_commands.describe(
|
||||
action="What to do with aliases",
|
||||
user="The user whose aliases to manage",
|
||||
text="Comma-separated aliases (only used with 'set')",
|
||||
)
|
||||
@app_commands.choices(action=[
|
||||
app_commands.Choice(name="view", value="view"),
|
||||
app_commands.Choice(name="set", value="set"),
|
||||
app_commands.Choice(name="clear", value="clear"),
|
||||
])
|
||||
async def bcs_alias(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
action: app_commands.Choice[str],
|
||||
user: discord.Member,
|
||||
text: str | None = None,
|
||||
):
|
||||
if not self._is_admin(interaction):
|
||||
await interaction.response.send_message("Admin only.", ephemeral=True)
|
||||
return
|
||||
|
||||
if action.value == "view":
|
||||
aliases = self.bot.drama_tracker.get_user_aliases(user.id)
|
||||
desc = ", ".join(aliases) if aliases else "_No aliases set._"
|
||||
embed = discord.Embed(
|
||||
title=f"Aliases: {user.display_name}",
|
||||
description=desc,
|
||||
color=discord.Color.blue(),
|
||||
)
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
|
||||
elif action.value == "set":
|
||||
if not text:
|
||||
await interaction.response.send_message(
|
||||
"Provide `text` with comma-separated aliases (e.g. `Glam, G`).", ephemeral=True
|
||||
)
|
||||
return
|
||||
aliases = [a.strip() for a in text.split(",") if a.strip()]
|
||||
self.bot.drama_tracker.set_user_aliases(user.id, aliases)
|
||||
user_data = self.bot.drama_tracker.get_user(user.id)
|
||||
asyncio.create_task(self.bot.db.save_user_state(
|
||||
user_id=user.id,
|
||||
offense_count=user_data.offense_count,
|
||||
immune=user_data.immune,
|
||||
off_topic_count=user_data.off_topic_count,
|
||||
baseline_coherence=user_data.baseline_coherence,
|
||||
user_notes=user_data.notes or None,
|
||||
aliases=",".join(aliases),
|
||||
))
|
||||
await interaction.response.send_message(
|
||||
f"Aliases for {user.display_name} set to: {', '.join(aliases)}", ephemeral=True
|
||||
)
|
||||
|
||||
elif action.value == "clear":
|
||||
self.bot.drama_tracker.set_user_aliases(user.id, [])
|
||||
user_data = self.bot.drama_tracker.get_user(user.id)
|
||||
asyncio.create_task(self.bot.db.save_user_state(
|
||||
user_id=user.id,
|
||||
offense_count=user_data.offense_count,
|
||||
immune=user_data.immune,
|
||||
off_topic_count=user_data.off_topic_count,
|
||||
baseline_coherence=user_data.baseline_coherence,
|
||||
user_notes=user_data.notes or None,
|
||||
aliases=None,
|
||||
))
|
||||
await interaction.response.send_message(
|
||||
f"Aliases cleared for {user.display_name}.", ephemeral=True
|
||||
)
|
||||
|
||||
@app_commands.command(
|
||||
name="bcs-mode",
|
||||
description="Switch the bot's personality mode.",
|
||||
|
||||
@@ -253,18 +253,18 @@ class SentimentCog(commands.Cog):
|
||||
"""Replace display name keys with anonymous keys in user notes map."""
|
||||
return {anon_map.get(name, name): notes for name, notes in user_notes_map.items()}
|
||||
|
||||
@staticmethod
|
||||
def _build_alias_context(
|
||||
self,
|
||||
messages: list[discord.Message],
|
||||
anon_map: dict[str, str],
|
||||
alias_config: dict,
|
||||
) -> str:
|
||||
"""Build anonymized alias context string for the LLM.
|
||||
|
||||
Maps user IDs from messages to their known nicknames using the
|
||||
config, then replaces display names with anonymous keys.
|
||||
Maps user IDs from messages to their known nicknames from
|
||||
DramaTracker, then replaces display names with anonymous keys.
|
||||
"""
|
||||
if not alias_config:
|
||||
all_aliases = self.bot.drama_tracker.get_all_aliases()
|
||||
if not all_aliases:
|
||||
return ""
|
||||
lines = []
|
||||
seen_ids: set[int] = set()
|
||||
@@ -273,14 +273,13 @@ class SentimentCog(commands.Cog):
|
||||
if uid in seen_ids:
|
||||
continue
|
||||
seen_ids.add(uid)
|
||||
aliases = alias_config.get(uid)
|
||||
aliases = all_aliases.get(uid)
|
||||
if aliases:
|
||||
anon_key = anon_map.get(msg.author.display_name, msg.author.display_name)
|
||||
lines.append(f" {anon_key} is also known as: {', '.join(aliases)}")
|
||||
# Also include aliases for members NOT in the conversation (so the LLM
|
||||
# can recognize name-drops of absent members)
|
||||
for uid, aliases in alias_config.items():
|
||||
uid = int(uid) if isinstance(uid, str) else uid
|
||||
for uid, aliases in all_aliases.items():
|
||||
if uid not in seen_ids:
|
||||
lines.append(f" (not in chat) also known as: {', '.join(aliases)}")
|
||||
return "\n".join(lines) if lines else ""
|
||||
@@ -498,8 +497,7 @@ class SentimentCog(commands.Cog):
|
||||
anon_conversation = self._anonymize_conversation(conversation, anon_map)
|
||||
anon_notes = self._anonymize_notes(user_notes_map, anon_map) if user_notes_map else user_notes_map
|
||||
|
||||
alias_config = config.get("user_aliases", {})
|
||||
alias_context = self._build_alias_context(all_messages, anon_map, alias_config)
|
||||
alias_context = self._build_alias_context(all_messages, anon_map)
|
||||
|
||||
channel_context = build_channel_context(ref_message, game_channels)
|
||||
|
||||
@@ -675,8 +673,7 @@ class SentimentCog(commands.Cog):
|
||||
anon_conversation = self._anonymize_conversation(conversation, anon_map)
|
||||
anon_notes = self._anonymize_notes(user_notes_map, anon_map) if user_notes_map else user_notes_map
|
||||
|
||||
alias_config = config.get("user_aliases", {})
|
||||
alias_context = self._build_alias_context(raw_messages, anon_map, alias_config)
|
||||
alias_context = self._build_alias_context(raw_messages, anon_map)
|
||||
|
||||
channel_context = build_channel_context(raw_messages[0], game_channels)
|
||||
mention_context = (
|
||||
|
||||
@@ -4,6 +4,11 @@ import logging
|
||||
logger = logging.getLogger("bcs.sentiment")
|
||||
|
||||
|
||||
def _aliases_csv(user_data) -> str | None:
|
||||
"""Convert aliases list to comma-separated string for DB storage."""
|
||||
return ",".join(user_data.aliases) if user_data.aliases else None
|
||||
|
||||
|
||||
def save_user_state(bot, dirty_users: set[int], user_id: int) -> None:
|
||||
"""Fire-and-forget save of a user's current state to DB."""
|
||||
user_data = bot.drama_tracker.get_user(user_id)
|
||||
@@ -16,6 +21,7 @@ def save_user_state(bot, dirty_users: set[int], user_id: int) -> None:
|
||||
user_notes=user_data.notes or None,
|
||||
warned=user_data.warned_since_reset,
|
||||
last_offense_at=user_data.last_offense_time or None,
|
||||
aliases=_aliases_csv(user_data),
|
||||
))
|
||||
dirty_users.discard(user_id)
|
||||
|
||||
@@ -37,5 +43,6 @@ async def flush_dirty_states(bot, dirty_users: set[int]) -> None:
|
||||
user_notes=user_data.notes or None,
|
||||
warned=user_data.warned_since_reset,
|
||||
last_offense_at=user_data.last_offense_time or None,
|
||||
aliases=_aliases_csv(user_data),
|
||||
)
|
||||
logger.info("Flushed %d dirty user states to DB.", len(dirty))
|
||||
|
||||
Reference in New Issue
Block a user