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

使用discord.js v14.9.0开发战斗命令时遇MessageActionRow构造函数错误

问题:discord.js v14.9.0 中 MessageActionRow 不是构造函数的报错

我正在用discord.js v14.9.0开发Discord机器人的战斗命令,代码如下:

const { SlashCommandBuilder } = require('@discordjs/builders');
const { MessageActionRow, MessageButtonComponent, Discord } = require('discord.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('combate')
        .setDescription('Empieza un combate contra un slime.'),
    async execute(interaction) {
        // Variables del usuario
        let usuario_vida = 100;
        let usuario_ataque = 0;

        // Variables del slime
        let slime_vida = 50;
        let slime_ataque = 0;

        // Función para lanzar el dado
        function rollDice(dado) {
            return Math.floor(Math.random() * dado) + 1;
        }

        // Función para mostrar el resultado del combate
        function mostrarResultado(ataque, defensa, ataque_roll, defensa_roll) {
            let resultado = "";
            resultado += `El ${ataque} ha atacado y ha hecho ${ataque_roll} puntos de daño.\n`;
            resultado += `La ${defensa} ha recibido ${ataque_roll} puntos de daño y ahora tiene ${defensa_roll} puntos de vida.`;
            return resultado;
        }

        function mostrarTablaVidas(vida_usuario, vida_slime) {
            let tabla = "```";
            tabla += `Slime: ${vida_slime} puntos de vida\n`;
            tabla += "- - -\n- - -\n- - -\n- - -\n- - -\n";
            tabla += `Usuario: ${vida_usuario} puntos de vida\n` + "```";
            return tabla;
        }

        // Función para actualizar la tabla de vidas
        async function actualizarTablaVidas(interaction, vida_usuario, vida_slime) {
            const tabla = mostrarTablaVidas(vida_usuario, vida_slime);
            const row = new MessageActionRow()
                .addComponents(
                    new MessageButtonComponent()
                        .setCustomId('ataque')
                        .setLabel('Atacar')
                        .setColor('PRIMARY')
                );
            await interaction.editReply({ content: tabla, components: [row] });
        }

        // Mostrar la tabla de vidas y el botón de ataque
        await actualizarTablaVidas(interaction, usuario_vida, slime_vida);

        // Registrar la función que se llamará cuando se haga clic en el botón de ataque
        const filter = i => i.customId === 'ataque' && i.user.id === interaction.user.id;
        const collector = interaction.channel.createMessageComponentCollector({ filter, time: 15000 });
        collector.on('interactionCreate', async (buttonInteraction) => {
            if (buttonInteraction.isButton() && buttonInteraction.customId === 'ataque') {
                // El usuario ataca al slime
                const ataque_roll = rollDice(6);
                slime_vida -= ataque_roll;
                const resultado_usuario = mostrarResultado("usuario", "slime", ataque_roll, slime_vida);

                // El slime ataca al usuario
                const defensa_roll = rollDice(4);
                usuario_vida -= defensa_roll;
                const resultado_slime = mostrarResultado("slime", "usuario", defensa_roll, usuario_vida);

                // Actualizar la tabla de vidas
                await actualizarTablaVidas(buttonInteraction, usuario_vida, vida_slime);

                // Mostrar el resultado del ataque
                await buttonInteraction.followUp({ content: resultado_usuario + "\n" + resultado_slime });

                // Comprobar si el combate ha terminado
                if (usuario_vida <= 0) {
                    await buttonInteraction.followUp({ content: "Has perdido el combate." });
                    collector.stop();
                } else if (slime_vida <= 0) {
                    await buttonInteraction.followUp({ content: "Has ganado el combate." });
                    collector.stop();
                }
            }
        });

        // Tiempo máximo de espera para el botón de ataque
        collector.on('end', async () => {
            await interaction.editReply({ content: "El combate ha terminado." });
        });
    },
};

运行命令时控制台报错:

TypeError: MessageActionRow is not a constructor
at actualizarTablaVidas (C:\Users\usago\OneDrive\Escritorio\RpgBot\commands\combate.js:43:25)
at Object.execute (C:\Users\usago\OneDrive\Escritorio\RpgBot\commands\combate.js:54:15)
at Client. (C:\Users\usago\OneDrive\Escritorio\RpgBot\index.js:33:17)
at Client.emit (node:events:513:28)
at InteractionCreateAction.handle (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\discord.js\src\client\actions\InteractionCreate.js:97:12)
at module.exports [as INTERACTION_CREATE] (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\discord.js\src\client\websocket\handlers\INTERACTION_CREATE.js:4:36)
at WebSocketManager.handlePacket (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\discord.js\src\client\websocket\WebSocketManager.js:352:31)
at WebSocketShard.onPacket (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\discord.js\src\client\websocket\WebSocketShard.js:494:22)
at WebSocketShard.onMessage (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\discord.js\src\client\websocket\WebSocketShard.js:328:10)
at callListener (C:\Users\usago\OneDrive\Escritorio\RpgBot\node_modules\ws\lib\event-target.js:290:14)

