Describe your issue here.
x in one of the [ ])x in each of the [ ])Suggestion for search.message documentation:
Using search.message to search for a message posted by a bot requires the original post be posted as_user: true. Requiring as_user: true may not be required if username for bot is included in the original post but I have not tried it.
@slack/client version: 4.8.0
node version: 10.13.0
OS version(s): OSX 10.14
I'm not sure the suggestion is entirely accurate.
Are you saying that when a message is sent by a bot (using chat.postMessage, with a bot token (xoxb), as_user as the default false), that you cannot find that message when calling search.messages (with a user token (xoxp) whose user can access the channel where the message was sent)?
All of a sudden last Friday afternoon (EST) our bot(app) was unable to update any post that it was the author of. We believe we tracked the problem down to the search.message results which were no longer able to find any message posted by itself. So we changed the post object to include as_user: true. But, now the bot is posting as me, the person who installed and authorized the bot.
And, yes, the bot(app) using the same token to post was unable to find and update a message where as_user was set to false for the original post.
And, yes, the bot(app) using the same token to post was unable to find and update a message where as_user was set to false for the original post.
I'm not sure if there was a recent change that correlates to this (but I will ask around internally), but search.messages is not supposed to work with a bot token, only user tokens (see: https://api.slack.com/methods/search.messages). So if you were using the same token to post, search, and update, that shouldn't have worked. Can you double-check and confirm? Maybe I'm misunderstanding.
Thank you.
To confirm, we have been using the same OAuth xoxp token generated by our Alert Reporter App to post a message, search for message, and update message for over a year and it was working until last Friday, 2/1/19.
How we use the App:
We have a program that will post an alert to a channel with a unique ID. A user clicks on the link in the post. The user evaluates the alert and selects a status for the alert. The program then searches for unique ID and updates the original post with the selected status. All this was being done by one App and all function calls were happening with one OAuth xoxp token.
What Happened:
It appears that on Friday afternoon the program was unable to find the alert with the unique ID on slack that it had previously found and was returning every post on slack (all 7000+) except posts the App had posted.
To confirm, we have been using the same OAuth xoxp token generated by our Alert Reporter App to post a message, search for message, and update message for over a year and it was working until last Friday, 2/1/19.
Okay this makes more sense now. Not your fault because it is confusing, but this is a user token not a bot token.
My theory is that the authenticated user for the xoxp token that authorized Alert Reporter App either left the team or otherwise could not access the channel where these messages were being posted, and that could be a cause for this behavior.
For background: the xoxp token's "power" is limited to those of the user who authorized the installation of the app into your workspace. This isn't immediately intuitive for many developers, but it is a useful security guarantee for users and admins.
But, now the bot is posting as me, the person who installed and authorized the bot.
The problem with this theory is that you said that you're the authorizing user, and it doesn't sound like you've left the team.
It appears that on Friday afternoon the program was unable to find the alert with the unique ID on slack that it had previously found and was returning every post on slack (all 7000+) except posts the App had posted.
~If my theory is correct, then I'm still not sure I understand this effect. Even if the authorizing user left the team, I don't think messages created by them should become unsearchable.~ Edit (2): This doesn't completely make sense. The search.messages call itself would be made with a user token that cannot access the previous messages (if the user left the team or no longer has access to this channel), but then the search results shouldn't work at all or exclude all messages from that channel (as opposed to just the ones posted by the app).
I'm going to keep investigating and gathering information, I just wanted to share what my current thoughts are so that you might be able to fill in any blanks or misconceptions I'm having.
Thank you.
I have narrowed our issue down. The xoxp token posts, searches, updates as it has been. But, in order to have a successful search, we must put the search in a for-loop and have each search followed by a setTimeout() of 2-4 secs in order to find the alert. Also, if I include 'count', 'score', 'sort_dir' the search results in over 7000+ results. If those are left out of the search obj then it finds the correct post but only after 2-4 loops.
Edit: What had been happening is in search.messages we had 'count', 'score', 'sort_dir' set in search query and it worked fine until Friday Feb 1. After Feb 1 it started resulting in 7000+ msg returns. This would prevent our updateMsg function from working and thus the program stopped. Removing 'count', 'score', 'sort_dir' from query and inserting a setTimeout between in our for-loop seemed to help.
I'm glad you figured it out! Can I ask to know a little more about what was going wrong, and how this setTimeout() based delay works?
It sounds like you were doing something like the following:
function messageSearch(alertIdentifier) {
return slack.search.messages({ query: alertIdentifier, count: 100, sort: 'score', sort_dir: 'asc' })
.then((result) => {
for (match in result.matches) {
if (match.text.contains(alertIdentifier)) {
return match.ts;
}
}
// No match found, wait and try again
return new Promise((resolve) => { setTimeout(resolve, 3000); })
.then(() => messageSearch(alertIdentifier));
});
}
// Example:
messageSearch('abcdef-12345')
.then((ts) => updateMsg(ts, someNewContent));
Does that look like what you had? If so, I don't see how just waiting and trying again would work. I'm also not sure why you'd get 7000+ results, if you're pretty sure the alert identifier is only used once (or a few times).
I think you should be using the pagination controls in the search.messages method. I'd like to help create a "recipe" for you and others to use, and share it on our documentation website.
This is how I'd do it, given what you've told me, but let me know if this would help:
// Given some initialized WebClient
const web = new WebClient(process.env.SLACK_TOKEN);
/**
* Paginated search: finds messages containing a query until the predicate
* says the search is done.
* @param {string} query - the term to search for
* @param {Function} predicate - a function that is called with each result and expected to return true when searching should end.
* @param {object} options - other options to send to the `search.messages` method
* @returns {Promise} - resolves when the search is complete.
*/
function paginatedSeachMessages(query, predicate, options) {
function recursiveSearch(searchArgs) {
return web.search.messages(searchArgs)
.then((result) => {
for (match of results.messages.matches) {
if (predicate(match) === true) {
return;
}
}
if (result.messages.paging.page !== result.messages.paging.pages) {
return recursiveSearch(Object.assign({}, searchArgs, { page: result.messages.paging.page + 1 }));
}
});
}
return recursiveSearch(Object.assign({ count: 100 }, options, { query }));
}
// Using the paginatedSeachMessages to find the message ts for all the messages
// that have an alert identifier mentioned in them
const alertId = 'ALERT-12345';
const messageIds = [];
paginatedSeachMessages(alertId, (message) => {
// Make sure this message mentions the the alert as the *first* part of the text
// This is just an example of how you might want to do it.
// You might also want to make sure the message was sent by this app.
if (message.text.indexOf(alertId) === 0) {
messageIds.push({ ts: message.ts, channel: message.channel.id });
}
// We only care to update the last 10 mentions of the alertId, so stop if we have that many
return messageIds.length >= 10;
}).then(() => {
for (messageId of messageIds) {
// Given that you have some newMessageText for this alert.
web.chat.update(Object.assign({ text: newMessageText }, messageId));
}
});
Thanks. I will attempt adapting your recommended example above. In the meantime, the function we use to search is below. The Alert ID we search for is unique so we do not iterate over results from the search.message method as there is only one message that should contain that Alert ID.
Up until 2/1/19 this function would find the unique Alert ID searched for. Now, the result from the search finds nearly all messages (thus the 7000+ number of results). If I drop "count, sort, sort_dir" from the query object it _sometimes_ finds the Alert ID message but its hit or miss. I have a test that runs the function and maybe it finds the Alert ID message 2 out of every 5 times. very strange.
For now, we are planning to just rewriting our function that posts the alert to store the chat.postmessage return obj ts and channel ID in the database table with the alert's data and then call the ts and channel id from the db to use in chat.update instead of doing a search.
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function searchSlackMsgs(alertId: number): Promise<any> {
const queryString: string = `Alert ID: ${alertId}`;
const client: WebClient = new WebClient(SLACK_TOKEN_ALERT_REPORTER);
const resObj: IMessageText = {
ts: '',
text: '',
channelId: '',
};
const searchQuery: any = {
query: queryString,
count: 1,
sort: 'score',
sort_dir: 'dsc',
};
for (let i: number = 0; i < 5; i += 1) {
const searchResult: any = await client.search.messages(searchQuery);
if (searchResult && searchResult.messages.total > 0) {
const msg: any = searchResult.messages.matches[0];
resObj.ts = msg.ts;
resObj.text = msg.text; // refreshUrl(msg.text);
resObj.channelId = msg.channel.id;
return resObj;
}
// console.log("** Pause 2 sec **")
await sleep(2000);
}
return false;
}
interface IMessageText {
ts: string;
text: string;
channelId: string;
}
i think storing the ts and channel is a superior solution too!