d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
246 lines
8.8 KiB
TypeScript
246 lines
8.8 KiB
TypeScript
import { SlackIcon } from '@/components/icons'
|
|
import { getScopesForService } from '@/lib/oauth/utils'
|
|
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
|
import {
|
|
SLACK_ALL_EVENT_OPTIONS,
|
|
SLACK_SIM_EVENT_OPTIONS,
|
|
SLACK_SOURCE_OPTIONS,
|
|
SLACK_THREAD_OPTIONS,
|
|
SLACK_TRIGGER_OUTPUTS,
|
|
slackEventsSupportingFilter,
|
|
} from '@/triggers/slack/shared'
|
|
import type { TriggerConfig } from '@/triggers/types'
|
|
|
|
// Filter sub-block gating is derived from the catalog's `filters` field so the
|
|
// UI conditions and the ingest route share one source of truth.
|
|
const SOURCE_FILTER_EVENTS = slackEventsSupportingFilter('source')
|
|
const CHANNEL_FILTER_EVENTS = slackEventsSupportingFilter('channels')
|
|
const THREAD_FILTER_EVENTS = slackEventsSupportingFilter('threads')
|
|
const EMOJI_FILTER_EVENTS = slackEventsSupportingFilter('emoji')
|
|
const NAME_FILTER_EVENTS = slackEventsSupportingFilter('name')
|
|
const INTERACTION_FILTER_EVENTS = slackEventsSupportingFilter('interaction')
|
|
// Bot/own toggles gate UI visibility only (the route applies them unconditionally),
|
|
// so they are not catalog `filters`.
|
|
const BOT_FILTER_EVENTS = ['message', 'app_mention']
|
|
const OWN_MESSAGE_EVENTS = ['message', 'app_mention', 'reaction_added', 'reaction_removed']
|
|
|
|
/**
|
|
* Unified Slack trigger. App Type selects how events arrive:
|
|
* - `sim` (default): the official Sim Slack app the user OAuth-connects — events
|
|
* route to this workflow by Slack `team_id` (stored as the webhook `routingKey`,
|
|
* derived at deploy time). No signing secret, bot token, or app setup.
|
|
* - `custom`: a bring-your-own Slack app, selected as a reusable bot credential
|
|
* (set up once). Events route by that credential (`webhook.routingKey =
|
|
* credentialId`) to one shared ingest URL, so many triggers on the same bot
|
|
* share a single Request URL, verified with the bot's own signing secret.
|
|
*/
|
|
export const slackOAuthTrigger: TriggerConfig = {
|
|
id: 'slack_oauth',
|
|
name: 'Slack',
|
|
provider: 'slack_app',
|
|
description: 'Trigger from Slack events (mentions, messages, reactions)',
|
|
version: '1.0.0',
|
|
icon: SlackIcon,
|
|
|
|
subBlocks: [
|
|
{
|
|
id: 'eventType',
|
|
title: 'Event',
|
|
type: 'dropdown',
|
|
options: [...SLACK_SIM_EVENT_OPTIONS],
|
|
placeholder: 'Select an event',
|
|
description:
|
|
'The single Slack event this trigger fires on. Add another trigger block for another event.',
|
|
required: true,
|
|
mode: 'trigger',
|
|
dependsOn: ['appType'],
|
|
fetchOptions: async (blockId: string) => {
|
|
const appType = useSubBlockStore.getState().getValue(blockId, 'appType')
|
|
return appType === 'custom' ? [...SLACK_ALL_EVENT_OPTIONS] : [...SLACK_SIM_EVENT_OPTIONS]
|
|
},
|
|
},
|
|
{
|
|
id: 'appType',
|
|
title: 'App Type',
|
|
type: 'dropdown',
|
|
// Ship 1 exposes custom bots only; the native "Sim" app mode returns in a
|
|
// later ship (un-hide + default back to 'sim'). Hidden — not removed — so
|
|
// the seeded 'custom' value keeps every `appType==='custom'` condition, the
|
|
// event-catalog fetch, and the deploy routing branch resolving correctly.
|
|
hidden: true,
|
|
options: [
|
|
{ label: 'Sim', id: 'sim' },
|
|
{ label: 'Custom', id: 'custom' },
|
|
],
|
|
value: () => 'custom',
|
|
// `value()` only seeds editor-created blocks; `defaultValue` is what
|
|
// buildProviderConfig persists when the stored value is absent
|
|
// (imported / programmatically-created workflows).
|
|
defaultValue: 'custom',
|
|
description: 'Use the official Sim Slack app, or your own custom Slack app.',
|
|
mode: 'trigger',
|
|
},
|
|
{
|
|
id: 'triggerCredentials',
|
|
title: 'Slack Account',
|
|
type: 'oauth-input',
|
|
canonicalParamId: 'oauthCredential',
|
|
serviceId: 'slack',
|
|
requiredScopes: getScopesForService('slack'),
|
|
placeholder: 'Select Slack account',
|
|
required: { field: 'appType', value: 'sim' },
|
|
mode: 'trigger',
|
|
condition: { field: 'appType', value: 'sim' },
|
|
},
|
|
{
|
|
id: 'customBotCredential',
|
|
title: 'Slack Bot',
|
|
type: 'oauth-input',
|
|
canonicalParamId: 'botCredential',
|
|
serviceId: 'slack',
|
|
credentialKind: 'custom-bot',
|
|
requiredScopes: getScopesForService('slack'),
|
|
placeholder: 'Select a connected bot',
|
|
description:
|
|
'Choose a custom Slack bot you set up once and reuse across triggers and actions.',
|
|
required: { field: 'appType', value: 'custom' },
|
|
mode: 'trigger',
|
|
condition: { field: 'appType', value: 'custom' },
|
|
},
|
|
{
|
|
id: 'manualBotCredential',
|
|
title: 'Bot Credential ID',
|
|
type: 'short-input',
|
|
canonicalParamId: 'botCredential',
|
|
placeholder: 'Enter bot credential ID',
|
|
description: 'Set the custom bot credential ID directly.',
|
|
required: { field: 'appType', value: 'custom' },
|
|
mode: 'trigger-advanced',
|
|
condition: { field: 'appType', value: 'custom' },
|
|
},
|
|
{
|
|
id: 'source',
|
|
title: 'Source',
|
|
type: 'dropdown',
|
|
multiSelect: true,
|
|
options: [...SLACK_SOURCE_OPTIONS],
|
|
placeholder: 'Any source',
|
|
description:
|
|
'Restrict to direct messages, public channels, or private channels. Leave empty to match any.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: SOURCE_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'channelFilter',
|
|
title: 'Channels',
|
|
type: 'channel-selector',
|
|
canonicalParamId: 'channelFilter',
|
|
multiSelect: true,
|
|
serviceId: 'slack',
|
|
selectorKey: 'slack.channels',
|
|
placeholder: 'Any channel the bot is in',
|
|
description:
|
|
'Restrict to specific channels. Leave empty to trigger on any channel the bot has been added to.',
|
|
dependsOn: { any: ['triggerCredentials', 'customBotCredential'] },
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: CHANNEL_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'manualChannelFilter',
|
|
title: 'Channel IDs',
|
|
type: 'short-input',
|
|
canonicalParamId: 'channelFilter',
|
|
placeholder: 'C0123456789, C0987654321',
|
|
description: 'Comma-separated channel IDs to restrict to. Set IDs directly here.',
|
|
required: false,
|
|
mode: 'trigger-advanced',
|
|
condition: { field: 'eventType', value: CHANNEL_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'threads',
|
|
title: 'Threads',
|
|
type: 'dropdown',
|
|
options: [...SLACK_THREAD_OPTIONS],
|
|
value: () => 'include',
|
|
description:
|
|
'Include thread replies, exclude them (top-level only), or fire only on thread replies.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: THREAD_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'emoji',
|
|
title: 'Emoji',
|
|
type: 'short-input',
|
|
placeholder: 'thumbsup, white_check_mark',
|
|
description: 'Comma-separated emoji names to restrict to. Leave empty to match any emoji.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: EMOJI_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'nameContains',
|
|
title: 'Name contains',
|
|
type: 'short-input',
|
|
placeholder: 'incident-',
|
|
description: 'Only fire when the created channel name contains this text.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: NAME_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'interactionFilter',
|
|
title: 'Action / Callback ID',
|
|
type: 'short-input',
|
|
placeholder: 'approve_btn, deny_btn',
|
|
description:
|
|
'Comma-separated action_ids (buttons/selects) or callback_ids (modals) to restrict to. Leave empty to fire on any interaction.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: INTERACTION_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'filterBotMessages',
|
|
title: 'Ignore bot messages',
|
|
type: 'switch',
|
|
defaultValue: true,
|
|
description: "Ignore messages sent by other bots. This app's own output is always ignored.",
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: BOT_FILTER_EVENTS },
|
|
},
|
|
{
|
|
id: 'includeOwnMessages',
|
|
title: 'Include own bot messages',
|
|
type: 'switch',
|
|
defaultValue: false,
|
|
description:
|
|
"Also fire on this app's own messages and reactions. Can cause loops — use with care.",
|
|
required: false,
|
|
mode: 'trigger-advanced',
|
|
condition: { field: 'eventType', value: OWN_MESSAGE_EVENTS },
|
|
},
|
|
{
|
|
id: 'includeFiles',
|
|
title: 'Include file attachments',
|
|
type: 'switch',
|
|
defaultValue: false,
|
|
description: 'Download and include file attachments from messages. Requires files:read.',
|
|
required: false,
|
|
mode: 'trigger',
|
|
condition: { field: 'eventType', value: 'message' },
|
|
},
|
|
],
|
|
|
|
outputs: SLACK_TRIGGER_OUTPUTS,
|
|
|
|
webhook: {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
},
|
|
}
|