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/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/wordle.py` (WordleCog)**: Watches for Wordle bot messages and generates fun commentary on results.
|
||||
|
||||
|
||||
### Key Utilities
|
||||
|
||||
|
||||
2
bot.py
2
bot.py
@@ -138,7 +138,7 @@ class BCSBot(commands.Bot):
|
||||
await self.load_extension("cogs.sentiment")
|
||||
await self.load_extension("cogs.commands")
|
||||
await self.load_extension("cogs.chat")
|
||||
await self.load_extension("cogs.wordle")
|
||||
|
||||
await self.tree.sync()
|
||||
logger.info("Slash commands synced.")
|
||||
|
||||
|
||||
205
cogs/wordle.py
205
cogs/wordle.py
@@ -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