mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(trigger): handle Slack reaction_added/reaction_removed event payloads (#3280)
* fix(trigger): handle Slack reaction_added/reaction_removed event payloads * fix(trigger): use oldest param for conversations.history consistency * fix oldest param * fix(trigger): use reactions.get API to fetch message text for thread replies
This commit is contained in:
@@ -679,6 +679,55 @@ async function downloadSlackFiles(
|
||||
return downloaded
|
||||
}
|
||||
|
||||
const SLACK_REACTION_EVENTS = new Set(['reaction_added', 'reaction_removed'])
|
||||
|
||||
/**
|
||||
* Fetches the text of a reacted-to message from Slack using the reactions.get API.
|
||||
* Unlike conversations.history, reactions.get works for both top-level messages and
|
||||
* thread replies, since it looks up the item directly by channel + timestamp.
|
||||
* Requires the bot token to have the reactions:read scope.
|
||||
*/
|
||||
async function fetchSlackMessageText(
|
||||
channel: string,
|
||||
messageTs: string,
|
||||
botToken: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
channel,
|
||||
timestamp: messageTs,
|
||||
})
|
||||
const response = await fetch(`https://slack.com/api/reactions.get?${params}`, {
|
||||
headers: { Authorization: `Bearer ${botToken}` },
|
||||
})
|
||||
|
||||
const data = (await response.json()) as {
|
||||
ok: boolean
|
||||
error?: string
|
||||
type?: string
|
||||
message?: { text?: string }
|
||||
}
|
||||
|
||||
if (!data.ok) {
|
||||
logger.warn('Slack reactions.get failed — message text unavailable', {
|
||||
channel,
|
||||
messageTs,
|
||||
error: data.error,
|
||||
})
|
||||
return ''
|
||||
}
|
||||
|
||||
return data.message?.text ?? ''
|
||||
} catch (error) {
|
||||
logger.warn('Error fetching Slack message text', {
|
||||
channel,
|
||||
messageTs,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format webhook input based on provider
|
||||
*/
|
||||
@@ -953,6 +1002,23 @@ export async function formatWebhookInput(
|
||||
})
|
||||
}
|
||||
|
||||
const eventType: string = rawEvent?.type || body?.type || 'unknown'
|
||||
const isReactionEvent = SLACK_REACTION_EVENTS.has(eventType)
|
||||
|
||||
// Reaction events nest channel/ts inside event.item
|
||||
const channel: string = isReactionEvent
|
||||
? rawEvent?.item?.channel || ''
|
||||
: rawEvent?.channel || ''
|
||||
const messageTs: string = isReactionEvent
|
||||
? rawEvent?.item?.ts || ''
|
||||
: rawEvent?.ts || rawEvent?.event_ts || ''
|
||||
|
||||
// For reaction events, attempt to fetch the original message text
|
||||
let text: string = rawEvent?.text || ''
|
||||
if (isReactionEvent && channel && messageTs && botToken) {
|
||||
text = await fetchSlackMessageText(channel, messageTs, botToken)
|
||||
}
|
||||
|
||||
const rawFiles: any[] = rawEvent?.files ?? []
|
||||
const hasFiles = rawFiles.length > 0
|
||||
|
||||
@@ -965,16 +1031,18 @@ export async function formatWebhookInput(
|
||||
|
||||
return {
|
||||
event: {
|
||||
event_type: rawEvent?.type || body?.type || 'unknown',
|
||||
channel: rawEvent?.channel || '',
|
||||
event_type: eventType,
|
||||
channel,
|
||||
channel_name: '',
|
||||
user: rawEvent?.user || '',
|
||||
user_name: '',
|
||||
text: rawEvent?.text || '',
|
||||
timestamp: rawEvent?.ts || rawEvent?.event_ts || '',
|
||||
text,
|
||||
timestamp: messageTs,
|
||||
thread_ts: rawEvent?.thread_ts || '',
|
||||
team_id: body?.team_id || rawEvent?.team || '',
|
||||
event_id: body?.event_id || '',
|
||||
reaction: rawEvent?.reaction || '',
|
||||
item_user: rawEvent?.item_user || '',
|
||||
hasFiles,
|
||||
files,
|
||||
},
|
||||
|
||||
@@ -67,8 +67,8 @@ export const slackWebhookTrigger: TriggerConfig = {
|
||||
'Go to <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer" class="text-muted-foreground underline transition-colors hover:text-muted-foreground/80">Slack Apps page</a>',
|
||||
'If you don\'t have an app:<br><ul class="mt-1 ml-5 list-disc"><li>Create an app from scratch</li><li>Give it a name and select your workspace</li></ul>',
|
||||
'Go to "Basic Information", find the "Signing Secret", and paste it in the field above.',
|
||||
'Go to "OAuth & Permissions" and add bot token scopes:<br><ul class="mt-1 ml-5 list-disc"><li><code>app_mentions:read</code> - For viewing messages that tag your bot with an @</li><li><code>chat:write</code> - To send messages to channels your bot is a part of</li><li><code>files:read</code> - To access files and images shared in messages</li></ul>',
|
||||
'Go to "Event Subscriptions":<br><ul class="mt-1 ml-5 list-disc"><li>Enable events</li><li>Under "Subscribe to Bot Events", add <code>app_mention</code> to listen to messages that mention your bot</li><li>Paste the Webhook URL above into the "Request URL" field</li></ul>',
|
||||
'Go to "OAuth & Permissions" and add bot token scopes:<br><ul class="mt-1 ml-5 list-disc"><li><code>app_mentions:read</code> - For viewing messages that tag your bot with an @</li><li><code>chat:write</code> - To send messages to channels your bot is a part of</li><li><code>files:read</code> - To access files and images shared in messages</li><li><code>reactions:read</code> - For listening to emoji reactions and fetching reacted-to message text</li></ul>',
|
||||
'Go to "Event Subscriptions":<br><ul class="mt-1 ml-5 list-disc"><li>Enable events</li><li>Under "Subscribe to Bot Events", add <code>app_mention</code> to listen to messages that mention your bot</li><li>For reaction events, also add <code>reaction_added</code> and/or <code>reaction_removed</code></li><li>Paste the Webhook URL above into the "Request URL" field</li></ul>',
|
||||
'Go to "Install App" in the left sidebar and install the app into your desired Slack workspace and channel.',
|
||||
'Copy the "Bot User OAuth Token" (starts with <code>xoxb-</code>) and paste it in the Bot Token field above to enable file downloads.',
|
||||
'Save changes in both Slack and here.',
|
||||
@@ -128,6 +128,16 @@ export const slackWebhookTrigger: TriggerConfig = {
|
||||
type: 'string',
|
||||
description: 'Unique event identifier',
|
||||
},
|
||||
reaction: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Emoji reaction name (e.g., thumbsup). Present for reaction_added/reaction_removed events',
|
||||
},
|
||||
item_user: {
|
||||
type: 'string',
|
||||
description:
|
||||
'User ID of the original message author. Present for reaction_added/reaction_removed events',
|
||||
},
|
||||
hasFiles: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the message has file attachments',
|
||||
|
||||
Reference in New Issue
Block a user