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

Discord Bot反应角色/票据命令重启失效 如何用MongoDB持久化

反应角色功能重启失效问题解决方案

核心问题定位

  • 你将messageReactionAdd、messageReactionRemove监听器写在dev-roles命令的执行函数内部,每次执行命令都会重复绑定一组监听器,长期运行会造成内存泄漏,且机器人重启后,之前发送的反应角色消息的绑定会完全丢失,功能直接失效。
  • 没有持久化存储「反应角色消息ID」「表情-角色映射关系」,重启后程序无法识别需要监听的消息、也不知道对应授予哪个角色。

方案1:零依赖轻量实现(无需数据库)

原理:将所有配置存在本地JSON文件,机器人启动时自动读取配置并绑定全局监听器,适合新手快速实现。

实现步骤

  1. 在项目根目录新建config文件夹,内部新建reaction-roles.json、tickets.json两个空文件,分别存储反应角色、票据命令的配置。
  2. 改造dev-roles命令,删除内部的所有监听器,发完消息后将配置写入本地JSON:
const { MessageEmbed } = require('discord.js');
const fs = require('fs');
const path = require('path');
const reactionConfigPath = path.join(__dirname, '../../config/reaction-roles.json');
const reactionConfig = JSON.parse(fs.readFileSync(reactionConfigPath, 'utf8'));

module.exports = {
    name: 'dev-roles',
    description: "Sets up a reaction role message.",
    async execute(message, args, Discord, client) {
        if(!message.member.permissions.has("ADMINISTRATOR")) return;
        const channelId = '835948463270264853';
        // 直接用角色ID映射,避免缓存失效导致的找不到角色问题
        const emojiRoleMap = {
            '1️⃣': '834764911103180800',
            '2️⃣': '834764911124414464',
            '3️⃣': '834802358525362177',
            '4️⃣': '834802359674601552',
            '5️⃣': '834764908054577183',
            '6️⃣': '834802358071722024',
            '7️⃣': '834764911137128474',
            '8️⃣': '834764911153643520',
            '9️⃣': '834802357929377793'
        };

        const embed = new Discord.MessageEmbed()
            .setColor('#b3e5fc')
            .setTitle('DEVELOPER ROLES')
            .setDescription('**ARE YOU A DEV?**\nIf yes, chose the coding languages you know!\nYou will be given acces to coding channels, where you can chat with oher developes, get some help with coding or just help others.\n\n**Note:**\nIf you are not a developer, please **do not** get these roles.\nIf you do so, you may be temporarely / permenantly baned from our server or using our bot.\n\n'
                + `**Reaction Roles:**\n`
                + `1️⃣ - for \`JavaScript\` - <@&834764911103180800>\n`
                + `2️⃣ - for \`Java\` - <@&834764911124414464>\n`
                + `3️⃣ - for \`HTML\` - <@&834802358525362177>\n`
                + `4️⃣ - for \`CSS\` - <@&834802359674601552>\n`
                + `5️⃣ - for \`Python\` - <@&834764908054577183>\n`
                + `6️⃣ - for \`PHP\` - <@&834802358071722024>\n`
                + `7️⃣ - for \`C#\` - <@&834764911137128474>\n`
                + `8️⃣ - for \`C++\` - <@&834764911153643520>\n`
                + `9️⃣ - for \`C\` - <@&834802357929377793>\n`);

        const targetChannel = client.channels.cache.get(channelId);
        const messageEmbed = await targetChannel.send(embed);
        Object.keys(emojiRoleMap).forEach(emoji => messageEmbed.react(emoji));

        // 写入配置到本地文件
        reactionConfig[messageEmbed.id] = {
            channelId: channelId,
            guildId: message.guild.id,
            emojiRoleMap: emojiRoleMap
        };
        fs.writeFileSync(reactionConfigPath, JSON.stringify(reactionConfig, null, 2));
        message.channel.send(`反应角色消息已配置完成,消息ID:${messageEmbed.id}`);
    }
}
  1. 将全局监听器移到机器人主入口文件(通常为index.js),仅绑定一次:
const { Client, Intents } = require('discord.js');
const fs = require('fs');
const path = require('path');

const client = new Client({
    intents: [
        Intents.FLAGS.GUILDS,
        Intents.FLAGS.GUILD_MESSAGES,
        Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
        Intents.FLAGS.GUILD_MEMBERS
    ],
    // 必须配置partials,否则无法获取旧消息的反应事件
    partials: ['MESSAGE', 'CHANNEL', 'REACTION']
});