预期效果:显示生命值表格和可点击的攻击按钮。


解决方案

1. 修正组件导入语句

discord.js v14 中,组件的命名和导入逻辑有更新:

  • 移除无用的Discord全局导入
  • 按钮组件的正确名称是MessageButton,而非MessageButtonComponent
  • 修正后的导入代码:
const { SlashCommandBuilder } = require('@discordjs/builders');
const { MessageActionRow, MessageButton } = require('discord.js');

2. 修正组件实例化代码

将new MessageButtonComponent()替换为new MessageButton(),对应修改actualizarTablaVidas函数内的代码:

const row = new MessageActionRow()
    .addComponents(
        new MessageButton()
            .setCustomId('ataque')
            .setLabel('Atacar')
            .setColor('PRIMARY')
    );

3. 修复其他潜在bug

  • 组件收集器的触发事件应为collect,而非interactionCreate
  • 调用actualizarTablaVidas时,参数需传入实际变量slime_vida,而非未定义的vida_slime
  • 初始需先发送延迟回复(deferReply),否则直接调用editReply会报错
  • 按钮交互后需调用deferUpdate确认响应,避免Discord提示交互超时

修正后的完整代码
const { SlashCommandBuilder } = require('@discordjs/builders');
const { MessageActionRow, MessageButton } = require('discord.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('combate')
        .setDescription('Empieza un combate contra un slime.'),
    async execute(interaction) {
        // Variables del usuario
        let usuario_vida = 100;
        let usuario_ataque = 0;

        // Variables del slime
        let slime_vida = 50;
        let slime_ataque = 0;

        // Función para lanzar el dado
        function rollDice(dado) {
            return Math.floor(Math.random() * dado) + 1;
        }

        // Función para mostrar el resultado del combate
        function mostrarResultado(ataque, defensa, ataque_roll, defensa_roll) {
            let resultado = "";
            resultado += `El ${ataque} ha atacado y ha hecho ${ataque_roll} puntos de daño.\n`;
            resultado += `La ${defensa} ha recibido ${ataque_roll} puntos de daño y ahora tiene ${defensa_roll} puntos de vida.`;
            return resultado;
        }

        function mostrarTablaVidas(vida_usuario, vida_slime) {
            let tabla = "```";
            tabla += `Slime: ${vida_slime} puntos de vida\n`;
            tabla += "- - -\n- - -\n- - -\n- - -\n- - -\n";
            tabla += `Usuario: ${vida_usuario} puntos de vida\n` + "```";
            return tabla;
        }

        // Función para actualizar la tabla de vidas
        async function actualizarTablaVidas(interaction, vida_usuario, vida_slime) {
            const tabla = mostrarTablaVidas(vida_usuario, vida_slime);
            const row = new MessageActionRow()
                .addComponents(
                    new MessageButton()
                        .setCustomId('ataque')
                        .setLabel('Atacar')
                        .setColor('PRIMARY')
                );
            await interaction.editReply({ content: tabla, components: [row] });
        }

        // 先发送延迟回复,避免editReply报错
        await interaction.deferReply();
        // Mostrar la tabla de vidas y el botón de ataque
        await actualizarTablaVidas(interaction, usuario_vida, slime_vida);

        // Registrar la función que se llamará cuando se haga clic en el botón de ataque
        const filter = i => i.customId === 'ataque' && i.user.id === interaction.user.id;
        const collector = interaction.channel.createMessageComponentCollector({ filter, time: 15000 });
        
        collector.on('collect', async (buttonInteraction) => {
            if (buttonInteraction.isButton() && buttonInteraction.customId === 'ataque') {
                // El usuario ataca al slime
                const ataque_roll = rollDice(6);
                slime_vida -= ataque_roll;
                const resultado_usuario = mostrarResultado("usuario", "slime", ataque_roll, slime_vida);

                // El slime ataca al usuario
                const defensa_roll = rollDice(4);
                usuario_vida -= defensa_roll;
                const resultado_slime = mostrarResultado("slime", "usuario", defensa_roll, usuario_vida);

                // Actualizar la tabla de vidas
                await actualizarTablaVidas(buttonInteraction, usuario_vida, slime_vida);

                // Mostrar el resultado del ataque
                await buttonInteraction.followUp({ content: resultado_usuario + "\n" + resultado_slime });

                // Comprobar si el combate ha terminado
                if (usuario_vida <= 0) {
                    await buttonInteraction.followUp({ content: "Has perdido el combate." });
                    collector.stop();
                } else if (slime_vida <= 0) {
                    await buttonInteraction.followUp({ content: "Has ganado el combate." });
                    collector.stop();
                }
                // 确认按钮交互,避免超时提示
                await buttonInteraction.deferUpdate();
            }
        });

        // Tiempo máximo de espera para el botón de ataque
        collector.on('end', async () => {
            await interaction.editReply({ content: "El combate ha terminado.", components: [] });
        });
    },
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 09:59:58