Issues with Discord.js Embed and Awaiting User Response

I’m encountering some challenges with my Discord bot code, and I’m still learning JavaScript, so please be patient with me.

I aim to develop a command that presents a message embed with choices, and afterward, it should wait for user input to show the corresponding information.

Here’s the code I’m working with:

if (message.content.startsWith(prefix + 'stats')) {
  const statsEmbed = new Discord.MessageEmbed()
    .setTitle('Team Statistics')
    .setAuthor('PHOENIX GUILD')
    .setColor('#FF5733')
    .setDescription('Choose a category below:')
    .setTimestamp()
    .addField('A. Battle Victories', 'Type "A" to view battle stats')
    .addField('B. Arena Points', 'Type "B" to view arena records')

  message.channel.send({embeds: [statsEmbed]})
  
  message.channel.awaitMessages(reply => reply.content === 'A', {
    max: 1,
    time: 15000,
    errors: ['timeout']
  })
  message.channel.send(gameData.BattleStats)
  
  message.channel.awaitMessages(reply => reply.content === 'B', {
    max: 1, 
    time: 15000,
    errors: ['timeout']
  })
  message.channel.send(gameData.ArenaStats)
}

The issue I’m facing is that both responses are being sent at once rather than waiting for user input. The embed shows up, but then all the data is displayed immediately without giving me a chance to choose an option.

Additionally, I want to be able to update these stats with specific commands, but I’m having trouble with that too. Whenever I attempt to add new entries, they replace the existing ones instead of adding to the list.

Can anyone guide me on what I might be doing wrong with the message collector?

you’re missing .then() after awaitMessages - your code’s running everything at once instead of waiting. add .then(collected => { message.channel.send(gameData.BattleStats); }) after the first awaitMessages, and do the same for the second one. also throw in a filter to check it’s the same user responding.

The Problem: Your Discord bot’s awaitMessages calls are executing concurrently, causing both responses to be sent immediately instead of waiting for user input. The embed displays correctly, but the bot doesn’t wait for the user to select an option before sending the corresponding data. Additionally, your current code uses multiple awaitMessages collectors which compete with each other, leading to the unintended behavior.

:thinking: Understanding the “Why” (The Root Cause):

The core issue lies in how your code handles asynchronous operations. awaitMessages in Discord.js is an asynchronous function; it returns a Promise. Your code initiates both awaitMessages calls, but it doesn’t wait for either one to resolve before sending the subsequent messages. JavaScript’s event loop doesn’t pause execution while awaiting Promises unless you explicitly use await. Since both awaitMessages calls are initiated almost simultaneously, both Promises resolve (or timeout), and the bot sends both gameData.BattleStats and gameData.ArenaStats regardless of the user’s input. The use of separate collectors further exacerbates the issue; they are competing for the same responses, leading to unpredictable behavior.

:gear: Step-by-Step Guide:

Step 1: Implement a Single awaitMessages Collector with a Combined Filter:

Replace your current code with a single awaitMessages collector that uses a filter to accept either ‘A’ or ‘B’ responses from the user who initiated the command. This ensures only one collector is active, preventing the race condition you’re experiencing. Crucially, we also add a check to make sure only the original user can respond.

if (message.content.startsWith(prefix + 'stats')) {
  const statsEmbed = new Discord.MessageEmbed()
    .setTitle('Team Statistics')
    .setAuthor('PHOENIX GUILD')
    .setColor('#FF5733')
    .setDescription('Choose a category below:')
    .setTimestamp()
    .addField('A. Battle Victories', 'Type "A" to view battle stats')
    .addField('B. Arena Points', 'Type "B" to view arena records')

  message.channel.send({ embeds: [statsEmbed] });

  const filter = m => m.author.id === message.author.id && ['A', 'B'].includes(m.content);

  message.channel.awaitMessages({ filter, max: 1, time: 15000, errors: ['time'] })
    .then(collected => {
      const response = collected.first().content;
      if (response === 'A') {
        message.channel.send(gameData.BattleStats);
      } else if (response === 'B') {
        message.channel.send(gameData.ArenaStats);
      }
    })
    .catch(err => {
      if (err.message === 'time') {
        message.channel.send('Time ran out, please try again.');
      } else {
        console.error('Error collecting messages:', err);
        message.channel.send('An unexpected error occurred.');
      }
    });
}

