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

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 });
            }
        })
    }
}

修复要点

  1. 将服务器ID直接嵌入按钮的customId中,避免依赖名称匹配
  2. 给收集器添加用户ID过滤,防止其他用户触发操作
  3. 对guild.fetch添加错误捕获,避免因权限或服务器不存在导致崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 19:24:55