From f7dfb7931a412ed12d0a41795ec207ef91a768b2 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 5 Mar 2026 17:44:25 -0500 Subject: [PATCH] feat: add redirect channel to topic drift messages Topic drift reminders and nudges now direct users to a specific channel (configurable via redirect_channel). Both static templates and LLM-generated redirects include the clickable channel mention. Co-Authored-By: Claude Opus 4.6 --- cogs/sentiment/topic_drift.py | 41 ++++++++++++++++++++++------------- config.yaml | 25 +++++++++++---------- prompts/topic_redirect.txt | 1 + 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/cogs/sentiment/topic_drift.py b/cogs/sentiment/topic_drift.py index b83d8f0..1ddfaa2 100644 --- a/cogs/sentiment/topic_drift.py +++ b/cogs/sentiment/topic_drift.py @@ -16,20 +16,20 @@ _PROMPTS_DIR = Path(__file__).resolve().parent.parent.parent / "prompts" _TOPIC_REDIRECT_PROMPT = (_PROMPTS_DIR / "topic_redirect.txt").read_text(encoding="utf-8") DEFAULT_TOPIC_REMINDS = [ - "Hey {username}, this is a gaming server 🎮 — maybe take the personal stuff to DMs?", - "{username}, sir this is a gaming channel.", - "Hey {username}, I don't remember this being a therapy session. Gaming talk, please. 🎮", - "{username}, I'm gonna need you to take that energy to DMs. This channel has a vibe to protect.", - "Not to be dramatic {username}, but this is wildly off-topic. Back to gaming? 🎮", + "Hey {username}, this is a gaming server 🎮 — take the personal stuff to {channel}.", + "{username}, sir this is a gaming channel. {channel} is right there.", + "Hey {username}, I don't remember this being a therapy session. Take it to {channel}. 🎮", + "{username}, I'm gonna need you to take that energy to {channel}. This channel has a vibe to protect.", + "Not to be dramatic {username}, but this is wildly off-topic. {channel} exists for a reason. 🎮", ] DEFAULT_TOPIC_NUDGES = [ - "{username}, we've been over this. Gaming. Channel. Please. 🎮", - "{username}, you keep drifting off-topic like it's a speedrun category. Reel it in.", - "Babe. {username}. The gaming channel. We talked about this. 😭", - "{username}, I will not ask again (I will definitely ask again). Stay on topic. 🎮", - "{username}, at this point I'm keeping score. That's off-topic strike {count}. Gaming talk only!", - "Look, {username}, I love the enthusiasm but this ain't the channel for it. Back to games. 🎮", + "{username}, we've been over this. Gaming. Channel. {channel} for the rest. 🎮", + "{username}, you keep drifting off-topic like it's a speedrun category. {channel}. Now.", + "Babe. {username}. The gaming channel. We talked about this. Go to {channel}. 😭", + "{username}, I will not ask again (I will definitely ask again). {channel} for off-topic. 🎮", + "{username}, at this point I'm keeping score. That's off-topic strike {count}. {channel} is waiting.", + "Look, {username}, I love the enthusiasm but this ain't the channel for it. {channel}. 🎮", ] # Per-channel deque of recent LLM-generated redirect messages (for variety) @@ -57,7 +57,7 @@ def _strip_brackets(text: str) -> str: async def _generate_llm_redirect( bot, message: discord.Message, topic_category: str, - topic_reasoning: str, count: int, + topic_reasoning: str, count: int, redirect_mention: str = "", ) -> str | None: """Ask the LLM chat model to generate a topic redirect message.""" recent = _get_recent_redirects(message.channel.id) @@ -70,6 +70,8 @@ async def _generate_llm_redirect( f"Off-topic strike count: {count}\n" f"What they said: {message.content[:300]}" ) + if redirect_mention: + user_prompt += f"\nRedirect channel: {redirect_mention}" messages = [{"role": "user", "content": user_prompt}] @@ -96,7 +98,7 @@ async def _generate_llm_redirect( return response if response else None -def _static_fallback(bot, message: discord.Message, count: int) -> str: +def _static_fallback(bot, message: discord.Message, count: int, redirect_mention: str = "") -> str: """Pick a static template message as fallback.""" messages_config = bot.config.get("messages", {}) if count >= 2: @@ -109,6 +111,7 @@ def _static_fallback(bot, message: discord.Message, count: int) -> str: pool = [pool] return random.choice(pool).format( username=message.author.display_name, count=count, + channel=redirect_mention or "the right channel", ) @@ -138,18 +141,26 @@ async def handle_topic_drift( count = tracker.record_off_topic(user_id) action_type = "topic_nudge" if count >= 2 else "topic_remind" + # Resolve redirect channel mention + redirect_mention = "" + redirect_name = config.get("redirect_channel") + if redirect_name and message.guild: + ch = discord.utils.get(message.guild.text_channels, name=redirect_name) + if ch: + redirect_mention = ch.mention + # Generate the redirect message use_llm = config.get("use_llm", False) redirect_text = None if use_llm: redirect_text = await _generate_llm_redirect( - bot, message, topic_category, topic_reasoning, count, + bot, message, topic_category, topic_reasoning, count, redirect_mention, ) if redirect_text: _record_redirect(message.channel.id, redirect_text) else: - redirect_text = _static_fallback(bot, message, count) + redirect_text = _static_fallback(bot, message, count, redirect_mention) await message.channel.send(redirect_text) diff --git a/config.yaml b/config.yaml index 1f1ab24..e410376 100644 --- a/config.yaml +++ b/config.yaml @@ -30,6 +30,7 @@ game_channels: topic_drift: enabled: true use_llm: true # Generate redirect messages via LLM instead of static templates + redirect_channel: "general" # Channel to suggest for off-topic chat ignored_channels: ["general"] # Channel names or IDs to skip topic drift monitoring remind_cooldown_minutes: 10 # Don't remind same user more than once per this window escalation_count: 3 # After this many reminds, DM the server owner @@ -51,18 +52,18 @@ messages: mute_title: "\U0001F6A8 BREEHAVIOR ALERT \U0001F6A8" mute_description: "{username} has been placed in timeout for {duration}.\n\nReason: Sustained elevated drama levels detected.\nDrama Score: {score}/1.0\nCategories: {categories}\n\nCool down and come back when you've resolved your skill issues." topic_reminds: - - "Hey {username}, this is a gaming server 🎮 — maybe take the personal stuff to DMs?" - - "{username}, sir this is a gaming channel." - - "Hey {username}, I don't remember this being a therapy session. Gaming talk, please. 🎮" - - "{username}, I'm gonna need you to take that energy to DMs. This channel has a vibe to protect." - - "Not to be dramatic {username}, but this is wildly off-topic. Back to gaming? 🎮" + - "Hey {username}, this is a gaming server 🎮 — take the personal stuff to {channel}." + - "{username}, sir this is a gaming channel. {channel} is right there." + - "Hey {username}, I don't remember this being a therapy session. Take it to {channel}. 🎮" + - "{username}, I'm gonna need you to take that energy to {channel}. This channel has a vibe to protect." + - "Not to be dramatic {username}, but this is wildly off-topic. {channel} exists for a reason. 🎮" topic_nudges: - - "{username}, we've been over this. Gaming. Channel. Please. 🎮" - - "{username}, you keep drifting off-topic like it's a speedrun category. Reel it in." - - "Babe. {username}. The gaming channel. We talked about this. 😭" - - "{username}, I will not ask again (I will definitely ask again). Stay on topic. 🎮" - - "{username}, at this point I'm keeping score. That's off-topic strike {count}. Gaming talk only!" - - "Look, {username}, I love the enthusiasm but this ain't the channel for it. Back to games. 🎮" + - "{username}, we've been over this. Gaming. Channel. {channel} for the rest. 🎮" + - "{username}, you keep drifting off-topic like it's a speedrun category. {channel}. Now." + - "Babe. {username}. The gaming channel. We talked about this. Go to {channel}. 😭" + - "{username}, I will not ask again (I will definitely ask again). {channel} for off-topic. 🎮" + - "{username}, at this point I'm keeping score. That's off-topic strike {count}. {channel} is waiting." + - "Look, {username}, I love the enthusiasm but this ain't the channel for it. {channel}. 🎮" topic_owner_dm: "Heads up: {username} keeps going off-topic with personal drama in #{channel}. They've been reminded {count} times. Might need a word." channel_redirect: "Hey {username}, that sounds like {game} talk — head over to {channel} for that!" @@ -176,7 +177,7 @@ coherence: default: "You okay there, {username}? That message was... something." reactions: - enabled: true + enabled: false chance: 0.15 # Probability of evaluating a message for reaction cooldown_seconds: 45 # Per-channel cooldown between reactions excluded_channels: [] # Channel names or IDs to skip reactions in diff --git a/prompts/topic_redirect.txt b/prompts/topic_redirect.txt index 9670b83..8c553a7 100644 --- a/prompts/topic_redirect.txt +++ b/prompts/topic_redirect.txt @@ -2,4 +2,5 @@ You're the hall monitor of "Skill Issue Support Group" (gaming Discord). Someone - Snarky and playful, not mean. Reference what they actually said — don't be vague. - Casual, like a friend ribbing them. If strike count 2+, escalate the sass. +- If a redirect channel is provided, tell them to take it there. Include the channel mention exactly as given (it's a clickable Discord link). - Max 1 emoji. No hashtags, brackets, metadata, or AI references.