如何在机器人会话之间保持机器人的反应角色帖子正常工作?
我正在使用 Discord.js 创建一个嵌入,其中包含来自机器人的一些自动自我反应,这些反应与嵌入中识别的可自我分配的角色相对应。这部分效果很好。
然后我使用 sqlite3 将反应数据存储在数据库中,以便我可以在机器人重新启动时获取数据。从理论上讲,这应该可以使反应角色的嵌入保持正常工作。如下所述,这效果不太好。
下面是从初始启动开始记录的所有内容(来自最多 3 个现有嵌入的大量记录信息供您忽略),通过新的
/roles
命令创建新嵌入,对该嵌入做出反应,关闭机器人,重新打开机器人,删除我最初对嵌入给出的反应(未记录,如上所述),然后重新添加相同的反应(已记录但无效,因为该角色从未从反应的删除中删除,如上所述) ,然后——为了更好的措施——再次删除并重新添加反应,以显示所有记录都正确(并且我可以保证它在这些操作中也能正确运行)。
PS C:\Users\snake\OneDrive\Documents\discord\R1N> node .
Connection to SQLite database has been established successfully.
...Zee-zee... R1N is now On-line!
[ready.js] Fetched all members for all guilds.
Executing (default): SELECT `id`, `messageId`, `channelId`, `embedTitle`, `embedDescription`, `embedFooter`, `embedThumbnail`, `embedImage`, `embedIcon`, `embedColor`, `maxReactions`, `fieldValue`, `createdAt`, `updatedAt` FROM `Embeds` AS `Embed`;
[ready.js] Setting up collectors for message 1253058917680808068
[ready.js] Collectors set up for message 1253058917680808068
[syncInitialReactions] Syncing initial reactions for message 1253058917680808068
[syncInitialReactions] Checking reaction 1252855872892440666 from user daggerbyte on message 1253058917680808068
Executing (default): SELECT `id`, `embedId`, `userId`, `emoji`, `roleId`, `createdAt`, `updatedAt` FROM `Reactions` AS `Reaction` WHERE `Reaction`.`embedId` = 14 AND `Reaction`.`userId` = '590630085607030784' AND `Reaction`.`emoji` = '1252855872892440666' LIMIT 1;
[syncInitialReactions] Reaction 1252855872892440666 from user daggerbyte on message 1253058917680808068 already recorded in database
[ready.js] Setting up collectors for message 1253487005900476486
[ready.js] Collectors set up for message 1253487005900476486
[syncInitialReactions] Syncing initial reactions for message 1253487005900476486
[ready.js] Setting up collectors for message 1253487126411219106
[ready.js] Collectors set up for message 1253487126411219106
[syncInitialReactions] Syncing initial reactions for message 1253487126411219106
[syncInitialReactions] Checking reaction 1252855872892440666 from user daggerbyte on message 1253487126411219106
Executing (default): SELECT `id`, `embedId`, `userId`, `emoji`, `roleId`, `createdAt`, `updatedAt` FROM `Reactions` AS `Reaction` WHERE `Reaction`.`embedId` = 16 AND `Reaction`.`userId` = '590630085607030784' AND `Reaction`.`emoji` = '1252855872892440666' LIMIT 1;
[syncInitialReactions] Database does not contain reaction 1252855872892440666 for user daggerbyte on message 1253487126411219106. Adding to database and assigning role.
Executing (default): INSERT INTO `Reactions` (`id`,`embedId`,`userId`,`emoji`,`roleId`,`createdAt`,`updatedAt`) VALUES (NULL,$1,$2,$3,$4,$5,$6);
[ready.js] Reaction collected on message 1253487126411219106 by user daggerbyte
[ready.js] Reaction removed on message 1253487126411219106 by user daggerbyte
[handleRoleAssignment] Role @⚜️ NOBLESSE OBLIGE removed from user daggerbyte for reaction <:noblesse:12528558728924406666>.
[ready.js] Reaction collected on message 1253487126411219106 by user daggerbyte
[handleRoleAssignment] Role @⚜️ NOBLESSE OBLIGE added to user daggerbyte for reaction <:noblesse:1252855872892440666>.
这是
roles.js
代码:
// ./commands/roles.js
const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js');
const commandConfig = require('../config/config');
const Embed = require('../models/embed');
const Reaction = require('../models/reaction');
const { handleRoleAssignment } = require('../utils/roleManager');
const EMBED_LIMIT = 2; // Limit to 2 embeds
module.exports = {
data: new SlashCommandBuilder()
.setName('roles')
.setDescription('Create an embed used for self-selected reaction roles.')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addStringOption(option =>
option.setName('reactions')
.setDescription('Comma-delimited list of reaction emojis and role names. Ex: "🔴 Red, 🟢 Green, 🔵 Blue"')
.setRequired(true))
.addStringOption(option =>
option.setName('title')
.setDescription('Title at the top of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('description')
.setDescription('Text for instructions near the top of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('footer')
.setDescription('Plain text footer at the bottom of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('thumbnail')
.setDescription('URL or @username of the image for the thumbnail in the top right corner of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('image')
.setDescription('URL or @username of the image for the main picture below the description of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('icon')
.setDescription('URL or @username of the image for the footer icon in the bottom left corner of the embed')
.setRequired(false))
.addStringOption(option =>
option.setName('color')
.setDescription('Hex code or named color for the embed. Ex: "#64ff00" or "Green"')
.setRequired(false))
.addIntegerOption(option =>
option.setName('max')
.setDescription('Max number of reaction roles allowed')
.setRequired(false)),
async execute(interaction) {
const { client, options } = interaction;
const reactionsInput = options.getString('reactions');
const description = options.getString('description') || '';
const maxReactions = options.getInteger('max');
const thumbnailInput = options.getString('thumbnail');
const imageInput = options.getString('image');
const iconInput = options.getString('icon');
const footer = options.getString('footer') || '';
const title = options.getString('title') || '';
const color = options.getString('color') || '#4e5058';
const thumbnail = await getImageUrl(thumbnailInput, client);
const image = await getImageUrl(imageInput, client);
const icon = await getImageUrl(iconInput, client);
const embed = new EmbedBuilder()
.setColor(color)
.setThumbnail(thumbnail || client.user.displayAvatarURL());
if (title.length > 0) {
embed.setTitle(title);
}
if (description.length > 0) {
embed.setDescription(description);
}
if (image.length > 0) {
embed.setImage(image);
}
if (footer.length > 0) {
embed.setFooter({ text: footer, iconURL: icon || client.user.displayAvatarURL() });
}
const reactions = reactionsInput.split(',').map(str => str.trim());
let fieldValue = '';
try {
for (const reaction of reactions) {
const match = reaction.match(/(<a?:\w+:\d+>|[^ ]+)\s+(.+)/u);
if (!match) {
throw new Error(`Invalid reaction format: ${reaction}`);
}
const [, emoji, roleName] = match;
const role = interaction.guild.roles.cache.find(r => r.name.toLowerCase() === roleName.toLowerCase());
if (!role) {
throw new Error(`Role not found: ${roleName}`);
}
fieldValue += `${emoji} <@&${role.id}>\n`;
}
embed.addFields({ name: '_ _', value: fieldValue.trim() + '\n_ _' });
const channel = commandConfig.rolesChannel
? interaction.guild.channels.cache.get(commandConfig.rolesChannel)
: interaction.channel;
if (!channel) {
throw new Error('Roles channel not found or not set.');
}
const message = await channel.send({ embeds: [embed] });
for (const reaction of reactions) {
const [emoji] = reaction.match(/(<a?:\w+:\d+>|[^ ]+)/u);
await message.react(emoji);
}
await interaction.reply({ content: 'Reaction roles embed created!', ephemeral: true });
// Manage embed limit
await manageEmbedLimit(client);
// Save new embed
const savedEmbed = await Embed.create({
messageId: message.id,
channelId: message.channel.id,
embedTitle: title,
embedDescription: description,
embedFooter: footer,
embedThumbnail: thumbnailInput || '',
embedImage: imageInput || '',
embedIcon: iconInput || '',
embedColor: color,
maxReactions: maxReactions || 0,
fieldValue: fieldValue
});
const filter = (reaction, user) => !user.bot;
const collector = message.createReactionCollector({ filter, dispose: true });
collector.on('collect', async (reaction, user) => {
await handleRoleAssignment(reaction, user, 'add', client, maxReactions, message.id);
});
collector.on('remove', async (reaction, user) => {
await handleRoleAssignment(reaction, user, 'remove', client, maxReactions, message.id);
});
} catch (error) {
console.error('Error creating reaction roles embed:', error);
if (!interaction.replied && !interaction.deferred) {
await interaction.reply({ content: `There was an error: ${error.message}`, ephemeral: true });
}
}
},
};
async function manageEmbedLimit(client) {
const savedEmbeds = await Embed.findAll({ order: [['createdAt', 'ASC']] });
while (savedEmbeds.length > EMBED_LIMIT) {
const oldestEmbed = savedEmbeds.shift();
const channel = await client.channels.fetch(oldestEmbed.channelId);
if (channel) {
const message = await channel.messages.fetch(oldestEmbed.messageId);
if (message) {
await message.delete();
}
}
await Embed.destroy({ where: { id: oldestEmbed.id } });
}
}
async function getImageUrl(input, client) {
if (!input) return '';
const customEmojiMatch = input.match(/<a?:\w+:(\d+)>/);
if (customEmojiMatch) {
const emojiId = customEmojiMatch[1];
const emoji = client.emojis.cache.get(emojiId);
if (emoji) {
return emoji.url;
}
}
const userMentionMatch = input.match(/<@!?(\d+)>/);
if (userMentionMatch) {
const userId = userMentionMatch[1];
const user = await client.users.fetch(userId);
if (user) {
return user.displayAvatarURL({ dynamic: true });
}
}
return input;
}
// my command, which functions well up until the problem detailed above...
// my interaction events handler...
// my ready event, which plays a role in this mess, I'm sure...
// my role handler, which also probably plays a role in the mess, I'm sure...
在 Discord.JS 收集器上,就像您使用过的那样
message.createReactionCollector
存储在执行内存中,因此当您重新启动代码时,它们将不再起作用。
如果您希望事件处理程序即使在重新启动时也能工作,那么我建议您使用 messageReactionAdd
和
messageReactionRemove
客户端事件。供您使用,代码可能是这样的client.on("messageReactionAdd", async (reaction, user) => {
await handleRoleAssignment(reaction, user, 'add', client, maxReactions, message.id);
})
client.on("messageReactionRemove", async (reaction, user) => {
await handleRoleAssignment(reaction, user, 'remove', client, maxReactions, message.id);
})
注意:这仅适用于缓存的消息!!