feat(slack): add remove reaction tool (#3414)

* feat(slack): add remove reaction tool

* lint
This commit is contained in:
Waleed
2026-03-04 15:28:41 -08:00
committed by GitHub
parent efc1aeed70
commit 127994f077
7 changed files with 252 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
---
title: Slack
description: Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events
description: Send, update, delete messages, send ephemeral messages, add or remove reactions in Slack or trigger workflows from Slack events
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -39,7 +39,7 @@ If you encounter issues with the Slack integration, contact us at [help@sim.ai](
## Usage Instructions
Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add or remove reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
@@ -799,4 +799,28 @@ Add an emoji reaction to a Slack message
| ↳ `timestamp` | string | Message timestamp |
| ↳ `reaction` | string | Emoji reaction name |
### `slack_remove_reaction`
Remove an emoji reaction from a Slack message
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `authMethod` | string | No | Authentication method: oauth or bot_token |
| `botToken` | string | No | Bot token for Custom Bot |
| `channel` | string | Yes | Channel ID where the message was posted \(e.g., C1234567890\) |
| `timestamp` | string | Yes | Timestamp of the message to remove reaction from \(e.g., 1405894322.002768\) |
| `name` | string | Yes | Name of the emoji reaction to remove \(without colons, e.g., thumbsup, heart, eyes\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Success message |
| `metadata` | object | Reaction metadata |
| ↳ `channel` | string | Channel ID |
| ↳ `timestamp` | string | Message timestamp |
| ↳ `reaction` | string | Emoji reaction name |

View File

@@ -0,0 +1,87 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
export const dynamic = 'force-dynamic'
const SlackRemoveReactionSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
channel: z.string().min(1, 'Channel is required'),
timestamp: z.string().min(1, 'Message timestamp is required'),
name: z.string().min(1, 'Emoji name is required'),
})
export async function POST(request: NextRequest) {
try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
return NextResponse.json(
{
success: false,
error: authResult.error || 'Authentication required',
},
{ status: 401 }
)
}
const body = await request.json()
const validatedData = SlackRemoveReactionSchema.parse(body)
const slackResponse = await fetch('https://slack.com/api/reactions.remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${validatedData.accessToken}`,
},
body: JSON.stringify({
channel: validatedData.channel,
timestamp: validatedData.timestamp,
name: validatedData.name,
}),
})
const data = await slackResponse.json()
if (!data.ok) {
return NextResponse.json(
{
success: false,
error: data.error || 'Failed to remove reaction',
},
{ status: slackResponse.status }
)
}
return NextResponse.json({
success: true,
output: {
content: `Successfully removed :${validatedData.name}: reaction`,
metadata: {
channel: validatedData.channel,
timestamp: validatedData.timestamp,
reaction: validatedData.name,
},
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{
success: false,
error: 'Invalid request data',
details: error.errors,
},
{ status: 400 }
)
}
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
},
{ status: 500 }
)
}
}

View File

