slackapi / bolt-js

A framework to build Slack apps using JavaScript
https://slack.dev/bolt-js
MIT License
2.73k stars 391 forks source link

How to make bot reply in a thread rather than post in the channel? #1370

Closed ro-internal closed 2 years ago

ro-internal commented 2 years ago

I have a bot that's meant to reply to messages in a channel with certain keywords it's listening for. I have it deployed and it's working perfectly, but it replies in the channel rather than starting a thread to a user's message. I apologize if this seems self-explanatory, I’m fairly new to JS and programming in general. Here's my code that's meant to start the thread:

app.message("ake up", async ({ command, say }) => { try { say({ text: "I'm awake! ⭐️ Do you need assistance?", thread_ts: message.ts }); } catch (error) { console.log("err") console.error(error); } });

Nothing happens when I send a message saying "wake up". When it's the same without thread_ts: message.ts and just says say("I'm awake! ⭐️ Do you need assistance?")}, it works perfectly. and responds with that, but just as a message in the channel.

What type of issue is this? (place an x in one of the [ ])

Requirements (place an x in each of the [ ])


Attachments:

Here's the beginning of my code for reference:

require("dotenv").config(); const app = new App({ token: process.env.SLACK_BOT_TOKEN, signingSecret: process.env.SLACK_SIGNING_SECRET, socketMode:true, appToken: process.env.APP_TOKEN });

srajiang commented 2 years ago

Hey @ro-internal - Always cool to see folks starting out in development trying out our frameworks :)

I pulled down the code sample you provided above and noticed a couple of things that might be the culprit(s) here:

// did you mean 'wake up'? 
app.message("ake`` up", async ({ command, say }) => {   
  // to access message and it's properties, declare it in your callback params. 
  // Bolt will pass message as an argument to your callback when it invokes it
  try {
    say({ text: "I'm awake! ⭐️ Do you need assistance?", thread_ts: message.ts });
  } catch (error) {
      console.log("err")
    console.error(error);
  }
});

A very slightly modified version of your code works for me! Let me know whether that fixes it for you.

app.message("wake up", async ({ message, say }) => { 
  try {
    await say({ text: "I'm awake! ⭐️ Do you need assistance?", thread_ts: message.ts });
  } catch (error) {
      console.log("err");
    console.error(error);
  }
});
ro-internal commented 2 years ago

@srajiang Thank you so much, that works perfectly! Glad to see I was close, just needed to change the parameters. As for the “ake up”, I left it as that because I can’t get the bot to ignore the case of a message. So if I left it as “wake up”, it would only work with “wake up” and not if a user used “Wake up”. Tried using app.message.toLowerCase but that didn’t seem to work either.

Additionally, I did notice that any attempts to use more phrases like “wake up” that I have coded, my other messages in the same thread will be sent to the entire channel, but the Slackbot’s reply will send in the correct thread. 71A87204-365F-46F7-AFE6-A02631ED8D4B

srajiang commented 2 years ago

As for the “ake up”, I left it as that because I can’t get the bot to ignore the case of a message.

Nice @ro-internal, for this you can probably leverage a regex literal where you currently have that string "ake up"

Here's some documentation from us on that (check out Using a RegExp Pattern) here

You can play around with different regex patterns here. It's some pretty powerful stuff once you start figuring out how to express your logic - The example I've linked with "wake up" uses the /gi flags to indicate that you want to match case insensitively and globally

ro-internal commented 2 years ago

@srajiang Thank you, I’ve been learning so much with regex!!

As an aside, are you able to help me with how Slack is sending my replies to the main channel but the bot replies in the correct thread?

srajiang commented 2 years ago

@ro-internal - I'm a little confused on this last one! Based on the picture of the thread above, I see the "wake up" message which is the parent message. Then I do see your IT HELP bot responding in thread (which I believe is the behavior you expect). What do you mean by:

Slack is sending my replies to the main channel

Do you mean your bot's replies?

ro-internal commented 2 years ago

@srajiang Sorry for the confusion! So essentially what’s happening is I’ll post the message my bot is looking for in the main channel and the bot will correctly reply and make a thread. But if I reply in the same thread with the same message the bot is listening for (or any of the other phrases my bot is listening for), my message is sent to the main channel instead. But the bot still correctly replies in the thread I want my replies to go to. That’s why you’re seeing the bot’s two replies in the picture of the thread I posted, but you’re not seeing my reply that prompted the second bot reply. Hope that makes it clearer!