Step 2: Handle Errors and Timeouts:

The .catch block handles potential errors, specifically timeouts. This improves the user experience by providing informative messages if the user doesn’t respond within the allotted time or if an unexpected error occurs. It also logs errors for debugging purposes. You may want to add more robust error handling (e.g. logging to a file) for a production environment.

Step 3: Consider Asynchronous/Await for Improved Readability (Optional):

While the above code uses .then() and .catch(), you could rewrite it using async/await for potentially improved readability if you prefer that style:

async function handleStatsCommand(message) {
  // ... (Embed creation remains the same) ...

  const filter = m => m.author.id === message.author.id && ['A', 'B'].includes(m.content);

  try {
    const collected = await message.channel.awaitMessages({ filter, max: 1, time: 15000, errors: ['time'] });
    const response = collected.first().content;
    if (response === 'A') {
      await message.channel.send(gameData.BattleStats);
    } else if (response === 'B') {
      await message.channel.send(gameData.ArenaStats);
    }
  } catch (error) {
    if (error.message === 'time') {
      await message.channel.send('Time ran out, please try again.');
    } else {
      console.error('Error collecting messages:', error);
      await message.channel.send('An unexpected error occurred.');
    }
  }
}


if (message.content.startsWith(prefix + 'stats')) {
  handleStatsCommand(message);
}

:mag: Common Pitfalls & What to Check Next:

  • Incorrect User ID: Double-check that message.author.id is correctly retrieving the ID of the user who sent the initial command.
  • Case Sensitivity: The filter is case-sensitive. If you want to accept both uppercase and lowercase input, convert the user’s response and your accepted values to lowercase for comparison: m.content.toLowerCase() === 'a' and m.content.toLowerCase() === 'b'.
  • Rate Limits: Be mindful of Discord’s rate limits. If you add more functionality, ensure your bot doesn’t exceed these limits.
  • Error Handling: The added error handling is basic. For production bots, implement more detailed error logging and potentially more sophisticated error recovery mechanisms.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

the problem is that awaitMessages runs concurrently, so responses come out at the same time. you should use a single collector with a filter for both options. also, consider using async/await for clarity and better control over the flow.

The issue is awaitMessages returns a Promise, but you’re not handling it right. Your code runs both awaitMessages calls then immediately sends both responses without waiting for user input. You need proper promise handling or async/await. Wrap your logic in a try-catch block and await each message collection: javascript try { const filter = m => m.author.id === message.author.id && ['A', 'B'].includes(m.content); const collected = await message.channel.awaitMessages({filter, max: 1, time: 15000}); const response = collected.first().content; if (response === 'A') { message.channel.send(gameData.BattleStats); } else if (response === 'B') { message.channel.send(gameData.ArenaStats); } } catch (error) { message.channel.send('No response received in time.'); } This waits for one response and handles it properly instead of setting up multiple collectors at once.

Skip the Discord.js collectors and async headaches - just automate it with Latenode. I’ve built tons of Discord bots where users interact with embeds and get different responses back.

Latenode handles async message waiting automatically. Set up a workflow that sends the embed, waits for input, then branches based on what they type. No promise chains or messy error handling.

For stats updates, Latenode connects straight to databases or spreadsheets. When someone runs an update command, it appends data instead of replacing it. I use this for leaderboards and game stats constantly.

The flow: Discord webhook triggers → Send embed → Wait for response → Check A or B → Send stats. You can add multiple choice paths without the code turning into spaghetti.

Your current approach needs proper async/await handling like others said, but automation makes this way cleaner. No more fighting collectors or timeout errors.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.