@@ -9,10 +9,10 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
type: 'slack',
name: 'Slack',
description:
'Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events',
'Send, update, delete messages, send ephemeral messages, add or remove reactions in Slack or trigger workflows from Slack events',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.',
'Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add or remove reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.',
docsLink: 'https://docs.sim.ai/tools/slack',
category: 'tools',
bgColor: '#611f69',
@@ -38,6 +38,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
{ label: 'Update Message', id: 'update' },
{ label: 'Delete Message', id: 'delete' },
{ label: 'Add Reaction', id: 'react' },
{ label: 'Remove Reaction', id: 'unreact' },
],
value: () => 'send',
},
@@ -608,7 +609,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
placeholder: 'Message timestamp (e.g., 1405894322.002768)',
condition: {
field: 'operation',
value: 'react',
value: ['react', 'unreact'],
},
required: true,
},
@@ -619,7 +620,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
placeholder: 'Emoji name without colons (e.g., thumbsup, heart, eyes)',
condition: {
field: 'operation',
value: 'react',
value: ['react', 'unreact'],
},
required: true,
},
@@ -641,6 +642,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
'slack_update_message',
'slack_delete_message',
'slack_add_reaction',
'slack_remove_reaction',
],
config: {
tool: (params) => {
@@ -673,6 +675,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
return 'slack_delete_message'
case 'react':
return 'slack_add_reaction'
case 'unreact':
return 'slack_remove_reaction'
default:
throw new Error(`Invalid Slack operation: ${params.operation}`)
}
@@ -841,6 +845,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
break
case 'react':
case 'unreact':
baseParams.timestamp = reactionTimestamp
baseParams.name = emojiName
break

View File

@@ -1808,6 +1808,7 @@ import {
slackListUsersTool,
slackMessageReaderTool,
slackMessageTool,
slackRemoveReactionTool,
slackUpdateMessageTool,
} from '@/tools/slack'
import { smsSendTool } from '@/tools/sms'
@@ -2611,6 +2612,7 @@ export const tools: Record<string, ToolConfig> = {
slack_update_message: slackUpdateMessageTool,
slack_delete_message: slackDeleteMessageTool,
slack_add_reaction: slackAddReactionTool,
slack_remove_reaction: slackRemoveReactionTool,
github_repo_info: githubRepoInfoTool,
github_repo_info_v2: githubRepoInfoV2Tool,
github_latest_commit: githubLatestCommitTool,

View File

@@ -11,6 +11,7 @@ import { slackListMembersTool } from '@/tools/slack/list_members'
import { slackListUsersTool } from '@/tools/slack/list_users'
import { slackMessageTool } from '@/tools/slack/message'
import { slackMessageReaderTool } from '@/tools/slack/message_reader'
import { slackRemoveReactionTool } from '@/tools/slack/remove_reaction'
import { slackUpdateMessageTool } from '@/tools/slack/update_message'
export {
@@ -22,6 +23,7 @@ export {
slackUpdateMessageTool,
slackDeleteMessageTool,
slackAddReactionTool,
slackRemoveReactionTool,
slackListChannelsTool,
slackListMembersTool,
slackListUsersTool,

View File

@@ -0,0 +1,108 @@
import type { SlackRemoveReactionParams, SlackRemoveReactionResponse } from '@/tools/slack/types'
import { REACTION_METADATA_OUTPUT_PROPERTIES } from '@/tools/slack/types'
import type { ToolConfig } from '@/tools/types'
export const slackRemoveReactionTool: ToolConfig<
SlackRemoveReactionParams,
SlackRemoveReactionResponse
> = {
id: 'slack_remove_reaction',
name: 'Slack Remove Reaction',
description: 'Remove an emoji reaction from a Slack message',
version: '1.0.0',
oauth: {
required: true,
provider: 'slack',
},
params: {
authMethod: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Authentication method: oauth or bot_token',
},
botToken: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Bot token for Custom Bot',
},
accessToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'OAuth access token or bot token for Slack API',
},
channel: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Channel ID where the message was posted (e.g., C1234567890)',
},
timestamp: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Timestamp of the message to remove reaction from (e.g., 1405894322.002768)',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Name of the emoji reaction to remove (without colons, e.g., thumbsup, heart, eyes)',
},
},
request: {
url: '/api/tools/slack/remove-reaction',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: SlackRemoveReactionParams) => ({
accessToken: params.accessToken || params.botToken,
channel: params.channel,
timestamp: params.timestamp,
name: params.name,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
return {
success: false,
output: {
content: data.error || 'Failed to remove reaction',
metadata: {
channel: '',
timestamp: '',
reaction: '',
},
},
error: data.error,
}
}
return {
success: true,
output: {
content: data.output.content,
metadata: data.output.metadata,
},
}
},
outputs: {
content: { type: 'string', description: 'Success message' },
metadata: {
type: 'object',
description: 'Reaction metadata',
properties: REACTION_METADATA_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -561,6 +561,12 @@ export interface SlackAddReactionParams extends SlackBaseParams {
name: string
}
export interface SlackRemoveReactionParams extends SlackBaseParams {
channel: string
timestamp: string
name: string
}
export interface SlackListChannelsParams extends SlackBaseParams {
includePrivate?: boolean
excludeArchived?: boolean
@@ -759,6 +765,17 @@ export interface SlackAddReactionResponse extends ToolResponse {
}
}
export interface SlackRemoveReactionResponse extends ToolResponse {
output: {
content: string
metadata: {
channel: string
timestamp: string
reaction: string
}
}
}
export interface SlackChannel {
id: string
name: string
@@ -866,6 +883,7 @@ export type SlackResponse =
| SlackUpdateMessageResponse
| SlackDeleteMessageResponse
| SlackAddReactionResponse
| SlackRemoveReactionResponse
| SlackListChannelsResponse
| SlackListMembersResponse
| SlackListUsersResponse