Discord JS多模态交互报错及方法缺失问题求助
问题解决:Discord连续模态框员工申请流程
错误1:ValidationError: Expected the value to be a string or number
原因
代码中question1文本输入框未设置setStyle()方法,TextInputStyle是必填枚举参数,未传入时会使用undefined,触发Shapeshift验证失败。
修复
给question1补充样式设置:
const question1 = new TextInputBuilder() .setCustomId('question1') .setLabel('Do you have a working mic?') .setPlaceholder('Yes or no') .setStyle(TextInputStyle.Short) // 新增此行 .setRequired(true);
错误2:连续模态框交互逻辑问题
关于i.awaitModalSubmit()不存在
该方法属于discord.js v14+的客户端级收集器方法,正确调用方式为client.awaitModalSubmit(),而非直接在交互对象上调用。
关于ModalSubmitInteraction无showModal()方法
Discord交互规则限制:模态框必须由用户主动触发的交互(如按钮、选择菜单)展示,ModalSubmitInteraction是用户提交模态框后的回调对象,无法直接调用showModal。必须先完成当前交互的响应(回复/延迟回复),再通过其他交互触发下一个模态框。
重构后的完整实现代码
collector.on('collect', async i => { if (i.customId !== 'applications') return; // 定义所有文本输入框(修复question1的style问题) const roleInput = new TextInputBuilder() .setCustomId('roleInput') .setLabel('Role') .setPlaceholder('Developer, Moderator, Builder, etc.') .setStyle(TextInputStyle.Short) .setRequired(true); const ignInput = new TextInputBuilder() .setCustomId('ignInput') .setLabel('Minecraft Username') .setStyle(TextInputStyle.Short) .setRequired(true) .setPlaceholder('Username'); const question1 = new TextInputBuilder() .setCustomId('question1') .setLabel('Do you have a working mic?') .setPlaceholder('Yes or no') .setStyle(TextInputStyle.Short) // 修复此处 .setRequired(true); const question2 = new TextInputBuilder() .setCustomId('question3') .setLabel('How much time can you spend per week?') .setStyle(TextInputStyle.Short) .setRequired(true) .setPlaceholder('___ hours'); const question3 = new TextInputBuilder() .setCustomId('question4') .setLabel('Why should we choose you?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question4 = new TextInputBuilder() .setCustomId('question5') .setLabel('Why do you want to be staff?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question5 = new TextInputBuilder() .setCustomId('question6') .setLabel('What staff experience do you have?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question6 = new TextInputBuilder() .setCustomId('question7') .setLabel('What would you do if someone spammed chat?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question7 = new TextInputBuilder() .setCustomId('question8') .setLabel('What if someone impersonated staff?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question8 = new TextInputBuilder() .setCustomId('question9') .setLabel('What if someone was bigoted in chat?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question9 = new TextInputBuilder() .setCustomId('question10') .setLabel('What if someone were hacking/exploiting?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question10 = new TextInputBuilder() .setCustomId('question11') .setLabel('What if someone doxxed another user?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question11 = new TextInputBuilder() .setCustomId('question12') .setLabel('What if you saw someone abusing staff?') .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setPlaceholder('Paragraph'); const question12 = new TextInputBuilder() .setCustomId('question13') .setLabel('Anything else you want us to know?') .setStyle(TextInputStyle.Paragraph) .setRequired(false) .setPlaceholder('Paragraph'); // 构建三个模态框 const modal1 = new ModalBuilder() .setTitle('Staff Application - Part 1') .setCustomId('applicationModal1') .addComponents( new ActionRowBuilder().addComponents(roleInput), new ActionRowBuilder().addComponents(ignInput), new ActionRowBuilder().addComponents(question1), new ActionRowBuilder().addComponents(question2), new ActionRowBuilder().addComponents(question3) ); const modal2 = new ModalBuilder() .setTitle('Staff Application - Part 2') .setCustomId('applicationModal2') .addComponents( new ActionRowBuilder().addComponents(question4), new ActionRowBuilder().addComponents(question5), new ActionRowBuilder().addComponents(question6), new ActionRowBuilder().addComponents(question7), new ActionRowBuilder().addComponents(question8) ); const modal3 = new ModalBuilder() .setTitle('Staff Application - Part 3') .setCustomId('applicationModal3') .addComponents( new ActionRowBuilder().addComponents(question9), new ActionRowBuilder().addComponents(question10), new ActionRowBuilder().addComponents(question11), new ActionRowBuilder().addComponents(question12) ); // 存储用户的申请数据 const applicationData = {}; // 展示第一个模态框 await i.showModal(modal1); // 监听第一个模态框提交 const firstSubmit = await client.awaitModalSubmit({ filter: int => int.customId === 'applicationModal1' && int.user.id === i.user.id, time: 600000 // 10分钟超时 }).catch(() => null); if (!firstSubmit) { return i.followUp({ content: '申请超时,请重新发起。', ephemeral: true }); } // 保存第一部分数据 applicationData.role = firstSubmit.fields.getTextInputValue('roleInput'); applicationData.ign = firstSubmit.fields.getTextInputValue('ignInput'); applicationData.q1 = firstSubmit.fields.getTextInputValue('question1'); applicationData.q2 = firstSubmit.fields.getTextInputValue('question3'); applicationData.q3 = firstSubmit.fields.getTextInputValue('question4'); // 回复第一个提交,然后触发第二个模态框 await firstSubmit.deferReply({ ephemeral: true }); // 创建临时按钮触发第二个模态框 const nextButton = new ButtonBuilder() .setCustomId('nextToModal2') .setLabel('继续填写第二部分') .setStyle(ButtonStyle.Primary); await firstSubmit.followUp({ components: [new ActionRowBuilder().addComponents(nextButton)], ephemeral: true }); // 监听按钮点击,展示第二个模态框 const buttonClick2 = await client.awaitMessageComponent({ filter: int => int.customId === 'nextToModal2' && int.user.id === i.user.id, time: 600000 }).catch(() => null); if (!buttonClick2) { return firstSubmit.followUp({ content: '申请超时,请重新发起。', ephemeral: true }); } await buttonClick2.showModal(modal2); // 监听第二个模态框提交 const secondSubmit = await client.awaitModalSubmit({ filter: int => int.customId === 'applicationModal2' && int.user.id === i.user.id, time: 600000 }).catch(() => null); if (!secondSubmit) { return buttonClick2.followUp({ content: '申请超时,请重新发起。', ephemeral: true }); } // 保存第二部分数据 applicationData.q4 = secondSubmit.fields.getTextInputValue('question5'); applicationData.q5 = secondSubmit.fields.getTextInputValue('question6'); applicationData.q6 = secondSubmit.fields.getTextInputValue('question7'); applicationData.q7 = secondSubmit.fields.getTextInputValue('question8'); applicationData.q8 = secondSubmit.fields.getTextInputValue('question9'); // 回复第二个提交,触发第三个模态框 await secondSubmit.deferReply({ ephemeral: true }); const nextButton3 = new ButtonBuilder() .setCustomId('nextToModal3') .setLabel('继续填写第三部分') .setStyle(ButtonStyle.Primary); await secondSubmit.followUp({ components: [new ActionRowBuilder().addComponents(nextButton3)], ephemeral: true }); // 监听按钮点击,展示第三个模态框 const buttonClick3 = await client.awaitMessageComponent({ filter: int => int.customId === 'nextToModal3' && int.user.id === i.user.id, time: 600000 }).catch(() => null); if (!buttonClick3) { return secondSubmit.followUp({ content: '申请超时,请重新发起。', ephemeral: true }); } await buttonClick3.showModal(modal3); // 监听第三个模态框提交 const thirdSubmit = await client.awaitModalSubmit({ filter: int => int.customId === 'applicationModal3' && int.user.id === i.user.id, time: 600000 }).catch(() => null); if (!thirdSubmit) { return buttonClick3.followUp({ content: '申请超时,请重新发起。', ephemeral: true }); } // 保存第三部分数据 applicationData.q9 = thirdSubmit.fields.getTextInputValue('question10'); applicationData.q10 = thirdSubmit.fields.getTextInputValue('question11'); applicationData.q11 = thirdSubmit.fields.getTextInputValue('question12'); applicationData.q12 = thirdSubmit.fields.getTextInputValue('question13'); // 完成申请,回复用户 await thirdSubmit.reply({ content: '你的申请已提交,正在审核中。', ephemeral: true }); // 构建并发送审核嵌入 const applicationEmbed = new EmbedBuilder() .setTitle('员工申请') .setDescription(`申请人:${i.user}`) .setAuthor({ name: i.user.tag, iconURL: i.user.displayAvatarURL() }) .setThumbnail(i.user.displayAvatarURL()) .addFields( { name: '申请职位', value: applicationData.role }, { name: 'Minecraft用户名', value: applicationData.ign }, { name: '你是否有可用的麦克风?', value: applicationData.q1 }, { name: '每周可投入时长', value: applicationData.q2 }, { name: '我们为什么选择你?', value: applicationData.q3 }, { name: '你为什么想成为员工?', value: applicationData.q4 }, { name: '你有过哪些员工经验?', value: applicationData.q5 }, { name: '遇到刷屏用户你会怎么做?', value: applicationData.q6 }, { name: '遇到冒充员工的用户你会怎么做?', value: applicationData.q7 }, { name: '遇到发表仇恨言论的用户你会怎么做?', value: applicationData.q8 }, { name: '遇到作弊/利用漏洞的用户你会怎么做?', value: applicationData.q9 }, { name: '遇到人肉其他用户的用户你会怎么做?', value: applicationData.q10 }, { name: '遇到滥用员工权限的人你会怎么做?', value: applicationData.q11 }, { name: '其他想告诉我们的内容', value: applicationData.q12 || '无' } ) .setColor(0xc50549); await applicationChannel.send({ embeds: [applicationEmbed] }); console.log('收到新的员工申请'); });
代码优化点
- 修复了
question1缺失样式的验证错误。 - 使用客户端级收集器替代嵌套的全局监听器,避免内存泄漏和重复触发。
- 遵循Discord交互规则,通过临时按钮触发后续模态框,解决
ModalSubmitInteraction无法直接展示模态框的问题。 - 统一存储用户输入数据,避免跨交互引用字段的混乱。
内容的提问来源于stack exchange,提问作者RedstoniaYT
相关产品推荐
相关产品推荐

