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
accessSpreadsheetfunction doesn't return a value, so the promise chain doesn't pass the sent message object tosentMessage1 - You're using
awaiton 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:
- Nested Event Listeners: You're attaching a new
messageReactionAddlistener every time a user clicks ✅. This will create duplicate listeners that trigger multiple times for a single reaction. - Redundant Function Definitions: You define
accessSpreadsheetthree times in nested scopes. This is messy and hard to maintain—we'll refactor it into a single reusable function. - 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
- Reusable Spreadsheet Function: We moved
accessSpreadsheetto the top as a single function, so you don't repeat code three times. - DM Fallback Logic: We wrap all
message.author.sendcalls 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. - Correct Async/Await Usage: We removed the messy mix of
awaitand.then()—now we properly capture the sent message objects, sosentMessage1is never undefined (fixing the second error). - Avoid Nested Event Listeners: Instead of attaching a permanent
messageReactionAddlistener, we useawaitReactionsto wait for the ✅ reaction. This prevents duplicate listeners and memory leaks. - Efficient Spreadsheet Calls: We use
Promise.allto fetch all required cells in parallel, reducing the number of API calls and speeding up your bot.
内容的提问来源于stack exchange,提问作者Gérard Gaillard
相关产品推荐
相关产品推荐

