Discord.js机器人角色反应跨会话失效:重启后首次移除不删角色
Discord机器人角色反应同步问题
我正在为Discord机器人开发/roles命令,功能是让机器人发送带自动自反应的Embed消息,用户点击对应反应就能获取Embed里标记的角色。同一会话内功能正常,但跨会话(机器人重启后)失效。于是用sqlite3数据库存储用户反应记录,实现重启后同步数据,让用户移除反应时能删除对应角色。但现在遇到机器人重启后用户首次移除反应时,对应角色未被删除的问题。
ready.js 事件加载代码
try { // 拉取所有服务器成员初始化缓存 await Promise.all(client.guilds.cache.map(guild => guild.members.fetch())); // 加载保存的Embed并设置反应收集器 const savedEmbeds = await Embed.findAll(); for (const savedEmbed of savedEmbeds) { const { id, messageId, channelId, maxReactions } = savedEmbed; const channel = await client.channels.fetch(channelId); if (!channel) { continue; } const message = await channel.messages.fetch(messageId); if (!message) { continue; } await setupReactionCollectors(message, client, maxReactions, id); } } catch (error) { console.error('[ready.js] 加载保存的Embed和反应时出错:', error); }
roleManager.js 同步初始反应代码
// 从Discord同步初始反应和角色到数据库 async function syncInitialReactions(message, client, maxReactions, embedId) { const reactions = message.reactions.cache; console.log(`[syncInitialReactions] 正在同步消息 ${message.id} 的初始反应`); for (const [emoji, reaction] of reactions) { const users = await reaction.users.fetch(); console.log(`[syncInitialReactions] 消息 ${message.id} 上表情 ${emoji.toString()} 的用户列表: ${Array.from(users.keys()).join(', ')}`); for (const user of users.values()) { if (!user.bot) { console.log(`[syncInitialReactions] 检查消息 ${message.id} 上用户 ${user.tag} 的反应 ${emoji}`); // 检查该反应是否已在数据库中 const existingReaction = await Reaction.findOne({ where: { embedId: embedId, userId: user.id, emoji: emoji.toString(), }, }); if (!existingReaction) { console.log(`[syncInitialReactions] 数据库中没有消息 ${message.id} 上用户 ${user.tag} 的反应 ${emoji}。正在添加到数据库并分配角色。`); // 如果未分配则分配角色 await handleRoleAssignment(reaction, user, 'add', client, maxReactions, embedId, true); // 将反应添加到数据库 await Reaction.create({ embedId: embedId, userId: user.id, emoji: emoji.toString(), roleId: reaction.message.guild.roles.cache.find(r => reaction.message.embeds[0]?.fields?.find(field => field.value.includes(emoji.toString()))?.value.includes(`<@&${r.id}>`))?.id, }); } else { console.log(`[syncInitialReactions] 消息 ${message.id} 上用户 ${user.tag} 的反应 ${emoji} 已记录在数据库中`); } } } // 移除数据库中Discord上已不存在的反应记录 const dbReactions = await Reaction.findAll({ where: { embedId: embedId, emoji: emoji.toString(), }, }); for (const dbReaction of dbReactions) { if (!users.has(dbReaction.userId)) { console.log(`[syncInitialReactions] 消息 ${message.id} 上用户 ${dbReaction.userId} 的反应 ${dbReaction.emoji} 在Discord上不存在。正在从数据库移除并删除角色。`); await handleRoleAssignment(reaction, { id: dbReaction.userId, tag: 'unknown' }, 'remove', client, maxReactions, embedId, true); await Reaction.destroy({ where: { id: dbReaction.id, }, }); } } } }
reactionManager.js 设置反应收集器代码
// ./utils/reactionManager.js const { handleRoleAssignment, syncInitialReactions } = require('./roleManager'); // 为指定消息设置反应收集器 async function setupReactionCollectors(message, client, maxReactions, embedId) { const filter = (reaction, user) => !user.bot; const collector = message.createReactionCollector({ filter, dispose: true }); collector.on('collect', async (reaction, user) => { console.log(`[reactionManager] 消息 ${message.id} 收到用户 ${user.tag} 的反应`); await handleRoleAssignment(reaction, user, 'add', client, maxReactions, embedId); }); collector.on('remove', async (reaction, user) => { console.log(`[reactionManager] 消息 ${message.id} 上用户 ${user.tag} 的反应被移除`); await handleRoleAssignment(reaction, user, 'remove', client, maxReactions, embedId); }); console.log(`[reactionManager] 已为消息 ${message.id} 设置收集器`); // 先设置收集器再同步初始反应 await syncInitialReactions(message, client, maxReactions, embedId); } module.exports = { setupReactionCollectors, };
问题原因分析
机器人重启后,Discord.js的message.createReactionCollector的dispose事件仅对收集器启动后新增的反应触发remove回调。重启前用户已有的历史反应,收集器不会将其纳入跟踪范围,所以首次移除这些反应时不会触发回调,也就不会执行角色删除逻辑。
另外,当前同步逻辑仅把反应记录到数据库,但没有让收集器关联这些历史反应,导致移除操作无法被捕获。
解决方案
方案1:改用全局反应事件监听
放弃消息级别的收集器,改用客户端全局的messageReactionAdd和messageReactionRemove事件,这样能捕获所有服务器内的反应操作,不受会话重启影响。需要确保客户端启用了这些事件的监听权限。
方案2:优化移除逻辑,优先查数据库
修改handleRoleAssignment的remove分支,先检查数据库中是否存在该用户的反应记录,存在则直接删除角色并更新数据库:
async function handleRoleAssignment(reaction, user, action, client, maxReactions, embedId, isSync = false) { if (action === 'remove') { // 优先检查数据库记录 const dbReaction = await Reaction.findOne({ where: { embedId: embedId, userId: user.id, emoji: reaction.emoji.toString() } }); if (dbReaction) { const guild = reaction.message.guild; const member = await guild.members.fetch(user.id); const role = guild.roles.cache.get(dbReaction.roleId); if (member && role) { await member.roles.remove(role); } await Reaction.destroy({ where: { id: dbReaction.id } }); return; } // 数据库无记录时,执行原有收集器触发的移除逻辑 // ... 你的原有remove逻辑 } // ... 你的原有add逻辑 }
方案3:调整同步与收集器启动顺序
把同步初始反应的操作放到收集器启动之前,避免同步过程干扰收集器的跟踪逻辑:
async function setupReactionCollectors(message, client, maxReactions, embedId) { // 先同步初始反应,再启动收集器 await syncInitialReactions(message, client, maxReactions, embedId); const filter = (reaction, user) => !user.bot; const collector = message.createReactionCollector({ filter, dispose: true }); collector.on('collect', async (reaction, user) => { console.log(`[reactionManager] 消息 ${message.id} 收到用户 ${user.tag} 的反应`); await handleRoleAssignment(reaction, user, 'add', client, maxReactions, embedId); }); collector.on('remove', async (reaction, user) => { console.log(`[reactionManager] 消息 ${message.id} 上用户 ${user.tag} 的反应被移除`); await handleRoleAssignment(reaction, user, 'remove', client, maxReactions, embedId); }); console.log(`[reactionManager] 已为消息 ${message.id} 设置收集器`); }
内容的提问来源于stack exchange,提问作者Jake Livsey
相关产品推荐
相关产品推荐

