chore: remove wordle cog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,7 +55,7 @@ LLM calls use OpenAI tool-calling for structured output (`ANALYSIS_TOOL`, `CONVE
|
|||||||
- **`cogs/sentiment.py` (SentimentCog)**: Core moderation engine. Listens to all messages, debounces per-channel (batches messages within `batch_window_seconds`), runs triage → escalation analysis, issues warnings/mutes. Also handles mention-triggered conversation scans and game channel redirects. Flushes dirty user states to DB every 5 minutes.
|
- **`cogs/sentiment.py` (SentimentCog)**: Core moderation engine. Listens to all messages, debounces per-channel (batches messages within `batch_window_seconds`), runs triage → escalation analysis, issues warnings/mutes. Also handles mention-triggered conversation scans and game channel redirects. Flushes dirty user states to DB every 5 minutes.
|
||||||
- **`cogs/chat.py` (ChatCog)**: Conversational AI. Responds to @mentions, replies to bot messages, proactive replies based on mode config. Handles image roasts via vision model. Strips leaked LLM metadata brackets from responses.
|
- **`cogs/chat.py` (ChatCog)**: Conversational AI. Responds to @mentions, replies to bot messages, proactive replies based on mode config. Handles image roasts via vision model. Strips leaked LLM metadata brackets from responses.
|
||||||
- **`cogs/commands.py` (CommandsCog)**: Slash commands — `/dramareport`, `/dramascore`, `/bcs-status`, `/bcs-threshold`, `/bcs-reset`, `/bcs-immune`, `/bcs-history`, `/bcs-scan`, `/bcs-test`, `/bcs-notes`, `/bcs-mode`.
|
- **`cogs/commands.py` (CommandsCog)**: Slash commands — `/dramareport`, `/dramascore`, `/bcs-status`, `/bcs-threshold`, `/bcs-reset`, `/bcs-immune`, `/bcs-history`, `/bcs-scan`, `/bcs-test`, `/bcs-notes`, `/bcs-mode`.
|
||||||
- **`cogs/wordle.py` (WordleCog)**: Watches for Wordle bot messages and generates fun commentary on results.
|
|
||||||
|
|
||||||
### Key Utilities
|
### Key Utilities
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class BCSBot(commands.Bot):
|
|||||||
await self.load_extension("cogs.sentiment")
|
await self.load_extension("cogs.sentiment")
|
||||||
await self.load_extension("cogs.commands")
|
await self.load_extension("cogs.commands")
|
||||||
await self.load_extension("cogs.chat")
|
await self.load_extension("cogs.chat")
|
||||||
await self.load_extension("cogs.wordle")
|
|
||||||
await self.tree.sync()
|
await self.tree.sync()
|
||||||
logger.info("Slash commands synced.")
|
logger.info("Slash commands synced.")
|
||||||
|
|
||||||
|
|||||||
-205
@@ -1,205 +0,0 @@
|
|||||||
import logging
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
from collections import deque
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
|
|
||||||
logger = logging.getLogger("bcs.wordle")
|
|
||||||
|
|
||||||
_PROMPTS_DIR = Path(__file__).resolve().parent.parent / "prompts"
|
|
||||||
|
|
||||||
_prompt_cache: dict[str, str] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _load_prompt(filename: str) -> str:
|
|
||||||
if filename not in _prompt_cache:
|
|
||||||
_prompt_cache[filename] = (_PROMPTS_DIR / filename).read_text(encoding="utf-8")
|
|
||||||
return _prompt_cache[filename]
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_wordle_embeds(message: discord.Message) -> dict | None:
|
|
||||||
"""Extract useful info from a Wordle bot message.
|
|
||||||
|
|
||||||
Returns a dict with keys like 'type', 'summary', 'scores', 'streak', 'wordle_number'
|
|
||||||
or None if this isn't a recognizable Wordle result message.
|
|
||||||
"""
|
|
||||||
if not message.embeds:
|
|
||||||
return None
|
|
||||||
|
|
||||||
full_text = ""
|
|
||||||
wordle_number = None
|
|
||||||
|
|
||||||
for embed in message.embeds:
|
|
||||||
if embed.description:
|
|
||||||
full_text += embed.description + "\n"
|
|
||||||
if embed.title:
|
|
||||||
full_text += embed.title + "\n"
|
|
||||||
m = re.search(r"Wordle No\.\s*(\d+)", embed.title)
|
|
||||||
if m:
|
|
||||||
wordle_number = int(m.group(1))
|
|
||||||
|
|
||||||
if not full_text.strip():
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Detect result messages (contain score patterns like "3/6:")
|
|
||||||
score_pattern = re.findall(r"(\d/6):\s*@?(.+?)(?:\n|$)", full_text)
|
|
||||||
streak_match = re.search(r"(\d+)\s*day streak", full_text)
|
|
||||||
|
|
||||||
if score_pattern:
|
|
||||||
scores = [{"score": s[0], "player": s[1].strip()} for s in score_pattern]
|
|
||||||
return {
|
|
||||||
"type": "results",
|
|
||||||
"wordle_number": wordle_number,
|
|
||||||
"streak": int(streak_match.group(1)) if streak_match else None,
|
|
||||||
"scores": scores,
|
|
||||||
"summary": full_text.strip(),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Detect "was playing" messages
|
|
||||||
if "was playing" in full_text:
|
|
||||||
return {
|
|
||||||
"type": "playing",
|
|
||||||
"wordle_number": wordle_number,
|
|
||||||
"summary": full_text.strip(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class WordleCog(commands.Cog):
|
|
||||||
def __init__(self, bot: commands.Bot):
|
|
||||||
self.bot = bot
|
|
||||||
self._chat_history: dict[int, deque] = {}
|
|
||||||
|
|
||||||
def _get_active_prompt(self) -> str:
|
|
||||||
mode_config = self.bot.get_mode_config()
|
|
||||||
prompt_file = mode_config.get("prompt_file", "chat_personality.txt")
|
|
||||||
return _load_prompt(prompt_file)
|
|
||||||
|
|
||||||
def _get_wordle_config(self) -> dict:
|
|
||||||
return self.bot.config.get("wordle", {})
|
|
||||||
|
|
||||||
@commands.Cog.listener()
|
|
||||||
async def on_message(self, message: discord.Message):
|
|
||||||
if not message.author.bot:
|
|
||||||
return
|
|
||||||
if not message.guild:
|
|
||||||
return
|
|
||||||
|
|
||||||
config = self._get_wordle_config()
|
|
||||||
if not config.get("enabled", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Match the Wordle bot by name
|
|
||||||
bot_name = config.get("bot_name", "Wordle")
|
|
||||||
if message.author.name != bot_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
parsed = _parse_wordle_embeds(message)
|
|
||||||
if not parsed:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Only comment on results, not "playing" notifications
|
|
||||||
if parsed["type"] == "playing":
|
|
||||||
reply_chance = config.get("playing_reply_chance", 0.0)
|
|
||||||
if reply_chance <= 0 or random.random() > reply_chance:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
reply_chance = config.get("reply_chance", 0.5)
|
|
||||||
if random.random() > reply_chance:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Build context for the LLM
|
|
||||||
context_parts = [
|
|
||||||
f"[Wordle bot posted in #{message.channel.name}]",
|
|
||||||
"[Wordle scoring: players guess a 5-letter word in up to 6 tries. "
|
|
||||||
"LOWER is BETTER — 1/6 is a genius guess, 2/6 is incredible, 3/6 is great, "
|
|
||||||
"4/6 is mediocre, 5/6 is rough, 6/6 barely scraped by, X/6 means they failed]",
|
|
||||||
]
|
|
||||||
|
|
||||||
if parsed["type"] == "results":
|
|
||||||
context_parts.append("[This is a Wordle results summary]")
|
|
||||||
if parsed.get("streak"):
|
|
||||||
context_parts.append(f"[Group streak: {parsed['streak']} days]")
|
|
||||||
if parsed.get("wordle_number"):
|
|
||||||
context_parts.append(f"[Wordle #{parsed['wordle_number']}]")
|
|
||||||
for s in parsed.get("scores", []):
|
|
||||||
context_parts.append(f"[{s['player']} scored {s['score']}]")
|
|
||||||
|
|
||||||
# Identify the winner (lowest score = best)
|
|
||||||
scores = parsed.get("scores", [])
|
|
||||||
if scores:
|
|
||||||
best = min(scores, key=lambda s: int(s["score"][0]))
|
|
||||||
worst = max(scores, key=lambda s: int(s["score"][0]))
|
|
||||||
if best != worst:
|
|
||||||
context_parts.append(
|
|
||||||
f"[{best['player']} won with {best['score']}, "
|
|
||||||
f"{worst['player']} came last with {worst['score']}]"
|
|
||||||
)
|
|
||||||
elif parsed["type"] == "playing":
|
|
||||||
context_parts.append(f"[Someone is currently playing Wordle]")
|
|
||||||
context_parts.append(f"[{parsed['summary']}]")
|
|
||||||
|
|
||||||
prompt_context = "\n".join(context_parts)
|
|
||||||
user_msg = (
|
|
||||||
f"{prompt_context}\n"
|
|
||||||
f"React to this Wordle update with a short, fun comment. "
|
|
||||||
f"Keep it to 1-2 sentences."
|
|
||||||
)
|
|
||||||
|
|
||||||
ch_id = message.channel.id
|
|
||||||
if ch_id not in self._chat_history:
|
|
||||||
self._chat_history[ch_id] = deque(maxlen=6)
|
|
||||||
|
|
||||||
self._chat_history[ch_id].append({"role": "user", "content": user_msg})
|
|
||||||
|
|
||||||
active_prompt = self._get_active_prompt()
|
|
||||||
|
|
||||||
recent_bot_replies = [
|
|
||||||
m["content"][:150] for m in self._chat_history[ch_id]
|
|
||||||
if m["role"] == "assistant"
|
|
||||||
][-3:]
|
|
||||||
|
|
||||||
typing_ctx = None
|
|
||||||
|
|
||||||
async def start_typing():
|
|
||||||
nonlocal typing_ctx
|
|
||||||
typing_ctx = message.channel.typing()
|
|
||||||
await typing_ctx.__aenter__()
|
|
||||||
|
|
||||||
response = await self.bot.llm_chat.chat(
|
|
||||||
list(self._chat_history[ch_id]),
|
|
||||||
active_prompt,
|
|
||||||
on_first_token=start_typing,
|
|
||||||
recent_bot_replies=recent_bot_replies,
|
|
||||||
)
|
|
||||||
|
|
||||||
if typing_ctx:
|
|
||||||
await typing_ctx.__aexit__(None, None, None)
|
|
||||||
|
|
||||||
# Strip leaked metadata brackets (same as chat.py)
|
|
||||||
if response:
|
|
||||||
segments = re.split(r"^\s*\[[^\]]*\]\s*$", response, flags=re.MULTILINE)
|
|
||||||
segments = [s.strip() for s in segments if s.strip()]
|
|
||||||
response = segments[-1] if segments else ""
|
|
||||||
|
|
||||||
if not response:
|
|
||||||
logger.warning("LLM returned no response for Wordle comment in #%s", message.channel.name)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._chat_history[ch_id].append({"role": "assistant", "content": response})
|
|
||||||
|
|
||||||
await message.reply(response, mention_author=False)
|
|
||||||
logger.info(
|
|
||||||
"Wordle %s reply in #%s: %s",
|
|
||||||
parsed["type"],
|
|
||||||
message.channel.name,
|
|
||||||
response[:100],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot):
|
|
||||||
await bot.add_cog(WordleCog(bot))
|
|
||||||
Reference in New Issue
Block a user