If you’d like, I can post my whole code here, it’s not long

srajiang commented 2 years ago

@ro-internal Yes, if you could post your code we could see what you're working with!

ro-internal commented 2 years ago

@srajiang see code below, thank you!

const { App } = require("@slack/bolt");
require("dotenv").config();
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode:true,
  appToken: process.env.APP_TOKEN
});

// Bot will listen for the phrase to show it's awake and listening
app.message("wake up", async ({ message, say }) => {
  try {
    await say({ text: "I'm awake! ⭐️ You can request to view our FAQ or submit a ticket by simplying typing 'faq' or 'submit a ticket' in the main channel." , thread_ts: message.ts});
  } catch (error) {
      console.log("err")
    console.error(error);
  }
});

// Bot will listen for the phrase and provide the form link
app.message("submit a ticket", async ({ message, say }) => {
    try {
      await say({text: "Do you need IT assistance? You can do so here: https://forms.monday.com/forms/af0537df1b1af029f4857e7b83d1a9ae?r=use1. Tickets will be addressed within two business days. 💡", thread_ts: message.ts});
    } catch (error) {
        console.log("err")
      console.error(error);
    }
});

// Bot will listen for "access"
app.message("access", async ({ message, say }) => {
  try {
    await say({text: "All requests for access for an approved software should be done through Torii. After IT approves access, it will be up to the app owner to grant full permission to access the app.", thread_ts: message.ts});
  } catch (error) {
      console.log("err")
    console.error(error);
  }
});

app.message("NVRAM", async ({ message, say }) => {
  try {
    await say({text: "Looks like you need to reset your NVRAM/SMC! This is handy for when your computer is sluggish, your battery isn't operating as normal, the volume controls aren't working, etc.\n\n Note: This does not apply to MacBooks with the M1 chip. A simple reboot will suffice for M1s.\n\n To check if your device has an M1 chip, click on the Apple logo at the top left of your screen and click on 'About this Mac' and your chip information will be displayed in 'Overview'.\n\n If you do not have an M1 device, please use this link to reset your NVRAM/SMC: https://www.caldigit.com/how-to-reset-nvram-and-smc-on-your-mac/ ", thread_ts: message.ts});
  } catch (error) {
      console.log("err")
    console.error(error);
  }
});

// Bot will listen for "faq" 
app.message("faq", async ({ message, say }) => {
    try {
      await say({text: "Internal IT FAQ\n\n" + 
      "Q. What other commands can I use for this bot?\n. A. You can say things like 'access' for information on requesting software access, 'submit a ticket' to receive the form link, or 'NVRAM' for information on resetting your NVRAM/SMC. You can even say 'Goodbye' or ask 'What's your name'?\n" +
      "Q. When should I submit a ticket to IT?\n A. Please submit a ticket for issues other than password resets or access grants. For example, please submit a ticket for software installation issues or if a new user account needs to be made.\n\n" +
      "Q. When will my ticket be answered?\n A. Tickets will be read and responded to within two business days. Please add one of the urgency emojis to the beginning of your ticket to help us prioritize them.\n\n" +
      "Q. What are the urgency emojis?\n A. Please use the following emojis at the beginning of the subject when submitting a ticket. This helps us prioritize our tickets at a glance and ensures your ticket is addressed in a timely manner.\n" + 
      "❓: general questions, no deadline or time requirement\n" + 
      "❗️: general urgency, needs answered within a day or two of submission\n" + 
      "‼️: immediate urgency, needs answered within an hour or two of submission\n" + 
      "⚠️: heads up, warning, or notice\n\n" +
      "Last updated: 18 February 2022", thread_ts: message.ts});
    } catch (error) {
        console.log("err")
      console.error(error);
    }
});

// Bot listens to say good bye :) 
app.message('goodbye', async ({ message, say }) => {
  // say() sends a message to the channel where the event was triggered
  await say(`See ya later, <@${message.user}> :wave:`);
});

// Bot tells you what his name is
app.message('hat is your name?', async ({ message, say }) => {
  // say() sends a message to the channel where the event was triggered
  await say(`Hi, <@${message.user}>! My name is Tony, but you can call me Ezechiel. :disguised_face:`);
});

