Discord.js V14中guild.leave()触发TypeError错误求助
Discord.js V14 机器人退服命令报错排查解决
问题
基于Discord.js V14开发的服务器管理命令,功能为展示机器人已加入的服务器下拉菜单,选择服务器后弹出踢除机器人的确认按钮,但点击按钮时终端抛出TypeError: Cannot read properties of undefined (reading 'leave')错误,报错指向await guild.leave();代码行。
错误信息
C:\Users\kalam\OneDrive\Υπολογιστής\Botanser\Commands\Moderation\servers.js:76 await guild.leave(); ^ TypeError: Cannot read properties of undefined (reading 'leave')
已启用Intents
const { Guilds, GuildMembers, GuildMessages, MessageContent, DirectMessages, GuildVoiceStates } = GatewayIntentBits; intents: [Guilds, GuildMembers, GuildMessages, MessageContent, DirectMessages, GuildVoiceStates]
原代码
const { SlashCommandBuilder } = require('@discordjs/builders'); const { EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('servers') .setDescription('Displays a dropdown menu with all the servers the bot is in.'), async execute(interaction) { if (interaction.user.id !== '759753313553743893') { return interaction.reply({ content: 'You are not authorized to use this command!', ephemeral: true }); } // Fetch all the guilds the bot is in const guilds = await interaction.client.guilds.fetch(); // Create an object with guild IDs as properties and guild names as values const guildList = {}; guilds.forEach(guild => { guildList[guild.id] = guild.name; }); // Create the options for the select menu const serverOptions = Object.keys(guildList).map(guildID => ({ label: guildList[guildID], value: guildID })); // Create the select menu const serverMenu = new StringSelectMenuBuilder() .setCustomId('server_menu') .setPlaceholder('Select a server') .addOptions(serverOptions); // Create the initial embed message with the select menu const initialEmbed = new EmbedBuilder() .setTitle('Server List') .setDescription('Please select a server from the dropdown menu.') .setColor('#00ff00') .setFooter({ text: 'Server Management System' }) .setTimestamp(); const row = new ActionRowBuilder().addComponents(serverMenu); const initialMessage = await interaction.reply({ embeds: [initialEmbed], components: [row], fetchReply: true, ephemeral: true }); // Create the button that will kick the bot from the selected server const kickButton = new ButtonBuilder() .setCustomId('kick_button') .setLabel('Kick bot') .setStyle('Danger'); // Listen for the select menu to be interacted with const filter = (interaction) => { return interaction.customId === 'server_menu'; }; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 15000 }); collector.on('collect', async (interaction) => { // Get the selected server ID and guild object const selectedServer = interaction.values[0]; const guild = interaction.client.guilds.cache.get(selectedServer); // Edit the embed message with the selected server's name and the kick button const updatedEmbed = new EmbedBuilder() .setTitle(guild.name) .setDescription('Do you want to kick the bot from this server?') .setColor('#ff0000') .setFooter({ text: 'Server Management System' }) .setTimestamp(); const row = new ActionRowBuilder().addComponents(kickButton); await interaction.update({ embeds: [updatedEmbed], components: [row], ephemeral: true }); }); // Listen for the kick button to be interacted with const buttonFilter = (interaction) => { return interaction.customId === 'kick_button'; }; const buttonCollector = interaction.channel.createMessageComponentCollector({ buttonFilter, time: 15000 }); buttonCollector.on('collect', async (interaction) => { // Get the selected server ID and guild object const selectedServer = interaction.message.embeds[0].title; const guildID = Object.keys(guildList).find(id => guildList[id] === selectedServer); const guild = await interaction.client.guilds.fetch(guildID); if (guild) { // Leave the guild await guild.leave(); // Reply to the interaction with a success message await interaction.update({ content: `The bot has been kicked from ${selectedServer}.`, embeds: [], components: [], ephemeral: true }); } else { // If the guild is not found, reply to the interaction with an error message await interaction.update({ content: `Could not find ${selectedServer}.`, embeds: [], components: [], ephemeral: true }); } }) } }
问题原因与修复方案
原因
原代码中按钮回调通过Embed标题匹配服务器ID的逻辑存在致命缺陷:
- 若存在名称相同的服务器,
find方法只会返回第一个匹配的ID,导致后续操作指向错误服务器或找不到服务器 - 若服务器名称包含特殊字符或被修改,会无法匹配到对应ID,最终
guild变量为undefined,调用leave()时报错
修复代码
const { SlashCommandBuilder } = require('@discordjs/builders'); const { EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('servers') .setDescription('Displays a dropdown menu with all the servers the bot is in.'), async execute(interaction) { if (interaction.user.id !== '759753313553743893') { return interaction.reply({ content: 'You are not authorized to use this command!', ephemeral: true }); } // Fetch all the guilds the bot is in const guilds = await interaction.client.guilds.fetch(); // Create the options for the select menu const serverOptions = guilds.map(guild => ({ label: guild.name, value: guild.id })); // Create the select menu const serverMenu = new StringSelectMenuBuilder() .setCustomId('server_menu') .setPlaceholder('Select a server') .addOptions(serverOptions); // Create the initial embed message with the select menu const initialEmbed = new EmbedBuilder() .setTitle('Server List') .setDescription('Please select a server from the dropdown menu.') .setColor('#00ff00') .setFooter({ text: 'Server Management System' }) .setTimestamp(); const row = new ActionRowBuilder().addComponents(serverMenu); await interaction.reply({ embeds: [initialEmbed], components: [row], ephemeral: true }); // Listen for the select menu to be interacted with const filter = (i) => i.customId === 'server_menu' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 15000 }); collector.on('collect', async (i) => { const selectedServerId = i.values[0]; const guild = interaction.client.guilds.cache.get(selectedServerId); // 创建带服务器ID的按钮 const kickButton = new ButtonBuilder() .setCustomId(`kick_button_${selectedServerId}`) .setLabel('Kick bot') .setStyle('Danger'); const updatedEmbed = new EmbedBuilder() .setTitle(guild.name) .setDescription('Do you want to kick the bot from this server?') .setColor('#ff0000') .setFooter({ text: 'Server Management System' }) .setTimestamp(); const buttonRow = new ActionRowBuilder().addComponents(kickButton); await i.update({ embeds: [updatedEmbed], components: [buttonRow], ephemeral: true }); }); // 监听按钮交互 const buttonFilter = (i) => i.customId.startsWith('kick_button_') && i.user.id === interaction.user.id; const buttonCollector = interaction.channel.createMessageComponentCollector({ buttonFilter, time: 15000 }); buttonCollector.on('collect', async (i) => { // 从customId提取服务器ID const guildId = i.customId.split('_')[2]; const guild = await interaction.client.guilds.fetch(guildId).catch(() => null); if (guild) { await guild.leave(); await i.update({ content: `The bot has been kicked from ${guild.name}.`, embeds: [], components: [], ephemeral: true }); } else { await i.update({ content: 'Could not find the specified server.', embeds: [], components: [], ephemeral: true }); } }) } }
修复要点
- 将服务器ID直接嵌入按钮的
customId中,避免依赖名称匹配 - 给收集器添加用户ID过滤,防止其他用户触发操作
- 对
guild.fetch添加错误捕获,避免因权限或服务器不存在导致崩溃
内容的提问来源于stack exchange,提问作者Illanser
相关产品推荐
相关产品推荐

