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

Discord.js反应功能报错:无法读取'react'属性及消息发送失败

Fixing Discord Bot Errors: "Can not send messages to this user" and "Cannot read property 'react' of undefined"

Let's break down your two errors and walk through fixing the root causes in your code:

Error 1: DiscordAPIError: Can not send messages to this user

This happens when your bot tries to send a direct message (DM) to a user who has either:

  • Disabled DMs from server members in their privacy settings, or
  • Left the server where the command was triggered (though this is less likely here)

The fix is to add a fallback: if sending a DM fails, send the message to the channel where the command was invoked instead.

Error 2: TypeError: Cannot read property 'react' of undefined

This occurs because sentMessage1 is undefined. The issue comes from mixing async/await and .then() incorrectly:

  • Your accessSpreadsheet function doesn't return a value, so the promise chain doesn't pass the sent message object to sentMessage1
  • You're using await on the entire promise chain but not capturing the resolved value correctly

Other Critical Issues in Your Code

Before we get to the fixed code, let's note a few other problems that will cause bugs down the line:

  1. Nested Event Listeners: You're attaching a new messageReactionAdd listener every time a user clicks ✅. This will create duplicate listeners that trigger multiple times for a single reaction.
  2. Redundant Function Definitions: You define accessSpreadsheet three times in nested scopes. This is messy and hard to maintain—we'll refactor it into a single reusable function.
  3. Inefficient Spreadsheet Calls: You're making multiple separate calls to get cells from the same spreadsheet. We can combine these into one call to reduce latency.

Fixed Code

Here's the cleaned-up, fixed version of your code with comments explaining each change:

// Reusable spreadsheet access function (no more redundant definitions!)
async function accessSpreadsheet(rangeOptions) {
  const doc = new GoogleSpreadsheet('1qA11t460-ceILmwu6RtfiPGb_n9MUD_7z6Ld7I_Z6yc');
  await promisify(doc.useServiceAccountAuth)(creds);
  const info = await promisify(doc.getInfo)();
  const sheet = info.worksheets[0];
  return promisify(sheet.getCells)(rangeOptions);
}

