Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,66 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
import { WebClient } from '@slack/web-api';
const sampleData = {
type: 'channel_created',
channel: {
id: 'C024BE91L',
name: 'fun',
created: 1360782804,
creator: 'U024BE7LH',
},
};
export const channelCreated = createTrigger({
auth: slackAuth,
name: 'channel_created',
displayName: 'Channel created',
description: 'Triggers when a channel is created',
props: {},
type: TriggerStrategy.APP_WEBHOOK,
sampleData: sampleData,
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: ['channel_created'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
test: async (context) => {
const client = new WebClient(context.auth.access_token);
const response = await client.conversations.list({
exclude_archived: true,
limit: 10,
types: 'public_channel,private_channel',
});
if (!response.channels) {
return [];
}
return response.channels.map((channel) => {
return {
type: 'channel_created',
channel: {
id: channel.id,
name: channel.name,
created: channel.created,
creator: channel.creator,
},
};
});
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
return [payloadBody.event];
},
});
type PayloadBody = {
event: object;
};

View File

@@ -0,0 +1,101 @@
import {
Property,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { userId } from '../common/props';
import { slackAuth } from '../../';
import { parseCommand } from '../common/utils';
export const newCommandInDirectMessageTrigger = createTrigger({
auth: slackAuth,
name: 'new-command-in-direct-message',
displayName: 'New Command in Direct Message',
description:
'Triggers when a specific command is sent to the bot (e.g., @bot command arg1 arg2) via Direct Message.',
props: {
user: userId,
commands: Property.Array({
displayName: 'Commands',
description:
'List of valid commands that the bot should respond to (e.g., help, ocr, remind)',
required: true,
defaultValue: ['help'],
}),
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: true,
}),
ignoreSelfMessages: Property.Checkbox({
displayName: 'Ignore Message from Yourself ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
const commands = (context.propsValue.commands as string[]) ?? [];
const user = context.propsValue.user as string;
const authUserId = context.auth.data['authed_user']?.id;
if (payloadBody.event.channel_type !== 'im') {
return [];
}
// Check for bot messages if configured to ignore them
if (
(context.propsValue.ignoreBots && payloadBody.event.bot_id) ||
(context.propsValue.ignoreSelfMessages && payloadBody.event.user === authUserId)
) {
return [];
}
// Check for mention and parse command
if (user && payloadBody.event.text) {
const parsedCommand = parseCommand(
payloadBody.event.text,
user,
commands
);
if (parsedCommand && commands.includes(parsedCommand.command)) {
// Return event with parsed command
return [
{
...payloadBody.event,
parsed_command: parsedCommand,
},
];
}
}
return [];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
text?: string;
channel_type: string;
user: string;
};
};

View File

@@ -0,0 +1,127 @@
import {
OAuth2PropertyValue,
Property,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { getChannels, multiSelectChannelInfo, userId } from '../common/props';
import { slackAuth } from '../../';
import { parseCommand } from '../common/utils';
export const newCommand = createTrigger({
auth: slackAuth,
name: 'new_command',
displayName: 'New Command in Channel',
description:
'Triggers when a specific command is sent to the bot (e.g., @bot command arg1 arg2)',
props: {
info: multiSelectChannelInfo,
user: userId,
commands: Property.Array({
displayName: 'Commands',
description:
'List of valid commands that the bot should respond to (e.g., help, ocr, remind)',
required: true,
defaultValue: ['help'],
}),
channels: Property.MultiSelectDropdown({
auth: slackAuth,
displayName: 'Channels',
description:
'If no channel is selected, the flow will be triggered for commands in all channels',
required: false,
refreshers: [],
async options({ auth }) {
if (!auth) {
return {
disabled: true,
placeholder: 'connect slack account',
options: [],
};
}
const authentication = auth as OAuth2PropertyValue;
const accessToken = authentication['access_token'];
const channels = await getChannels(accessToken);
return {
disabled: false,
placeholder: 'Select channel',
options: channels,
};
},
}),
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: true,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
const channels = (context.propsValue.channels as string[]) ?? [];
const commands = (context.propsValue.commands as string[]) ?? [];
const user = context.propsValue.user as string;
// check if it's channel message
if (!['channel','group'].includes(payloadBody.event.channel_type)) {
return [];
}
// Check if we should process this channel
if (
!(channels.length === 0 || channels.includes(payloadBody.event.channel))
) {
return [];
}
// Check for bot messages if configured to ignore them
if (context.propsValue.ignoreBots && payloadBody.event.bot_id) {
return [];
}
// Check for mention and parse command
if (user && payloadBody.event.text) {
const parsedCommand = parseCommand(
payloadBody.event.text,
user,
commands
);
if (parsedCommand && commands.includes(parsedCommand.command)) {
// Return event with parsed command
return [
{
...payloadBody.event,
parsed_command: parsedCommand,
},
];
}
}
return [];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
text?: string;
channel_type:string
};
};

View File

@@ -0,0 +1,63 @@
import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
export const newDirectMessageTrigger = createTrigger({
auth: slackAuth,
name: 'new-direct-message',
displayName: 'New Direct Message',
description: 'Triggers when a message was posted in a direct message channel.',
props: {
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: false,
}),
ignoreSelfMessages: Property.Checkbox({
displayName: 'Ignore Message from Yourself ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
async run(context) {
const payloadBody = context.payload.body as PayloadBody;
const userId = context.auth.data['authed_user']?.id;
if (payloadBody.event.channel_type !== 'im') {
return [];
}
// check for bot messages
if (
(context.propsValue.ignoreBots && payloadBody.event.bot_id) ||
(context.propsValue.ignoreSelfMessages && payloadBody.event.user === userId)
) {
return [];
}
return [payloadBody.event];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
user: string;
channel_type: string;
};
};

View File

@@ -0,0 +1,80 @@
import {
Property,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
import { userId } from '../common/props';
export const newMentionInDirectMessageTrigger = createTrigger({
auth: slackAuth,
name: 'new-mention-in-direct-message',
displayName: 'New Mention in Direct Message',
description:
'Triggers when a username is mentioned in a direct message channel.',
props: {
user: userId,
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: false,
}),
ignoreSelfMessages: Property.Checkbox({
displayName: 'Ignore Message from Yourself ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
async run(context) {
const payloadBody = context.payload.body as PayloadBody;
const userId = context.auth.data['authed_user']?.id;
if (payloadBody.event.channel_type !== 'im') {
return [];
}
// check for bot messages
if (
(context.propsValue.ignoreBots && payloadBody.event.bot_id) ||
(context.propsValue.ignoreSelfMessages &&
payloadBody.event.user === userId)
) {
return [];
}
//check for mention
if (
context.propsValue.user &&
payloadBody.event.text?.includes(`<@${context.propsValue.user}>`)
) {
return [payloadBody.event];
}
return [];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
user: string;
channel_type: string;
text?: string;
};
};

View File

@@ -0,0 +1,99 @@
import {
OAuth2PropertyValue,
Property,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { getChannels, multiSelectChannelInfo, userId } from '../common/props';
import { slackAuth } from '../../';
export const newMention = createTrigger({
auth: slackAuth,
name: 'new_mention',
displayName: 'New Mention in Channel',
description: 'Triggers when a username is mentioned.',
props: {
info: multiSelectChannelInfo,
user: userId,
channels: Property.MultiSelectDropdown({
auth: slackAuth,
displayName: 'Channels',
description:
'If no channel is selected, the flow will be triggered for username mentions in all channels',
required: false,
refreshers: [],
async options({ auth }) {
if (!auth) {
return {
disabled: true,
placeholder: 'connect slack account',
options: [],
};
}
const authentication = auth as OAuth2PropertyValue;
const accessToken = authentication['access_token'];
const channels = await getChannels(accessToken);
return {
disabled: false,
placeholder: 'Select channel',
options: channels,
};
},
}),
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
const channels = (context.propsValue.channels as string[]) ?? [];
// check if it's channel message
if (!['channel','group'].includes(payloadBody.event.channel_type)) {
return [];
}
if (channels.length === 0 || channels.includes(payloadBody.event.channel)) {
// check for bot messages
if (context.propsValue.ignoreBots && payloadBody.event.bot_id) {
return [];
}
// check for mention
if (
context.propsValue.user &&
payloadBody.event.text?.includes(`<@${context.propsValue.user}>`)
) {
return [payloadBody.event];
}
}
return [];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
text?: string;
channel_type:string
};
};

View File

@@ -0,0 +1,64 @@
import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { singleSelectChannelInfo, slackChannel } from '../common/props';
import { slackAuth } from '../../';
import { WebClient } from '@slack/web-api';
import { isNil } from '@activepieces/shared';
import { getFirstFiveOrAll } from '../common/utils';
export const newMessageInChannelTrigger = createTrigger({
auth: slackAuth,
name: 'new-message-in-channel',
displayName: 'New Message Posted to Channel',
description: 'Triggers when a new message is posted to a specific #channel you choose.',
props: {
info: singleSelectChannelInfo,
channel: slackChannel(true),
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
sampleData: undefined,
async onEnable(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,
});
},
async onDisable(context) {
// Ignored
},
async run(context) {
const payloadBody = context.payload.body as PayloadBody;
// check if it's channel message
if (!['channel','group'].includes(payloadBody.event.channel_type)) {
return [];
}
if (payloadBody.event.channel === context.propsValue.channel) {
// check for bot messages
if (context.propsValue.ignoreBots && payloadBody.event.bot_id) {
return [];
}
return [payloadBody.event];
}
return [];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
channel_type:string
};
};

View File

@@ -0,0 +1,52 @@
import { Property, TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
export const newMessageTrigger = createTrigger({
auth: slackAuth,
name: 'new-message',
displayName: 'New Public Message Posted Anywhere',
description: 'Triggers when a new message is posted to any channel.',
props: {
ignoreBots: Property.Checkbox({
displayName: 'Ignore Bot Messages ?',
required: true,
defaultValue: false,
}),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['message'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
// check if it's channel message
if (!['channel','group'].includes(payloadBody.event.channel_type)) {
return [];
}
// check for bot messages
if (context.propsValue.ignoreBots && payloadBody.event.bot_id) {
return [];
}
return [payloadBody.event];
},
});
type PayloadBody = {
event: {
channel: string;
bot_id?: string;
channel_type:string
};
};

View File

@@ -0,0 +1,64 @@
import {
Property,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
import { slackChannel } from '../common/props';
import { WebClient } from '@slack/web-api';
export const newReactionAdded = createTrigger({
auth: slackAuth,
name: 'new_reaction_added',
displayName: 'New Reaction',
description: 'Triggers when a new reaction is added to a message',
props: {
emojis: Property.Array({
displayName: 'Emojis (E.g fire, smile)',
description: 'Select emojis to trigger on',
required: false,
}),
channel: slackChannel(false),
},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['reaction_added'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
if (context.propsValue.emojis) {
if (!context.propsValue.emojis.includes(payloadBody.event.reaction)) {
return [];
}
}
if (context.propsValue.channel) {
if (payloadBody.event.item['channel'] !== context.propsValue.channel) {
return [];
}
}
return [payloadBody.event];
},
});
type PayloadBody = {
event: {
reaction: string;
item: {
channel: string;
};
};
};

View File

@@ -0,0 +1,44 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
export const newSavedMessageTrigger = createTrigger({
auth: slackAuth,
name: 'new-saved-message',
displayName: 'New Saved Message',
description: 'Triggers when you save a message.',
props: {},
type: TriggerStrategy.APP_WEBHOOK,
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'];
context.app.createListeners({
events: ['star_added'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
// check if it's saved message
if (payloadBody.event.type === 'star_added' && payloadBody.event.item.type ==='message') {
return [payloadBody.event.item];
}
return [];
},
});
type PayloadBody = {
event: {
type: string;
event_ts: string;
item:{
type:string
}
};
};

View File

@@ -0,0 +1,62 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
import { WebClient } from '@slack/web-api';
const sampleData = {
id: 'heart',
image: 'https://emoji.slack-edge.com/T06BTHUEFFF/heart/84a171ae62daacc7.jpg',
};
export const newTeamCustomEmojiTrigger = createTrigger({
auth: slackAuth,
name: 'new-team-custom-emoji',
displayName: 'New Team Custom Emoji',
description: 'Triggers when a custom emoji has been added to a team.',
props: {},
type: TriggerStrategy.APP_WEBHOOK,
sampleData,
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: ['emoji_changed'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
test: async (context) => {
const client = new WebClient(context.auth.access_token);
const response = await client.emoji.list();
if (!response.emoji) return [sampleData];
return Object.entries(response.emoji).map(([id, image]) => ({
id,
image,
}));
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
// check if it's emoji message
if (payloadBody.event.type !== 'emoji_changed' && payloadBody.event.subtype !== 'add') {
return [];
}
return [{ id: payloadBody.event.name, image: payloadBody.event.value }];
},
});
type PayloadBody = {
event: {
type: string;
subtype: string;
name: string;
value: string;
};
};

View File

@@ -0,0 +1,103 @@
import { TriggerStrategy, createTrigger } from '@activepieces/pieces-framework';
import { slackAuth } from '../../';
import { WebClient } from '@slack/web-api';
const sampleData = {
id: 'USLACKBOT',
team_id: 'T06BTHUEFFF',
name: 'slackbot',
deleted: false,
color: '757575',
real_name: 'Slackbot',
tz: 'America/Los_Angeles',
tz_label: 'Pacific Daylight Time',
tz_offset: -25200,
profile: {
title: '',
phone: '',
skype: '',
real_name: 'Slackbot',
real_name_normalized: 'Slackbot',
display_name: 'Slackbot',
display_name_normalized: 'Slackbot',
fields: {},
status_text: '',
status_emoji: '',
status_emoji_display_info: [],
status_expiration: 0,
avatar_hash: 'sv41d8cd98f0',
always_active: true,
first_name: 'slackbot',
last_name: '',
image_24: 'https://a.slack-edge.com/80588/img/slackbot_24.png',
image_32: 'https://a.slack-edge.com/80588/img/slackbot_32.png',
image_48: 'https://a.slack-edge.com/80588/img/slackbot_48.png',
image_72: 'https://a.slack-edge.com/80588/img/slackbot_72.png',
image_192: 'https://a.slack-edge.com/80588/marketing/img/avatars/slackbot/avatar-slackbot.png',
image_512: 'https://a.slack-edge.com/80588/img/slackbot_512.png',
status_text_canonical: '',
team: 'T06BTHUEFFF',
},
is_admin: false,
is_owner: false,
is_primary_owner: false,
is_restricted: false,
is_ultra_restricted: false,
is_bot: false,
is_app_user: false,
updated: 0,
is_email_confirmed: false,
who_can_share_contact_card: 'EVERYONE',
};
export const newUserTrigger = createTrigger({
auth: slackAuth,
name: 'new-user',
displayName: 'New User',
description: 'Triggers when a new user is created / first joins your org.',
props: {},
type: TriggerStrategy.APP_WEBHOOK,
sampleData,
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: ['team_join'],
identifierValue: teamId,
});
},
onDisable: async (context) => {
// Ignored
},
test: async (context) => {
const client = new WebClient(context.auth.access_token);
const response = await client.users.list({limit:10});
if (!response.members) return [sampleData];
return response.members;
},
run: async (context) => {
const payloadBody = context.payload.body as PayloadBody;
// check if it's emoji message
if (payloadBody.event.type !== 'team_join') {
return [];
}
return [payloadBody.event.user];
},
});
type PayloadBody = {
event: {
type: string;
event_ts:string,
user:{
id:string
}
};
};