I am building a Slack app using the JavaScript Bolt framework. The concept of the app is just listening to specific message keywords in channels and then forwarding those messages to the users of the app.
What I am trying to achieve is including a permalink in the forwarded message. I am trying to use the chat.getPermalink
method to get the url and then include that in my chat.postMessage
method. I am trying to leverage Bolt's 'Context' in order to pass the property in chat.getPermalink to chat.postMessage. I am asking for help here because I cannot get the Context to work..
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
});
let token = process.env.SLACK_BOT_TOKEN,
web = new WebClient(token);
let jira_text = "jira";
let rdu_qa = '@rdu_qa';
//Get permalink
async function PermaLinks({payload, context, next}) {
let perm = app.client.chat.getPermalink({
token: context.botToken,
channel: "C0109KMQCFQ",
message_ts: payload.ts
});
context.permalink = perm.permalink;
await next();
}
app.event('message', PermaLinks, async ({ payload, message, context}) => {
let userzArray = ["D010Q34TQL9", "UVBBD8989"];
//if channel is general and incldues the text 'Jira' or 'rdu_qa'
if (payload.channel === "C0109KMQCFQ") {
if (payload.text.includes(jira_text) || payload.text.includes(rdu_qa)) {
try {
// Call the chat.postMessage to each of the users
let oneUser = await userzArray.forEach(userId => { app.client.chat.postMessage({
token: context.botToken,
bot_id: "USLACKBOT",
channel: userId,
blocks: [
{
type: "section",
text: {
text: payload.text,
type: "mrkdwn"
},
fields: [
{
type: "mrkdwn",
text: `posted by <@${message.user}>`
},
{
type:"mrkdwn",
text: "in General channel" //channel.name//getChannelNameGeneral
},
{
type:"mrkdwn",
text: context.permalink // Permalink should be right here
}
]
},
{
"type": "divider"
},
] // End of block of Jira notification stuff
});
});
// console.log(result);
} catch (error) {
console.error(error);
}
} // If text sent to General channel includes keyword 'Jira' or 'rdu_qa'
} //end of if message was posted in General channel
There are a couple problems I can see in the example code, but I think the main issue regarding the context
is that you're storing a Promise as context.permalink
, not the actual result of the method call. In order to store the result, you should use the await
keyword before calling the method (app.client.chat.getPermalink(...)
).
I've revised the code you shared here, and I'll explain the modifications below.
const { App } = require('@slack/bolt');
const token = process.env.SLACK_BOT_TOKEN
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
token,
});
// Users who should be notified when certain messages are heard
let userzArray = ["D010Q34TQL9", "UVBBD8989"];
// Conversation IDs corresponding to the users in the array above. This variable will be set automatically when the app starts.
let conversationsToNotify;
// Match messages that include the text 'jira' or '@rdu_qa'
app.message(/jira|@rdu_qa/, async ({ message, client }) => {
// Match the messages that are in the specified channel
if (message.channel === 'C0109KMQCFQ') {
try {
// Get a permalink to this message
const permalinkResult = await client.chat.getPermalink({
channel: message.channel,
message_ts: message.ts,
});
// Send a message to each user containing the permalink for this message
await Promise.all(conversationsToNotify.map((conversationId) => {
return client.chat.postMessage({
channel: conversationId,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `>>> ${payload.text}`,
},
fields: [
{
type: 'mrkdwn',
text: `posted by <@${message.user}>`,
},
{
type: 'mrkdwn',
text: `in <#${message.channel}>`,
},
{
type:'mrkdwn',
text: `<Original|${permalinkResult.permalink}>`,
},
],
},
{
type: 'divider'
},
],
});
}));
} catch (error) {
console.error(error);
}
}
});
async function convertUsersToConversations(input) {
return Promise.all(input.map((id) => {
// For all IDs that seem like user IDs, convert them to a DM conversation ID
if (id.startsWith('U')) {
return app.client.conversations.open({
token,
users: id,
})
.then((result) => result.channel.id);
}
// For all IDs that don't seem to belong to a user, return them as is
return id;
}));
});
(async () => {
// Start the app
conversationsToNotify = await convertUsersToConversations(userzArray);
await app.start(process.env.PORT || 3000);
console.log('⚡️ Bolt app is running!');
})();
WebClient
object. In Bolt v1.6.0 and later, there is a client
argument available in listeners and middleware which you can use to call Web API methods instead. The advantage of using the client
argument is that you don't need to read the token from the context and pass it as an argument for each method call on your own, Bolt will find the right token for you.app.event('message', ...)
method to listen for message events, I've changed to using app.message(...)
. The latter works mostly the same, but has one more advantage: you can pass a pattern to match the text of a message as the first argument (before the listener function): app.message(pattern, ...)
. That helps remove some of the conditions inside the listener. Instead of using just the two string variables jira_text
and @rdu_qa
, I've combined them in a single regular expression that matches when either of those values is seen in the text: /jira|@rdu_qa/
.Promise.all()
to collect the Promises of each call to chat.postMessage
into one promise. Currently, you're using userzArray.forEach(...)
, which doesn't return anything. So then using await
on that value will immediately resolve, and doesn't really do anything useful. What we need to do is collect each of the Promises and wait for them to all complete. This is what Promise.all()
does. We just need an array of Promises to pass in, which we can get by simply changing userzArray.forEach()
to userzArray.map()
.
chat.postMessage
. You're trying to use Slackbot to send those messages, but that's not recommended because users are less likely to understand where that message is coming from. Instead, you should send this message as a DM from your bot user. In order to do that, you need a conversation ID, not a user ID, for each user you want to send this notification to. One of the items in userzArray
is already a DM conversation ID (it starts with a D
), but the other is not. In order to make this work consistently, I've created the conversationsToNotify
array which contains the conversation IDs for each user after calling conversations.open
to create a DM. Now in the code, you'll see conversationsToNotify.map()
instead of userzArray.map()
. Your Slack app will now need the im:write
and chat:write
permission scopes (don't forget to reinstall once you add scopes). Looking up the conversation IDs will slow down your app from starting up if the number of users in the array gets larger. My recommendation would be to save the conversation IDs in your code (or in a database) instead of the user IDs. This will ensure a consistently fast start up time for your app.chat.postMessage
fails? The way I've written the code above, the error would be logged to the console, but later if the second call fails, there's no way to know. That's because Promise.all()
returns a promise that will reject as soon as any one of the promises rejects, and then ignores what happens afterwards. If you're using Node v12.9.0 or greater, I would recommend using Promise.allSettled()
instead (which would require a few changes in your catch
clause as well).message
argument in the listener everywhere instead of payload
argument. These are actually the same value when dealing with message
events. payload
is mostly only useful in middleware that handle several kinds of events (action, event, view, shortcut, etc) so that there's one way to refer to all of their payloads.userzArray
outside the listener, and make it a constant. There's no point in redeclaring it inside the listener each time it runs, and it doesn't change.convertUsersToConversations
). This function is called before the app is started to avoid a race condition where the an incoming message is handled before the app knows which channels to notify.