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

Discord.js 14井字棋按钮交互报错DiscordAPIError[40060]求助

问题根源分析

你的问题核心是交互确认逻辑不规范,以及游戏状态管理缺失:

  • DiscordAPIError[40060]:同一个按钮交互被多次调用reply()/update()/deferUpdate(),或者先defer后又重复确认交互。
  • 应用无响应:按钮点击后未在3秒内调用任何交互确认方法,Discord判定超时。
  • 偶尔正常:仅当代码逻辑刚好在3秒内完成且无重复确认时,才能正常运行。
修复方案
  1. 强制及时确认交互:所有按钮点击事件第一时间调用deferUpdate()(更新现有消息场景用该方法,而非deferReply),给后续逻辑争取处理时间。
  2. 避免重复确认:确保每个交互只被确认一次,逻辑分支中不要重复调用update/reply类方法。
  3. 维护游戏状态:用对象存储每个对局的状态(当前玩家、棋盘格子状态),避免重复操作或逻辑冲突。
  4. 按钮ID携带状态:按钮ID包含格子坐标,方便快速定位操作位置。
优化后的完整代码示例
const { Client, GatewayIntentBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// 存储活跃对局:key为频道ID+用户ID,value为游戏状态
const activeGames = new Map();

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

// 触发游戏的命令(示例:/tic-tac-toe)
client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== 'tic-tac-toe') return;

  const gameKey = `${interaction.channelId}-${interaction.user.id}`;
  // 初始化游戏状态
  activeGames.set(gameKey, {
    currentPlayer: 'X', // X先手
    board: Array(9).fill(null), // 9个格子初始为空
  });

  // 构建井字棋按钮行
  const rows = [];
  for (let i = 0; i < 3; i++) {
    const row = new ActionRowBuilder();
    for (let j = 0; j < 3; j++) {
      const index = i * 3 + j;
      row.addComponents(
        new ButtonBuilder()
          .setCustomId(`ttt-${index}`)
          .setStyle(ButtonStyle.Secondary)
          .setLabel(' ')
      );
    }
    rows.push(row);
  }

  await interaction.reply({ content: '井字棋游戏开始!X先下', components: rows });
});

// 处理按钮点击逻辑
client.on('interactionCreate', async interaction => {
  if (!interaction.isButton() || !interaction.customId.startsWith('ttt-')) return;

  // 第一时间确认交互,避免超时
  await interaction.deferUpdate();

  const gameKey = `${interaction.channelId}-${interaction.user.id}`;
  const game = activeGames.get(gameKey);
  if (!game) {
    await interaction.editReply({ content: '该游戏已结束或不存在,请重新发起!', components: [] });
    return;
  }

  // 解析按钮对应的格子索引
  const index = parseInt(interaction.customId.split('-')[1]);
  if (game.board[index] !== null) {
    await interaction.editReply({ content: `该格子已被${game.board[index]}占用,请选择其他格子!`, components: interaction.message.components });
    return;
  }

  // 更新棋盘状态并切换玩家
  game.board[index] = game.currentPlayer;
  game.currentPlayer = game.currentPlayer === 'X' ? 'O' : 'X';

  // 更新按钮样式与文本
  const updatedRows = interaction.message.components.map(row => {
    return ActionRowBuilder.from(row).setComponents(
      row.components.map(btn => {
        const btnIndex = parseInt(btn.customId.split('-')[1]);
        if (btnIndex === index) {
          return ButtonBuilder.from(btn)
            .setStyle(game.board[index] === 'X' ? ButtonStyle.Primary : ButtonStyle.Danger)
            .setLabel(game.board[index])
            .setDisabled(true); // 点击后禁用按钮,防止重复触发
        }
        return btn;
      })
    );
  });

  // 检查胜负或平局
  const winner = checkWinner(game.board);
  let content = `轮到${game.currentPlayer}下棋!`;
  if (winner) {
    content = `🎉 ${winner}获胜!游戏结束!`;
    // 禁用所有按钮
    updatedRows.forEach(row => row.components.forEach(btn => btn.setDisabled(true)));
    activeGames.delete(gameKey); // 清理游戏状态
  } else if (game.board.every(cell => cell !== null)) {
    content = '🤝 平局!游戏结束!';
    activeGames.delete(gameKey);
  }

  // 更新消息内容与组件
  await interaction.editReply({ content, components: updatedRows });
});

// 胜负判断辅助函数
function checkWinner(board) {
  const winPatterns = [
    [0,1,2], [3,4,5], [6,7,8], // 横向
    [0,3,6], [1,4,7], [2,5,8], // 纵向
    [0,4,8], [2,4,6] // 对角线
  ];

  for (const pattern of winPatterns) {
    const [a, b, c] = pattern;
    if (board[a] && board[a] === board[b] && board[a] === board[c]) {
      return board[a];
    }
  }
  return null;
}

client.login('你的机器人令牌');
关键修复点说明
  • 及时确认交互:按钮点击后立即调用deferUpdate(),确保Discord在3秒内收到确认,避免超时提示。
  • 单一交互确认:后续仅调用一次editReply(),杜绝重复确认导致的40060错误。
  • 状态管理:用activeGames存储对局状态,防止无状态导致的逻辑混乱。
  • 按钮禁用:点击过的格子按钮直接禁用,避免重复触发交互。
  • 胜负检测:每次操作后自动检查胜负,及时结束游戏并清理状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:30:59