Add a new cog that gives the bot ambient presence by reacting to messages with contextual emoji chosen by the triage LLM. Includes RNG gating and per-channel cooldown to keep reactions sparse and natural. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2.0 KiB
Python
70 lines
2.0 KiB
Python
import asyncio
|
|
import logging
|
|
import random
|
|
import time
|
|
|
|
import discord
|
|
from discord.ext import commands
|
|
|
|
logger = logging.getLogger("bcs.reactions")
|
|
|
|
|
|
class ReactionCog(commands.Cog):
|
|
def __init__(self, bot: commands.Bot):
|
|
self.bot = bot
|
|
# Per-channel timestamp of last reaction
|
|
self._last_reaction: dict[int, float] = {}
|
|
|
|
@commands.Cog.listener()
|
|
async def on_message(self, message: discord.Message):
|
|
if message.author.bot or not message.guild:
|
|
return
|
|
|
|
cfg = self.bot.config.get("reactions", {})
|
|
if not cfg.get("enabled", False):
|
|
return
|
|
|
|
# Skip empty messages
|
|
if not message.content or not message.content.strip():
|
|
return
|
|
|
|
# RNG gate
|
|
chance = cfg.get("chance", 0.15)
|
|
if random.random() > chance:
|
|
return
|
|
|
|
# Per-channel cooldown
|
|
ch_id = message.channel.id
|
|
cooldown = cfg.get("cooldown_seconds", 45)
|
|
now = time.monotonic()
|
|
if now - self._last_reaction.get(ch_id, 0) < cooldown:
|
|
return
|
|
|
|
# Fire and forget so we don't block anything
|
|
asyncio.create_task(self._try_react(message, ch_id, now))
|
|
|
|
async def _try_react(self, message: discord.Message, ch_id: int, now: float):
|
|
try:
|
|
emoji = await self.bot.llm.pick_reaction(
|
|
message.content, message.channel.name,
|
|
)
|
|
if not emoji:
|
|
return
|
|
|
|
await message.add_reaction(emoji)
|
|
self._last_reaction[ch_id] = now
|
|
logger.info(
|
|
"Reacted %s to %s in #%s: %s",
|
|
emoji, message.author.display_name,
|
|
message.channel.name, message.content[:60],
|
|
)
|
|
except discord.HTTPException as e:
|
|
# Invalid emoji or missing permissions — silently skip
|
|
logger.debug("Reaction failed: %s", e)
|
|
except Exception:
|
|
logger.exception("Unexpected reaction error")
|
|
|
|
|
|
async def setup(bot: commands.Bot):
|
|
await bot.add_cog(ReactionCog(bot))
|