- 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.)
110 lines
3.9 KiB
Python
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
|