ia_bot/handlers/reactions.py
Benjamyn 43ca943cb8 feat: A.B.I.N.A.S.H Discord bot with GIF emotes, slash commands, and music
- Core bot with slash command tree (discord.py 2.7.x compatible)
- Commands: /ping, /info, /hello, /emoji, /app-emoji
- Admin: /upload-emoji, /list-emojis, /delete-emoji
- GIF emote detection and reaction handling
- Message reactions with percentage-based triggers and cooldowns
- Voice/music cog with YTDLSource, skip, volume, pause, resume, stop
- Global error handler and voice state change logging
- Environment config via .env (DISCORD_TOKEN, COMMAND_PREFIX, etc.)
2026-08-27 16:05:27 +10:00

110 lines
3.9 KiB
Python

"""Reaction handler with percentage-based triggers, cooldowns, and mention reactions."""
import asyncio
import logging
from typing import Optional
import discord
from discord.ext import commands
logger = logging.getLogger(__name__)
# ─── Configuration ───────────────────────────────────────────────────────────
REACTION_CHANCE = 0.05 # 5% chance to react to a message
REACTION_COOLDOWN = 60 # seconds per channel
# ─── Reaction Handler ────────────────────────────────────────────────────────
class ReactionHandler:
"""Handles message reactions with cooldowns and mention triggers."""
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
self._cooldowns: dict[int, float] = {} # channel_id -> next_available
self._reaction_emojis: list[discord.Emoji] = []
async def handle_reactions(self, message: discord.Message) -> None:
"""Check if a message should be reacted to and handle reactions."""
channel_id = message.channel.id
# Enforce cooldown
now = discord.utils.utcnow().timestamp()
if channel_id in self._cooldowns:
if now < self._cooldowns[channel_id]:
return
# Check if message mentions the bot
if self._is_mention(message):
await self._handle_mention(message)
else:
# Random chance to react
if self._should_react():
await self._add_reaction(message)
# Enforce cooldown
self._cooldown(channel_id)
def _is_mention(self, message: discord.Message) -> bool:
"""Check if the message mentions the bot."""
return f"<@{self.bot.user.id}>" in message.content or f"<@!{self.bot.user.id}>" in message.content
def _should_react(self) -> bool:
"""Determine if we should react (random chance)."""
import random
return random.random() < REACTION_CHANCE
async def _handle_mention(self, message: discord.Message) -> None:
"""Handle a mention — react with a random emoji."""
if self._reaction_emojis:
emoji = self._reaction_emojis[0]
try:
await message.add_reaction(emoji)
except discord.Forbidden:
pass
async def _add_reaction(self, message: discord.Message) -> None:
"""Add a reaction to a message."""
if self._reaction_emojis:
emoji = self._reaction_emojis[0]
try:
await message.add_reaction(emoji)
except discord.Forbidden:
pass
def _cooldown(self, channel_id: int) -> None:
"""Set cooldown for a channel."""
self._cooldowns[channel_id] = discord.utils.utcnow().timestamp() + REACTION_COOLDOWN
def register_emoji(self, emoji: discord.Emoji) -> None:
"""Register an emoji for reactions."""
self._reaction_emojis.append(emoji)
# ─── Bot Integration ─────────────────────────────────────────────────────────
async def setup(bot: commands.Bot) -> None:
"""Register the reaction handler with the bot."""
handler = ReactionHandler(bot)
@bot.event
async def on_message(message: discord.Message) -> None:
"""Handle reactions on messages."""
if message.author == bot.user:
return
if message.author.bot:
return
await handler.handle_reactions(message)
def register_emoji(bot: commands.Bot, emoji: discord.Emoji) -> None:
"""Register an emoji for reactions."""
handler = ReactionHandler.__new__(ReactionHandler)
handler.bot = bot
handler._reaction_emojis = []
bot._reaction_handler = handler