// 启动时读取配置文件
const reactionConfigPath = path.join(__dirname, 'config/reaction-roles.json');
if (!fs.existsSync(reactionConfigPath)) fs.writeFileSync(reactionConfigPath, JSON.stringify({}, null, 2));
const reactionConfig = JSON.parse(fs.readFileSync(reactionConfigPath, 'utf8'));

// 绑定全局反应添加监听器
client.on('messageReactionAdd', async (reaction, user) => {
    if (reaction.partial) await reaction.fetch();
    if (reaction.message.partial) await reaction.message.fetch();
    if (user.bot || !reaction.message.guild) return;

    const config = reactionConfig[reaction.message.id];
    if (!config) return;
    const roleId = config.emojiRoleMap[reaction.emoji.name];
    if (!roleId) return;

    const member = await reaction.message.guild.members.fetch(user.id);
    await member.roles.add(roleId);
});

// 绑定全局反应移除监听器
client.on('messageReactionRemove', async (reaction, user) => {
    if (reaction.partial) await reaction.fetch();
    if (reaction.message.partial) await reaction.message.fetch();
    if (user.bot || !reaction.message.guild) return;

    const config = reactionConfig[reaction.message.id];
    if (!config) return;
    const roleId = config.emojiRoleMap[reaction.emoji.name];
    if (!roleId) return;

    const member = await reaction.message.guild.members.fetch(user.id);
    await member.roles.remove(roleId);
});

// 后续接入原有命令加载、机器人登录逻辑即可
  1. 票据类命令按照相同逻辑改造:将票据的配置(允许创建的频道ID、分类ID、管理员角色ID等)存入tickets.json,将创建/删除票据的监听器移到主入口绑定,即可解决重启失效问题。

方案2:MongoDB持久化实现(适配Compass使用需求)

若需要使用数据库存储配置,按照以下步骤操作,新手也可快速上手:

  1. 安装依赖:
npm install mongoose
  1. 在Compass中连接本地/远程MongoDB,新建名为discord-bot的数据库,无需手动创建集合,程序运行时会自动生成。
  2. 在项目根目录新建models文件夹,新建ReactionRole.js模型文件,定义反应角色的存储结构:
const mongoose = require('mongoose');

const reactionRoleSchema = new mongoose.Schema({
    messageId: { type: String, required: true, unique: true },
    channelId: { type: String, required: true },
    guildId: { type: String, required: true },
    emojiRoleMap: { type: Object, required: true }
});

module.exports = mongoose.model('ReactionRole', reactionRoleSchema);
  1. 参照上述模型结构,新建TicketConfig.js模型存储票据命令的配置,字段根据业务需求设置即可。
  2. 在主入口index.js中加入MongoDB连接逻辑,将之前读取本地JSON的部分替换为从数据库查询:
const mongoose = require('mongoose');
const ReactionRole = require('./models/ReactionRole');
// 替换为Compass中显示的连接字符串
const mongoURI = 'mongodb://localhost:27017/discord-bot';

mongoose.connect(mongoURI)
    .then(() => console.log('MongoDB连接成功'))
    .catch(err => console.log('MongoDB连接失败:', err));

// 全局监听器中替换数据查询逻辑
client.on('messageReactionAdd', async (reaction, user) => {
    if (reaction.partial) await reaction.fetch();
    if (reaction.message.partial) await reaction.message.fetch();
    if (user.bot || !reaction.message.guild) return;

    const config = await ReactionRole.findOne({ messageId: reaction.message.id });
    if (!config) return;
    const roleId = config.emojiRoleMap[reaction.emoji.name];
    if (!roleId) return;

    const member = await reaction.message.guild.members.fetch(user.id);
    await member.roles.add(roleId);
});
  1. 改造dev-roles命令,发完消息后将配置写入MongoDB,替代本地JSON写入逻辑:
// 命令内部发完消息后的写入逻辑
const newReactionRole = new ReactionRole({
    messageId: messageEmbed.id,
    channelId: channelId,
    guildId: message.guild.id,
    emojiRoleMap: emojiRoleMap
});
await newReactionRole.save();
message.channel.send(`反应角色消息已配置完成,消息ID:${messageEmbed.id}`);
  1. 所有业务逻辑和本地JSON方案完全一致,仅将存储介质替换为MongoDB,可直接在Compass中查看、修改存储的配置数据,机器人重启后自动从数据库读取恢复功能。

注意事项

  • 必须在机器人开发面板开启GUILD_MESSAGE_REACTIONS、GUILD_MEMBERS两个intent,否则无法获取反应事件、也无权限修改用户角色。
  • 机器人自身的角色必须位于所有可授予角色的层级上方,否则无法正常分配、移除角色。
  • 全局监听器仅在主入口绑定一次,避免重复绑定造成内存泄漏。

内容的提问来源于stack exchange,提问作者krzheski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 10:06:03