Discord.py:如何让on_raw_reaction_add按顺序处理避免误删最新反应
解决Discord投票机器人快速重复反应误删问题
核心问题是on_raw_reaction_add事件的并发处理导致同一用户的多个反应操作互相干扰,解决思路是给每个用户加异步锁,确保同一用户的反应事件串行执行,避免并发冲突。
具体实现步骤
- 维护一个用户锁字典,为每个用户分配独立的异步锁,保证同一用户的事件排队处理。
- 在反应事件处理逻辑外层包裹锁,确保同一时间只有一个该用户的反应事件在执行。
- 处理时获取该用户在投票消息上的所有有效反应,仅保留最新添加的那个,移除其他。
代码示例(基于discord.py)
import asyncio from collections import defaultdict import discord bot = discord.Bot(intents=discord.Intents.all()) user_locks = defaultdict(asyncio.Lock()) # 存储每个用户的异步锁 VOTE_MESSAGE_ID = 1234567890123456789 # 替换成你的投票消息ID ALLOWED_EMOJIS = {'1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'} @bot.event async def on_raw_reaction_add(payload): # 过滤非投票消息和非允许表情 if payload.message_id != VOTE_MESSAGE_ID or str(payload.emoji) not in ALLOWED_EMOJIS: return # 忽略机器人自己的反应 if payload.user_id == bot.user.id: return # 获取当前用户的锁,串行处理该用户的所有反应事件 lock = user_locks[payload.user_id] async with lock: try: # 获取投票消息对象 channel = await bot.fetch_channel(payload.channel_id) message = await channel.fetch_message(payload.message_id) user = bot.get_user(payload.user_id) if not user: user = await bot.fetch_user(payload.user_id) # 收集该用户的所有有效反应 user_active_reactions = [] for reaction in message.reactions: emoji_str = str(reaction.emoji) if emoji_str in ALLOWED_EMOJIS: # 检查该反应是否包含当前用户 async for reactor in reaction.users(): if reactor.id == payload.user_id: user_active_reactions.append(emoji_str) break # 如果用户有多个有效反应,仅保留最新的(当前触发事件的表情) if len(user_active_reactions) > 1: for emoji in user_active_reactions: if emoji != str(payload.emoji): await message.remove_reaction(emoji, user) except discord.NotFound: # 消息或用户不存在时跳过 pass except discord.Forbidden: # 机器人无权限移除反应时记录日志 print(f"无权限移除用户 {payload.user_id} 的反应") bot.run("YOUR_BOT_TOKEN")
关键说明
- 异步锁的作用:同一用户的所有
on_raw_reaction_add事件会等待前一个事件处理完成后再执行,彻底避免了并发检查时的逻辑冲突。比如用户快速点击多个表情,每个反应事件都会排队,处理时能准确获取当前用户的所有反应状态,只保留最新的那个。 - 性能优化:
defaultdict会自动为新用户创建锁,锁会被复用直到机器人重启,无需额外清理(如果需要可以在投票结束后清空对应用户的锁)。 - 边界处理:加入了消息/用户不存在、权限不足的异常捕获,避免机器人崩溃。
内容的提问来源于stack exchange,提问作者Kiwi
相关产品推荐
相关产品推荐