client.on('message', async message => {
  if (message.content.toLowerCase().startsWith('!bot')) {
    try {
      // First embed: 5 numbered options
      const embed = new RichEmbed()
        .setColor('#0099ff')
        .setTitle("A title");

      // Get cells for the first embed
      const cells = await accessSpreadsheet({
        'min-row': 2,
        'max-row': 7,
        'min-col': 16,
        'max-col': 16,
        'return-empty': true,
      });

      // Add fields for the first 5 options
      for (let i = 0; i < 5 && cells[i]; i++) {
        embed.addField('Name', `•${i+1}⃣ ${cells[i].value}`, false);
      }

      // Send embed with fallback to channel if DM fails
      let sentMessage;
      try {
        sentMessage = await message.author.send(embed);
      } catch (dmErr) {
        console.log("Failed to send DM, sending to channel instead:", dmErr);
        sentMessage = await message.channel.send(embed);
      }

      // Add 1-5 reactions to the sent message
      for (let n = 1; n <= 5; n++) {
        await sentMessage.react(`${n}⃣`);
      }

      // Wait for user to select a reaction
      const filter = (reaction, user) => ['1', '2', '3', '4', '5'].includes(reaction.emoji.name.slice(0, 1)) && user.id === message.author.id;
      const collected = await sentMessage.awaitReactions(filter, { max: 1, time: 4000 });

      if (collected.size === 0) {
        await message.channel.send('Time ran out');
        return;
      }

      const selectedReaction = collected.first();
      const selectedNumber = selectedReaction.emoji.name.slice(0, 1);

      // Handle selected option (we'll focus on case '1' as that's where your error was)
      if (selectedNumber === '1') {
        // Get all needed cells in one call (more efficient!)
        const [nameCells, descCells, discordCells, dateCells, validateCells] = await Promise.all([
          accessSpreadsheet({ 'min-row': 2, 'max-row': 7, 'min-col': 16, 'max-col': 16, 'return-empty': true }),
          accessSpreadsheet({ 'min-row': 2, 'max-row': 7, 'min-col': 17, 'max-col': 17, 'return-empty': true }),
          accessSpreadsheet({ 'min-row': 2, 'max-row': 7, 'min-col': 18, 'max-col': 18, 'return-empty': true }),
          accessSpreadsheet({ 'min-row': 2, 'max-row': 7, 'min-col': 20, 'max-col': 20, 'return-empty': true }),
          accessSpreadsheet({ 'min-row': 2, 'max-row': 7, 'min-col': 21, 'max-col': 21, 'return-empty': true }),
        ]);

        // Create the detail embed
        const embed1 = new RichEmbed()
          .setColor('#0099ff')
          .setTitle(`You selected: ${nameCells[0]?.value || 'No name'}`)
          .setDescription(`Description:\n\n${descCells[0]?.value || 'No description'}`)
          .addField(`Discord:`, `${discordCells[0]?.value || 'No Discord'}`, true)
          .addField(`Date:`, `${dateCells[0]?.value || 'No date'}`, true)
          .addField(`To be validated with ✅:`, `Click on the reaction`);

        // Send detail embed with fallback
        let detailMessage;
        try {
          detailMessage = await message.author.send(embed1);
        } catch (dmErr) {
          console.log("Failed to send detail DM, sending to channel instead:", dmErr);
          detailMessage = await message.channel.send(embed1);
        }

        // Add ✅ reaction
        await detailMessage.react('✅');

        // Wait for user to click ✅ (use awaitReactions instead of permanent listener!)
        const validateFilter = (reaction, user) => reaction.emoji.name === '✅' && user.id === message.author.id;
        const validateCollected = await detailMessage.awaitReactions(validateFilter, { max: 1, time: 60000 });

        if (validateCollected.size === 0) {
          await message.channel.send('Validation timed out');
          return;
        }

        // Now send the final options embed
        const embed2 = new RichEmbed()
          .setColor('#0099ff')
          .addField(`Days before toornament: ${validateCells[0]?.value || 'No data'}`, `Choose options:\n1⃣\n2⃣\n3⃣\n\n**(5 sec)**`);

        // Send final embed with fallback
        let sentMessage1;
        try {
          sentMessage1 = await message.author.send(embed2);
        } catch (dmErr) {
          console.log("Failed to send options DM, sending to channel instead:", dmErr);
          sentMessage1 = await message.channel.send(embed2);
        }

        // Add 1-3 reactions (this was where your undefined error was!)
        for (let z = 1; z <= 3; z++) {
          await sentMessage1.react(`${z}⃣`);
        }

        // Wait for user to select an option
        const optionFilter = (reaction, user) => ['1', '2', '3'].includes(reaction.emoji.name.slice(0, 1)) && user.id === message.author.id;
        const optionCollected = await sentMessage1.awaitReactions(optionFilter, { max: 1, time: 5000 });

        if (optionCollected.size === 0) {
          await message.channel.send('Time ran out');
          return;
        }

        const optionReaction = optionCollected.first();
        switch (optionReaction.emoji.name.slice(0, 1)) {
          case '1':
            await message.channel.send('1');
            break;
          case '2':
            await message.channel.send('You chose `two`.');
            break;
          case '3':
            await message.channel.send('You chose `3`.');
            break;
        }
      } else {
        // Handle other options (2-5)
        await message.channel.send(`You chose \`${selectedNumber}\`.`);
      }
    } catch (err) {
      console.error("Command error:", err);
    }
  }
});

Key Fixes Explained

  1. Reusable Spreadsheet Function: We moved accessSpreadsheet to the top as a single function, so you don't repeat code three times.
  2. DM Fallback Logic: We wrap all message.author.send calls in try/catch blocks, and if DM fails, send the message to the command channel instead. This fixes the "Can not send messages to this user" error.
  3. Correct Async/Await Usage: We removed the messy mix of await and .then()—now we properly capture the sent message objects, so sentMessage1 is never undefined (fixing the second error).
  4. Avoid Nested Event Listeners: Instead of attaching a permanent messageReactionAdd listener, we use awaitReactions to wait for the ✅ reaction. This prevents duplicate listeners and memory leaks.
  5. Efficient Spreadsheet Calls: We use Promise.all to fetch all required cells in parallel, reducing the number of API calls and speeding up your bot.

内容的提问来源于stack exchange,提问作者Gérard Gaillard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:22:27