From 3a01be4940d2fe9fc4fec26cc02f60ae82f71b61 Mon Sep 17 00:00:00 2001 From: meenulekha-premakumar Date: Fri, 12 Sep 2025 17:36:40 +0200 Subject: [PATCH 1/5] feat: add message contains keyword trigger --- packages/pieces/community/slack/src/index.ts | 2 + .../lib/triggers/message-contains-keyword.ts | 103 ++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts diff --git a/packages/pieces/community/slack/src/index.ts b/packages/pieces/community/slack/src/index.ts index 8302cc33a36..3e9c43c4d57 100644 --- a/packages/pieces/community/slack/src/index.ts +++ b/packages/pieces/community/slack/src/index.ts @@ -46,6 +46,7 @@ import { findUserByIdAction } from './lib/actions/find-user-by-id'; import { newUserTrigger } from './lib/triggers/new-user'; import { newSavedMessageTrigger } from './lib/triggers/new-saved-message'; import { newTeamCustomEmojiTrigger } from './lib/triggers/new-team-custom-emoji'; +import { messageContainsKeywordTrigger } from './lib/triggers/message-contains-keyword'; import { inviteUserToChannelAction } from './lib/actions/invite-user-to-channel'; import { listUsers } from './lib/actions/list-users'; import { deleteMessageAction } from './lib/actions/delete-message'; @@ -236,6 +237,7 @@ export const slack = createPiece({ newUserTrigger, newSavedMessageTrigger, newTeamCustomEmojiTrigger, + messageContainsKeywordTrigger, ], }); diff --git a/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts b/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts new file mode 100644 index 00000000000..01d1bf819d5 --- /dev/null +++ b/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts @@ -0,0 +1,103 @@ +import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; +import { slackAuth } from '../../'; + +export const messageContainsKeywordTrigger = createTrigger({ + auth: slackAuth, + name: 'message-contains-keyword', + displayName: 'Message Contains Keyword', + description: 'Triggers when a message containing a specific keyword is posted to any channel.', + props: { + keyword: Property.ShortText({ + displayName: 'Keyword', + description: 'The keyword to search for in messages. Case-insensitive.', + required: true, + }), + matchWholeWord: Property.Checkbox({ + displayName: 'Match Whole Word Only', + description: 'If enabled, only matches the keyword as a complete word (e.g. "test" won\'t match "testing")', + required: false, + defaultValue: false, + }), + ignoreBots: Property.Checkbox({ + displayName: 'Ignore Bot Messages', + description: 'If enabled, messages from bots will be ignored', + required: false, + defaultValue: true, + }), + includeThreads: Property.Checkbox({ + displayName: 'Include Thread Messages', + description: 'If enabled, also triggers on keyword matches in thread replies', + required: false, + defaultValue: true, + }), + }, + type: TriggerStrategy.APP_WEBHOOK, + sampleData: { + text: 'This is a sample message with the keyword', + channel: 'C123', + user: 'U456', + channel_type: 'channel' + }, + onEnable: async (context) => { + // Older OAuth2 has team_id, newer has team.id + const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; + context.app.createListeners({ + events: ['message'], + identifierValue: teamId, + }); + }, + onDisable: async (context) => { + // Ignored + }, + + run: async (context) => { + const payloadBody = context.payload.body as PayloadBody; + const { keyword, matchWholeWord, ignoreBots, includeThreads } = context.propsValue; + + if (!payloadBody.event.text) { + return []; + } + + if (ignoreBots && payloadBody.event.bot_id) { + return []; + } + + if (!['channel', 'group'].includes(payloadBody.event.channel_type)) { + if (!includeThreads || !payloadBody.event.thread_ts) { + return []; + } + } + + // Check if the message contains the keyword + const messageText = payloadBody.event.text.toLowerCase(); + const searchKeyword = keyword.toLowerCase(); + + let keywordFound = false; + + if (matchWholeWord) { + // Match whole word only + const wordRegex = new RegExp(`\\b${searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i'); + keywordFound = wordRegex.test(payloadBody.event.text); + } else { + // else do a simple substring match + keywordFound = messageText.includes(searchKeyword); + } + + if (keywordFound) { + return [payloadBody.event]; + } + + return []; + }, +}); + +type PayloadBody = { + event: { + text: string; + channel: string; + user: string; + channel_type: string; + bot_id?: string; + thread_ts?: string; + }; +}; From c6c367985f8e1190636d4b4dfb4a8bf70e58fb37 Mon Sep 17 00:00:00 2001 From: meenulekha-premakumar Date: Wed, 24 Sep 2025 11:28:59 +0200 Subject: [PATCH 2/5] fix: bump slack package version --- packages/pieces/community/slack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pieces/community/slack/package.json b/packages/pieces/community/slack/package.json index d0081e7c7b8..f49536488f9 100644 --- a/packages/pieces/community/slack/package.json +++ b/packages/pieces/community/slack/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-slack", - "version": "0.11.4", + "version": "0.11.5", "dependencies": { "@slack/web-api": "7.9.0", "slackify-markdown": "4.4.0" From b08a8458da2a2272fedb7df65e15e7af413a88d6 Mon Sep 17 00:00:00 2001 From: meenulekha-premakumar Date: Thu, 16 Oct 2025 17:32:00 +0200 Subject: [PATCH 3/5] refactor: add keyword filtering to slack new-message trigger --- .../lib/triggers/message-contains-keyword.ts | 103 ------------------ .../slack/src/lib/triggers/new-message.ts | 69 +++++++++++- 2 files changed, 63 insertions(+), 109 deletions(-) delete mode 100644 packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts diff --git a/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts b/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts deleted file mode 100644 index 01d1bf819d5..00000000000 --- a/packages/pieces/community/slack/src/lib/triggers/message-contains-keyword.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework'; -import { slackAuth } from '../../'; - -export const messageContainsKeywordTrigger = createTrigger({ - auth: slackAuth, - name: 'message-contains-keyword', - displayName: 'Message Contains Keyword', - description: 'Triggers when a message containing a specific keyword is posted to any channel.', - props: { - keyword: Property.ShortText({ - displayName: 'Keyword', - description: 'The keyword to search for in messages. Case-insensitive.', - required: true, - }), - matchWholeWord: Property.Checkbox({ - displayName: 'Match Whole Word Only', - description: 'If enabled, only matches the keyword as a complete word (e.g. "test" won\'t match "testing")', - required: false, - defaultValue: false, - }), - ignoreBots: Property.Checkbox({ - displayName: 'Ignore Bot Messages', - description: 'If enabled, messages from bots will be ignored', - required: false, - defaultValue: true, - }), - includeThreads: Property.Checkbox({ - displayName: 'Include Thread Messages', - description: 'If enabled, also triggers on keyword matches in thread replies', - required: false, - defaultValue: true, - }), - }, - type: TriggerStrategy.APP_WEBHOOK, - sampleData: { - text: 'This is a sample message with the keyword', - channel: 'C123', - user: 'U456', - channel_type: 'channel' - }, - onEnable: async (context) => { - // Older OAuth2 has team_id, newer has team.id - const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; - context.app.createListeners({ - events: ['message'], - identifierValue: teamId, - }); - }, - onDisable: async (context) => { - // Ignored - }, - - run: async (context) => { - const payloadBody = context.payload.body as PayloadBody; - const { keyword, matchWholeWord, ignoreBots, includeThreads } = context.propsValue; - - if (!payloadBody.event.text) { - return []; - } - - if (ignoreBots && payloadBody.event.bot_id) { - return []; - } - - if (!['channel', 'group'].includes(payloadBody.event.channel_type)) { - if (!includeThreads || !payloadBody.event.thread_ts) { - return []; - } - } - - // Check if the message contains the keyword - const messageText = payloadBody.event.text.toLowerCase(); - const searchKeyword = keyword.toLowerCase(); - - let keywordFound = false; - - if (matchWholeWord) { - // Match whole word only - const wordRegex = new RegExp(`\\b${searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i'); - keywordFound = wordRegex.test(payloadBody.event.text); - } else { - // else do a simple substring match - keywordFound = messageText.includes(searchKeyword); - } - - if (keywordFound) { - return [payloadBody.event]; - } - - return []; - }, -}); - -type PayloadBody = { - event: { - text: string; - channel: string; - user: string; - channel_type: string; - bot_id?: string; - thread_ts?: string; - }; -}; diff --git a/packages/pieces/community/slack/src/lib/triggers/new-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-message.ts index 5b92ba83dd8..c658b12de4b 100644 --- a/packages/pieces/community/slack/src/lib/triggers/new-message.ts +++ b/packages/pieces/community/slack/src/lib/triggers/new-message.ts @@ -9,12 +9,36 @@ export const newMessageTrigger = createTrigger({ props: { ignoreBots: Property.Checkbox({ displayName: 'Ignore Bot Messages ?', - required: true, + required: false, + defaultValue: false, + }), + keyword: Property.ShortText({ + displayName: 'Keyword', + description: 'The keyword to search for in messages. Leave empty to trigger on all messages.', + required: false, + }), + includeThreads: Property.Checkbox({ + displayName: 'Include Thread Messages', + description: 'If enabled, also triggers on matches in thread replies', + required: false, + defaultValue: true, + }), + matchWholeWord: Property.Checkbox({ + displayName: 'Match Whole Word Only', + description: 'If enabled, only matches the keyword as a complete word (e.g. "test" won\'t match "testing"). Leave empty if no keyword.', + required: false, defaultValue: false, }), }, + type: TriggerStrategy.APP_WEBHOOK, - sampleData: undefined, + sampleData: { + text: 'Hello world!', + channel: 'C1234567890', + user: 'U1234567890', + channel_type: 'channel', + ts: '1234567890.123456' + }, onEnable: async (context) => { // Older OAuth2 has team_id, newer has team.id const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; @@ -29,24 +53,57 @@ export const newMessageTrigger = createTrigger({ run: async (context) => { const payloadBody = context.payload.body as PayloadBody; + const {ignoreBots, keyword, includeThreads, matchWholeWord} = context.propsValue; + if (ignoreBots && payloadBody.event.bot_id) { + return []; + } // check if it's channel message if (!['channel','group'].includes(payloadBody.event.channel_type)) { + if (!includeThreads || !payloadBody.event.thread_ts) { + return []; + } + } + + // if text is not provided, return + if (!payloadBody.event.text || !payloadBody.event.text.trim()) { return []; } - // check for bot messages - if (context.propsValue.ignoreBots && payloadBody.event.bot_id) { - return []; + // if keyword is provided, check if the message contains the keyword + if (keyword && keyword.trim()) { + if (!payloadBody.event.text) { + return []; + } + + const messageText = payloadBody.event.text.toLowerCase(); + const searchKeyword = keyword.toLowerCase(); + let keywordFound = false; + + if (matchWholeWord) { + const wordRegex = new RegExp(`\\b${searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i'); + keywordFound = wordRegex.test(payloadBody.event.text); + } else { + keywordFound = messageText.includes(searchKeyword); + } + + if (!keywordFound) { + return []; + } } + return [payloadBody.event]; + }, }); type PayloadBody = { event: { + text?: string; channel: string; + user?: string; + channel_type:string; bot_id?: string; - channel_type:string + thread_ts?: string; }; }; From 95e7edd558330a5b3336b551a1e4b3c8867768f7 Mon Sep 17 00:00:00 2001 From: meenulekha-premakumar Date: Tue, 21 Oct 2025 17:26:36 +0200 Subject: [PATCH 4/5] feat: filter out system messages in slack new-message trigger --- .../slack/src/lib/triggers/new-message.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/pieces/community/slack/src/lib/triggers/new-message.ts b/packages/pieces/community/slack/src/lib/triggers/new-message.ts index c658b12de4b..32b2868c5d0 100644 --- a/packages/pieces/community/slack/src/lib/triggers/new-message.ts +++ b/packages/pieces/community/slack/src/lib/triggers/new-message.ts @@ -25,20 +25,14 @@ export const newMessageTrigger = createTrigger({ }), matchWholeWord: Property.Checkbox({ displayName: 'Match Whole Word Only', - description: 'If enabled, only matches the keyword as a complete word (e.g. "test" won\'t match "testing"). Leave empty if no keyword.', + description: 'If enabled, only matches the keyword as a complete word (e.g. "test" won\'t match "testing").', required: false, defaultValue: false, }), }, type: TriggerStrategy.APP_WEBHOOK, - sampleData: { - text: 'Hello world!', - channel: 'C1234567890', - user: 'U1234567890', - channel_type: 'channel', - ts: '1234567890.123456' - }, + sampleData: undefined, onEnable: async (context) => { // Older OAuth2 has team_id, newer has team.id const teamId = context.auth.data['team_id'] ?? context.auth.data['team']['id']; @@ -55,6 +49,11 @@ export const newMessageTrigger = createTrigger({ const payloadBody = context.payload.body as PayloadBody; const {ignoreBots, keyword, includeThreads, matchWholeWord} = context.propsValue; + // ignore system messages (joins, leaves, file shares, etc.) + if (payloadBody.event.subtype) { + return []; + } + if (ignoreBots && payloadBody.event.bot_id) { return []; } @@ -65,11 +64,6 @@ export const newMessageTrigger = createTrigger({ } } - // if text is not provided, return - if (!payloadBody.event.text || !payloadBody.event.text.trim()) { - return []; - } - // if keyword is provided, check if the message contains the keyword if (keyword && keyword.trim()) { if (!payloadBody.event.text) { @@ -105,5 +99,6 @@ type PayloadBody = { channel_type:string; bot_id?: string; thread_ts?: string; + subtype?: string; }; }; From 348c8a545dbbf3b869b4bf0c11e5e4263760ccda Mon Sep 17 00:00:00 2001 From: meenulekha-premakumar Date: Thu, 23 Oct 2025 13:59:05 +0200 Subject: [PATCH 5/5] fix: address PR comments --- packages/pieces/community/slack/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/pieces/community/slack/src/index.ts b/packages/pieces/community/slack/src/index.ts index 3e9c43c4d57..8302cc33a36 100644 --- a/packages/pieces/community/slack/src/index.ts +++ b/packages/pieces/community/slack/src/index.ts @@ -46,7 +46,6 @@ import { findUserByIdAction } from './lib/actions/find-user-by-id'; import { newUserTrigger } from './lib/triggers/new-user'; import { newSavedMessageTrigger } from './lib/triggers/new-saved-message'; import { newTeamCustomEmojiTrigger } from './lib/triggers/new-team-custom-emoji'; -import { messageContainsKeywordTrigger } from './lib/triggers/message-contains-keyword'; import { inviteUserToChannelAction } from './lib/actions/invite-user-to-channel'; import { listUsers } from './lib/actions/list-users'; import { deleteMessageAction } from './lib/actions/delete-message'; @@ -237,7 +236,6 @@ export const slack = createPiece({ newUserTrigger, newSavedMessageTrigger, newTeamCustomEmojiTrigger, - messageContainsKeywordTrigger, ], });