You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:07:32