(async () => {
  // Start your app
  await app.start();
  console.log(`⚡️ Slack Bolt app is running!`);
})();
srajiang commented 2 years ago

Nice! Thanks for sharing that @ro-internal - I was able to reproduce the behavior that you were seeing.

When a message event that's threaded to a parent comes through from slack, there will be a message.thread_ts value. I was able to get the bot to reply in the same thread and to fix the issue of your message getting sent to the main channel by making sure to supply the message.thread_ts. This code below works for me, let me know whether that works for you.

// Bot will listen for the phrase to show it's awake and listening
app.message("wake up", async ({ message, say }) => {
  try {
      await say({ text: "I'm awake! ⭐️ You can request to view our FAQ or submit a ticket by simplying typing 'faq' or 'submit a ticket' in the main channel." , thread_ts: message.thread_ts || message.ts});
  } catch (error) {
      console.log("err")
      console.error(error);
  }
});

Debugging

One other helpful thing you can turn on is debug logging

const { App, LogLevel } = require('@slack/bolt');
require('dotenv').config();

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  // signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
  logLevel: LogLevel.DEBUG,  // this will give you logs on what's happening with the app and let you see the event payloads that come through!
});
ro-internal commented 2 years ago

@srajiang That worked perfectly, thank you so much! And thank you for the debugging tip, I’ll make sure to add that, that’s a huge help! I’ll go ahead and close this ticket then 💯

dhaval079 commented 2 months ago

Nice! Thanks for sharing that @ro-internal - I was able to reproduce the behavior that you were seeing.

When a message event that's threaded to a parent comes through from slack, there will be a message.thread_ts value. I was able to get the bot to reply in the same thread and to fix the issue of your message getting sent to the main channel by making sure to supply the message.thread_ts. This code below works for me, let me know whether that works for you.

// Bot will listen for the phrase to show it's awake and listening
app.message("wake up", async ({ message, say }) => {
  try {
      await say({ text: "I'm awake! ⭐️ You can request to view our FAQ or submit a ticket by simplying typing 'faq' or 'submit a ticket' in the main channel." , thread_ts: message.thread_ts || message.ts});
  } catch (error) {
      console.log("err")
      console.error(error);
  }
});

Debugging

One other helpful thing you can turn on is debug logging

const { App, LogLevel } = require('@slack/bolt');
require('dotenv').config();

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  // signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
  logLevel: LogLevel.DEBUG,  // this will give you logs on what's happening with the app and let you see the event payloads that come through!
});

I want to do the same but in the form of action, for example i have a form and when i click submit the response should go in the same message as thread

app.action("submit_button", async ({ message, body, ack, client }) => {
  await ack();

  try {
    const userId = body.user.id;
    const channelId = body.channel.id;
    console.log("Channel ID : ", channelId)
    const title = body.state.values.title_block?.title_input.value || "No title provided";
    const attendees = body.state.values.attendees_block?.attendees_input.value || "No attendees provided";
    const keywords = body.state.values.keyword_block?.keyword_input.value || "No attendees provided";

    // Get the time information
    let timeInfo = "";
    if(radio_buttons === "wide_search"){
      timeInfo = timeframe;
    }
    else if(radio_buttons === "precise_search"){
      timeInfo = month + " " + year;
    }
    else{
      console.log("None of the radio button selected");
      timeInfo = "None of the radio button selected";
    }
    await client.chat.postMessage({
      channel: channelId,
      thread_ts : message.ts,
      text: `Thank you <@${userId}>! You entered:
      - Title: ${title}
      - Time: ${timeInfo}
      - Attendees: ${attendees}
      - Keywords : ${keywords}`,
    });
    timeInfo = ""
    timeframe = ""
    month  = ""
    year = ""
  } catch (error) {
    console.error(error);
  }
});

could you help me how to fix this , as i am getting this error TypeError: Cannot read properties of undefined (reading 'ts')

zimeg commented 2 months ago

@dhaval079 That message might not be a valid argument for app.action listener middleware :thinking: The optional body.message.ts could be used instead!

Also happy to explore this more in another issue if that doesn't fix things since it's a slightly unrelated question 🙏