Add game channel redirect feature and sexual_vulgar detection

Detect when users discuss a game in the wrong channel (e.g. GTA talk
in #warzone) and send a friendly redirect to the correct channel.
Also add sexual_vulgar category and scoring rules so crude sexual
remarks directed at someone aren't softened by "lmao".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 17:02:59 -05:00
parent e41845de02
commit fee3e3e1bd
5 changed files with 150 additions and 4 deletions

View File

@@ -358,8 +358,25 @@ class CommandsCog(commands.Cog):
await interaction.response.defer(ephemeral=True)
# Build channel context for game detection
game_channels = self.bot.config.get("game_channels", {})
channel_context = ""
if game_channels and hasattr(interaction.channel, "name"):
ch_name = interaction.channel.name
current_game = game_channels.get(ch_name)
lines = []
if current_game:
lines.append(f"Current channel: #{ch_name} ({current_game})")
else:
lines.append(f"Current channel: #{ch_name}")
channel_list = ", ".join(f"#{ch} ({g})" for ch, g in game_channels.items())
lines.append(f"Game channels: {channel_list}")
channel_context = "\n".join(lines)
user_notes = self.bot.drama_tracker.get_user_notes(interaction.user.id)
raw, parsed = await self.bot.llm.raw_analyze(message, user_notes=user_notes)
raw, parsed = await self.bot.llm.raw_analyze(
message, user_notes=user_notes, channel_context=channel_context,
)
embed = discord.Embed(
title="BCS Test Analysis", color=discord.Color.blue()
@@ -389,6 +406,14 @@ class CommandsCog(commands.Cog):
value=parsed["reasoning"][:1024] or "n/a",
inline=False,
)
detected_game = parsed.get("detected_game")
if detected_game:
game_label = game_channels.get(detected_game, detected_game)
embed.add_field(
name="Detected Game",
value=f"#{detected_game} ({game_label})",
inline=True,
)
else:
embed.add_field(
name="Parsing", value="Failed to parse response", inline=False

View File

@@ -19,6 +19,8 @@ class SentimentCog(commands.Cog):
self._channel_history: dict[int, deque] = {}
# Track which user IDs have unsaved in-memory changes
self._dirty_users: set[int] = set()
# Per-user redirect cooldown: {user_id: last_redirect_datetime}
self._redirect_cooldowns: dict[int, datetime] = {}
async def cog_load(self):
self._flush_states.start()
@@ -79,11 +81,16 @@ class SentimentCog(commands.Cog):
if not self.bot.drama_tracker.can_analyze(message.author.id, cooldown):
return
# Build channel context for game detection
game_channels = config.get("game_channels", {})
channel_context = self._build_channel_context(message, game_channels)
# Analyze the message
context = self._get_context(message)
user_notes = self.bot.drama_tracker.get_user_notes(message.author.id)
result = await self.bot.llm.analyze_message(
message.content, context, user_notes=user_notes
message.content, context, user_notes=user_notes,
channel_context=channel_context,
)
if result is None:
@@ -137,6 +144,11 @@ class SentimentCog(commands.Cog):
if off_topic:
await self._handle_topic_drift(message, topic_category, topic_reasoning, db_message_id)
# Game channel redirect detection
detected_game = result.get("detected_game")
if detected_game and game_channels and not monitoring.get("dry_run", False):
await self._handle_channel_redirect(message, detected_game, game_channels, db_message_id)
# Coherence / intoxication detection
coherence_score = result.get("coherence_score", 0.85)
coherence_flag = result.get("coherence_flag", "normal")
@@ -481,6 +493,90 @@ class SentimentCog(commands.Cog):
)
logger.info("Flushed %d dirty user states to DB.", len(dirty))
def _build_channel_context(self, message: discord.Message, game_channels: dict) -> str:
"""Build a channel context string for LLM game detection."""
if not game_channels:
return ""
channel_name = getattr(message.channel, "name", "")
current_game = game_channels.get(channel_name)
lines = []
if current_game:
lines.append(f"Current channel: #{channel_name} ({current_game})")
else:
lines.append(f"Current channel: #{channel_name}")
channel_list = ", ".join(f"#{ch} ({game})" for ch, game in game_channels.items())
lines.append(f"Game channels: {channel_list}")
return "\n".join(lines)
async def _handle_channel_redirect(
self, message: discord.Message, detected_game: str,
game_channels: dict, db_message_id: int | None = None,
):
"""Send a redirect message if the user is talking about a different game."""
channel_name = getattr(message.channel, "name", "")
# Only redirect if message is in a game channel
if channel_name not in game_channels:
return
# No redirect needed if detected game matches current channel
if detected_game == channel_name:
return
# Detected game must be a valid game channel
if detected_game not in game_channels:
return
# Find the target channel in the guild
target_channel = discord.utils.get(
message.guild.text_channels, name=detected_game
)
if not target_channel:
return
# Check per-user cooldown (reuse topic_drift remind_cooldown_minutes)
user_id = message.author.id
cooldown_minutes = self.bot.config.get("topic_drift", {}).get("remind_cooldown_minutes", 10)
now = datetime.now(timezone.utc)
last_redirect = self._redirect_cooldowns.get(user_id)
if last_redirect and (now - last_redirect) < timedelta(minutes=cooldown_minutes):
return
self._redirect_cooldowns[user_id] = now
# Send redirect message
messages_config = self.bot.config.get("messages", {})
game_name = game_channels[detected_game]
redirect_text = messages_config.get(
"channel_redirect",
"Hey {username}, that sounds like {game} talk — head over to {channel} for that!",
).format(
username=message.author.display_name,
game=game_name,
channel=target_channel.mention,
)
await message.channel.send(redirect_text)
await self._log_action(
message.guild,
f"**CHANNEL REDIRECT** | {message.author.mention} | "
f"#{channel_name} → #{detected_game} ({game_name})",
)
logger.info(
"Redirected %s from #%s to #%s (%s)",
message.author, channel_name, detected_game, game_name,
)
asyncio.create_task(self.bot.db.save_action(
guild_id=message.guild.id,
user_id=user_id,
username=message.author.display_name,
action_type="channel_redirect",
message_id=db_message_id,
details=f"from=#{channel_name} to=#{detected_game} game={game_name}",
))
def _store_context(self, message: discord.Message):
ch_id = message.channel.id
if ch_id not in self._channel_history: