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:
@@ -0,0 +1,49 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { singleSelectChannelInfo, slackChannel } from '../common/props';
|
||||
|
||||
import { WebClient } from '@slack/web-api';
|
||||
import { processMessageTimestamp } from '../common/utils';
|
||||
|
||||
export const addRectionToMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-add-reaction-to-message',
|
||||
displayName: 'Add Reaction to Message',
|
||||
description: 'Add an emoji reaction to a message.',
|
||||
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
ts: Property.ShortText({
|
||||
displayName: 'Message Timestamp',
|
||||
description:
|
||||
'Please provide the timestamp of the message you wish to react, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.',
|
||||
required: true,
|
||||
}),
|
||||
reaction: Property.ShortText({
|
||||
displayName: 'Reaction (emoji) name',
|
||||
required: true,
|
||||
description: 'e.g.`thumbsup`',
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const { channel, ts, reaction } = context.propsValue;
|
||||
|
||||
const slack = new WebClient(context.auth.access_token);
|
||||
|
||||
const messageTimestamp = processMessageTimestamp(ts);
|
||||
|
||||
if (messageTimestamp) {
|
||||
const response = await slack.reactions.add({
|
||||
channel,
|
||||
timestamp: messageTimestamp,
|
||||
name: reaction,
|
||||
});
|
||||
|
||||
return response;
|
||||
} else {
|
||||
throw new Error('Invalid Timestamp Value.');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const createChannelAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-create-channel',
|
||||
displayName: 'Create Channel',
|
||||
description: 'Creates a new channel.',
|
||||
props: {
|
||||
channelName: Property.ShortText({
|
||||
displayName: 'Channel Name',
|
||||
required: true,
|
||||
}),
|
||||
isPrivate: Property.Checkbox({
|
||||
displayName: 'Is Private?',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.access_token);
|
||||
return await client.conversations.create({
|
||||
name: propsValue.channelName,
|
||||
is_private: propsValue.isPrivate,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const findUserByEmailAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-find-user-by-email',
|
||||
displayName: 'Find User by Email',
|
||||
description: 'Finds a user by matching against their email address.',
|
||||
props: {
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const email = propsValue.email;
|
||||
const client = new WebClient(auth.access_token);
|
||||
return await client.users.lookupByEmail({
|
||||
email,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { UsersListResponse, WebClient } from '@slack/web-api';
|
||||
|
||||
export const findUserByHandleAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-find-user-by-handle',
|
||||
displayName: 'Find User by Handle',
|
||||
description: 'Finds a user by matching against their Slack handle.',
|
||||
props: {
|
||||
handle: Property.ShortText({
|
||||
displayName: 'Handle',
|
||||
description: 'User handle (display name), without the leading @',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const handle = propsValue.handle.replace('@', '');
|
||||
const client = new WebClient(auth.access_token);
|
||||
for await (const page of client.paginate('users.list', {
|
||||
limit: 1000, // Only limits page size, not total number of results
|
||||
})) {
|
||||
const response = page as UsersListResponse;
|
||||
if (response.members) {
|
||||
const matchedMember = response.members.find(
|
||||
(member) => member.profile?.display_name === handle
|
||||
);
|
||||
if (matchedMember) {
|
||||
return matchedMember;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find user with handle @${handle}`);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const findUserByIdAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'find-user-by-id',
|
||||
displayName: 'Find User by ID',
|
||||
description: 'Finds a user by their ID.',
|
||||
props: {
|
||||
id: Property.ShortText({
|
||||
displayName: 'ID',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.access_token);
|
||||
return await client.users.profile.get({
|
||||
user: propsValue.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ConversationsHistoryResponse, WebClient } from '@slack/web-api';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { singleSelectChannelInfo, slackChannel } from '../common/props';
|
||||
|
||||
export const getChannelHistory = createAction({
|
||||
// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication,
|
||||
name: 'getChannelHistory',
|
||||
auth: slackAuth,
|
||||
displayName: 'Get channel history',
|
||||
description:
|
||||
'Retrieve all messages from a specific channel ("conversation") between specified timestamps',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
oldest: Property.Number({
|
||||
displayName: 'Oldest',
|
||||
description:
|
||||
'Only messages after this timestamp will be included in results',
|
||||
required: false,
|
||||
}),
|
||||
latest: Property.Number({
|
||||
displayName: 'Latest',
|
||||
description:
|
||||
'Only messages before this timestamp will be included in results. Default is the current time',
|
||||
required: false,
|
||||
}),
|
||||
inclusive: Property.Checkbox({
|
||||
displayName: 'Inclusive',
|
||||
description:
|
||||
'Include messages with oldest or latest timestamps in results. Ignored unless either timestamp is specified',
|
||||
defaultValue: false,
|
||||
required: true,
|
||||
}),
|
||||
includeAllMetadata: Property.Checkbox({
|
||||
displayName: 'Include all metadata',
|
||||
description: 'Return all metadata associated with each message',
|
||||
defaultValue: false,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.access_token);
|
||||
const messages = [];
|
||||
await client.conversations.history({ channel: propsValue.channel });
|
||||
for await (const page of client.paginate('conversations.history', {
|
||||
channel: propsValue.channel,
|
||||
oldest: propsValue.oldest,
|
||||
latest: propsValue.latest,
|
||||
limit: 200, // page size, does not limit the total number of results
|
||||
include_all_metadata: propsValue.includeAllMetadata,
|
||||
inclusive: propsValue.inclusive,
|
||||
})) {
|
||||
const response = page as ConversationsHistoryResponse;
|
||||
if (response.messages) {
|
||||
messages.push(...response.messages);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { slackAuth } from '../../index';
|
||||
import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const getFileAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'get-file',
|
||||
displayName: 'Get File',
|
||||
description: 'Return information about a given file ID.',
|
||||
props: {
|
||||
fileId: Property.ShortText({
|
||||
displayName: 'File ID',
|
||||
required: true,
|
||||
description: 'You can pass the file ID from the New Message Trigger payload.',
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const client = new WebClient(context.auth.access_token);
|
||||
|
||||
const fileData = await client.files.info({ file: context.propsValue.fileId });
|
||||
|
||||
const fileDownloadUrl = fileData.file?.url_private_download;
|
||||
|
||||
if (!fileDownloadUrl) {
|
||||
throw new Error('Unable to find the download URL.');
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: fileDownloadUrl,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: context.auth.access_token,
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
return {
|
||||
...fileData.file,
|
||||
data: await context.files.write({
|
||||
fileName: fileData.file?.name || `file`,
|
||||
data: Buffer.from(response.body),
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { singleSelectChannelInfo, slackChannel } from '../common/props';
|
||||
import { processMessageTimestamp } from '../common/utils';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const getMessageAction = createAction({
|
||||
name: 'get-message',
|
||||
displayName: 'Get Message by Timestamp',
|
||||
description: `Retrieves a specific message from a channel history using the message's timestamp.`,
|
||||
auth: slackAuth,
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
ts: Property.ShortText({
|
||||
displayName: 'Message Timestamp',
|
||||
description:
|
||||
'Please provide the timestamp of the message you wish to retrieve, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const messageTimestamp = processMessageTimestamp(propsValue.ts);
|
||||
if (!messageTimestamp) {
|
||||
throw new Error('Invalid Timestamp Value.');
|
||||
}
|
||||
const client = new WebClient(auth.access_token);
|
||||
|
||||
return await client.conversations.history({
|
||||
channel: propsValue.channel,
|
||||
latest: messageTimestamp,
|
||||
limit: 1,
|
||||
inclusive: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { slackAuth } from '../../index';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { singleSelectChannelInfo, slackChannel, userId } from '../common/props';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const inviteUserToChannelAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'invite-user-to-channel',
|
||||
displayName: 'Invite User to Channel',
|
||||
description: 'Invites an existing User to an existing channel.',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
userId,
|
||||
},
|
||||
async run(context) {
|
||||
const client = new WebClient(context.auth.access_token);
|
||||
|
||||
return await client.conversations.invite({
|
||||
channel: context.propsValue.channel,
|
||||
users: `${context.propsValue.userId}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { UsersListResponse, WebClient } from '@slack/web-api';
|
||||
import { slackAuth } from '../..';
|
||||
import { Member } from '@slack/web-api/dist/types/response/UsersListResponse';
|
||||
|
||||
export const listUsers = createAction({
|
||||
// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication,
|
||||
name: 'listUsers',
|
||||
displayName: 'List users',
|
||||
description: 'List all users of the workspace',
|
||||
props: {
|
||||
includeBots: Property.Checkbox({
|
||||
displayName: 'Include bots?',
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
}),
|
||||
includeDisabled: Property.Checkbox({
|
||||
displayName: 'Include disabled users?',
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
auth: slackAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.access_token);
|
||||
const results: Member[] = [];
|
||||
for await (const page of client.paginate('users.list', {
|
||||
limit: 1000, // Only limits page size, not total number of results
|
||||
})) {
|
||||
const response = page as UsersListResponse;
|
||||
if (response.members) {
|
||||
results.push(
|
||||
...response.members.filter(
|
||||
(member) =>
|
||||
(propsValue.includeDisabled || !member.deleted) &&
|
||||
(propsValue.includeBots || !member.is_bot)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import slackifyMarkdown from 'slackify-markdown';
|
||||
|
||||
export const markdownToSlackFormat = createAction({
|
||||
name: 'markdownToSlackFormat',
|
||||
displayName: 'Markdown to Slack format',
|
||||
description:
|
||||
"Convert Markdown-formatted text to Slack's pseudo - markdown syntax",
|
||||
requireAuth: false,
|
||||
props: {
|
||||
markdown: Property.LongText({
|
||||
displayName: 'Markdown text',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
|
||||
async run({ propsValue }) {
|
||||
return slackifyMarkdown(propsValue.markdown);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import {
|
||||
profilePicture,
|
||||
text,
|
||||
userId,
|
||||
username,
|
||||
actions,
|
||||
} from '../common/props';
|
||||
import { requestAction } from '../common/request-action';
|
||||
|
||||
export const requestActionDirectMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'request_action_direct_message',
|
||||
displayName: 'Request Action from A User',
|
||||
description:
|
||||
'Send a message to a user and wait until the user selects an action',
|
||||
props: {
|
||||
userId,
|
||||
text,
|
||||
actions,
|
||||
username,
|
||||
profilePicture,
|
||||
},
|
||||
async run(context) {
|
||||
const { userId } = context.propsValue;
|
||||
assertNotNullOrUndefined(userId, 'userId');
|
||||
|
||||
return await requestAction(userId, context);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import {
|
||||
profilePicture,
|
||||
text,
|
||||
slackChannel,
|
||||
username,
|
||||
actions,
|
||||
singleSelectChannelInfo,
|
||||
threadTs,
|
||||
} from '../common/props';
|
||||
import { requestAction } from '../common/request-action';
|
||||
|
||||
export const requestActionMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'request_action_message',
|
||||
displayName: 'Request Action in A Channel',
|
||||
description:
|
||||
'Send a message in a channel and wait until an action is selected',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
text,
|
||||
actions,
|
||||
threadTs,
|
||||
username,
|
||||
profilePicture,
|
||||
},
|
||||
async run(context) {
|
||||
const { channel } = context.propsValue;
|
||||
assertNotNullOrUndefined(channel, 'channel');
|
||||
|
||||
return await requestAction(channel, context);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { slackSendMessage } from '../common/utils';
|
||||
import { slackAuth } from '../..';
|
||||
import {
|
||||
assertNotNullOrUndefined,
|
||||
ExecutionType,
|
||||
PauseType,
|
||||
} from '@activepieces/shared';
|
||||
import { profilePicture, text, userId, username } from '../common/props';
|
||||
import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
|
||||
|
||||
export const requestApprovalDirectMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'request_approval_direct_message',
|
||||
displayName: 'Request Approval from A User',
|
||||
description:
|
||||
'Send approval message to a user and then wait until the message is approved or disapproved',
|
||||
props: {
|
||||
userId,
|
||||
text,
|
||||
username,
|
||||
profilePicture,
|
||||
},
|
||||
async run(context) {
|
||||
if (context.executionType === ExecutionType.BEGIN) {
|
||||
const token = context.auth.access_token;
|
||||
const { userId, username, profilePicture } = context.propsValue;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(text, 'text');
|
||||
assertNotNullOrUndefined(userId, 'userId');
|
||||
|
||||
const postMessage = await slackSendMessage({
|
||||
token,
|
||||
text: `${context.propsValue.text}`,
|
||||
username,
|
||||
profilePicture,
|
||||
conversationId: userId,
|
||||
});
|
||||
|
||||
const dmId = (postMessage as ChatPostMessageResponse).channel as string;
|
||||
const messageTs = (postMessage as ChatPostMessageResponse).ts as string
|
||||
|
||||
const approvalLink = context.generateResumeUrl({
|
||||
queryParams: { action: 'approve',messageTs },
|
||||
});
|
||||
const disapprovalLink = context.generateResumeUrl({
|
||||
queryParams: { action: 'disapprove',messageTs },
|
||||
});
|
||||
|
||||
const client = new WebClient(token);
|
||||
await client.chat.update({
|
||||
ts:messageTs,
|
||||
channel:dmId,
|
||||
text: context.propsValue.text,
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${context.propsValue.text}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
block_id: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Approve',
|
||||
},
|
||||
style: 'primary',
|
||||
url: approvalLink,
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Disapprove',
|
||||
},
|
||||
style: 'danger',
|
||||
url: disapprovalLink,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
context.run.pause({
|
||||
pauseMetadata: {
|
||||
type: PauseType.WEBHOOK,
|
||||
response: {},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
approved: false, // default approval is false
|
||||
messageTs
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
approved: context.resumePayload.queryParams['action'] === 'approve',
|
||||
messageTs: context.resumePayload.queryParams['messageTs']
|
||||
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { slackSendMessage } from '../common/utils';
|
||||
import { slackAuth } from '../..';
|
||||
import {
|
||||
assertNotNullOrUndefined,
|
||||
ExecutionType,
|
||||
PauseType,
|
||||
} from '@activepieces/shared';
|
||||
import {
|
||||
profilePicture,
|
||||
singleSelectChannelInfo,
|
||||
slackChannel,
|
||||
text,
|
||||
username,
|
||||
} from '../common/props';
|
||||
import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
|
||||
|
||||
export const requestSendApprovalMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'request_approval_message',
|
||||
displayName: 'Request Approval in a Channel',
|
||||
description:
|
||||
'Send approval message to a channel and then wait until the message is approved or disapproved',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
text,
|
||||
username,
|
||||
profilePicture,
|
||||
},
|
||||
async run(context) {
|
||||
if (context.executionType === ExecutionType.BEGIN) {
|
||||
const token = context.auth.access_token;
|
||||
const { channel, username, profilePicture } = context.propsValue;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(text, 'text');
|
||||
assertNotNullOrUndefined(channel, 'channel');
|
||||
|
||||
const postMessage = await slackSendMessage({
|
||||
token,
|
||||
text: `${context.propsValue.text}`,
|
||||
username,
|
||||
profilePicture,
|
||||
conversationId: channel,
|
||||
});
|
||||
const messageTs = (postMessage as ChatPostMessageResponse).ts as string
|
||||
|
||||
const approvalLink = context.generateResumeUrl({
|
||||
queryParams: { action: 'approve', channel, messageTs },
|
||||
});
|
||||
const disapprovalLink = context.generateResumeUrl({
|
||||
queryParams: { action: 'disapprove', channel, messageTs },
|
||||
});
|
||||
|
||||
const client = new WebClient(token);
|
||||
await client.chat.update({
|
||||
channel: channel,
|
||||
ts: messageTs,
|
||||
text: context.propsValue.text,
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${context.propsValue.text}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
block_id: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Approve',
|
||||
},
|
||||
style: 'primary',
|
||||
url: approvalLink,
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Disapprove',
|
||||
},
|
||||
style: 'danger',
|
||||
url: disapprovalLink,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
context.run.pause({
|
||||
pauseMetadata: {
|
||||
type: PauseType.WEBHOOK,
|
||||
response: {},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
approved: false, // default approval is false
|
||||
messageTs
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
approved: context.resumePayload.queryParams['action'] === 'approve',
|
||||
messageTs: context.resumePayload.queryParams['messageTs']
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
import { slackChannel } from '../common/props';
|
||||
import { processMessageTimestamp } from '../common/utils';
|
||||
|
||||
export const retrieveThreadMessages = createAction({
|
||||
name: 'retrieveThreadMessages',
|
||||
displayName: 'Retrieve Thread Messages',
|
||||
description: 'Retrieves thread messages by channel and thread timestamp.',
|
||||
auth: slackAuth,
|
||||
props: {
|
||||
channel: slackChannel(true),
|
||||
threadTs: Property.ShortText({
|
||||
displayName: 'Thread ts',
|
||||
description:
|
||||
'Provide the ts (timestamp) value of the **parent** message to retrieve replies of this message. Do not use the ts value of the reply itself; use its parent instead. For example `1710304378.475129`.Alternatively, you can easily obtain the message link by clicking on the three dots next to the parent message and selecting the `Copy link` option.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.access_token);
|
||||
const messageTimestamp = processMessageTimestamp(propsValue.threadTs);
|
||||
if (!messageTimestamp) {
|
||||
throw new Error('Invalid Timestamp Value.');
|
||||
}
|
||||
return await client.conversations.replies({
|
||||
channel: propsValue.channel,
|
||||
ts: messageTimestamp,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const searchMessages = createAction({
|
||||
name: 'searchMessages',
|
||||
displayName: 'Search messages',
|
||||
description: 'Searches for messages matching a query',
|
||||
auth: slackAuth,
|
||||
props: {
|
||||
query: Property.ShortText({
|
||||
displayName: 'Search query',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const userToken = auth.data['authed_user']?.access_token;
|
||||
if (userToken === undefined) {
|
||||
throw new Error(JSON.stringify(
|
||||
{
|
||||
message: 'Missing user token, please re-authenticate'
|
||||
}
|
||||
));
|
||||
}
|
||||
const client = new WebClient(userToken);
|
||||
const matches = [];
|
||||
|
||||
// We can't use the usual "for await ... of" syntax with client.paginate
|
||||
// Because search.messages uses a bastardized version of cursor-based pagination
|
||||
// Where you need to pass * as first cursor
|
||||
// https://api.slack.com/methods/search.messages#arg_cursor
|
||||
let cursor = '*';
|
||||
do {
|
||||
const page = await client.search.messages({
|
||||
query: propsValue.query,
|
||||
count: 100,
|
||||
// @ts-expect-error TS2353 - SDK is not aware cursor is actually supported
|
||||
cursor,
|
||||
});
|
||||
if (page.messages?.matches) {
|
||||
matches.push(...page.messages.matches);
|
||||
}
|
||||
// @ts-expect-error TS2353 - SDK is not aware next_cursor is actually returned
|
||||
cursor = page.messages?.pagination?.next_cursor;
|
||||
} while (cursor);
|
||||
|
||||
return matches;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackSendMessage } from '../common/utils';
|
||||
import { slackAuth } from '../../';
|
||||
import { assertNotNullOrUndefined } from '@activepieces/shared';
|
||||
import {
|
||||
profilePicture,
|
||||
text,
|
||||
userId,
|
||||
username,
|
||||
blocks,
|
||||
mentionOriginFlow,
|
||||
} from '../common/props';
|
||||
import { Block,KnownBlock } from '@slack/web-api';
|
||||
|
||||
|
||||
export const slackSendDirectMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'send_direct_message',
|
||||
displayName: 'Send Message To A User',
|
||||
description: 'Send message to a user',
|
||||
props: {
|
||||
userId,
|
||||
text,
|
||||
username,
|
||||
profilePicture,
|
||||
mentionOriginFlow,
|
||||
blocks,
|
||||
unfurlLinks: Property.Checkbox({
|
||||
displayName: 'Unfurl Links',
|
||||
description: 'Enable link unfurling for this message',
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const { text, userId, blocks, unfurlLinks, mentionOriginFlow } = context.propsValue;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(text, 'text');
|
||||
assertNotNullOrUndefined(userId, 'userId');
|
||||
|
||||
const blockList: (KnownBlock | Block)[] = [{ type: 'section', text: { type: 'mrkdwn', text } }]
|
||||
|
||||
if(blocks && Array.isArray(blocks)) {
|
||||
blockList.push(...(blocks as unknown as (KnownBlock | Block)[]))
|
||||
}
|
||||
|
||||
if(mentionOriginFlow) {
|
||||
(blockList as KnownBlock[])?.push({ type: 'context', elements: [
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": `Message sent by <${new URL(context.server.publicUrl).origin}/projects/${context.project.id}/flows/${context.flows.current.id}|this flow>.`
|
||||
}
|
||||
] })
|
||||
}
|
||||
|
||||
return slackSendMessage({
|
||||
token,
|
||||
text,
|
||||
username: context.propsValue.username,
|
||||
profilePicture: context.propsValue.profilePicture,
|
||||
conversationId: userId,
|
||||
blocks:blockList,
|
||||
unfurlLinks,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
profilePicture,
|
||||
slackChannel,
|
||||
username,
|
||||
blocks,
|
||||
threadTs,
|
||||
singleSelectChannelInfo,
|
||||
mentionOriginFlow,
|
||||
} from '../common/props';
|
||||
import { processMessageTimestamp, slackSendMessage } from '../common/utils';
|
||||
import { slackAuth } from '../../';
|
||||
import { Block,KnownBlock } from '@slack/web-api';
|
||||
|
||||
|
||||
export const slackSendMessageAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'send_channel_message',
|
||||
displayName: 'Send Message To A Channel',
|
||||
description: 'Send message to a channel',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
text: Property.LongText({
|
||||
displayName: 'Message',
|
||||
description: 'The text of your message. When using Block Kit blocks, this is used as a fallback for notifications.',
|
||||
required: false,
|
||||
}),
|
||||
threadTs,
|
||||
username,
|
||||
profilePicture,
|
||||
file: Property.File({
|
||||
displayName: 'Attachment',
|
||||
required: false,
|
||||
}),
|
||||
replyBroadcast: Property.Checkbox({
|
||||
displayName: 'Broadcast reply to channel',
|
||||
description: 'When replying to a thread, also make the message visible to everyone in the channel (only applicable when Thread Timestamp is provided)',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
mentionOriginFlow,
|
||||
unfurlLinks: Property.Checkbox({
|
||||
displayName: 'Unfurl Links',
|
||||
description: 'Enable link unfurling for this message',
|
||||
required: false,
|
||||
defaultValue: true,
|
||||
}),
|
||||
blocks,
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const { text, channel, username, profilePicture, threadTs, file, mentionOriginFlow, blocks, replyBroadcast, unfurlLinks } =
|
||||
context.propsValue;
|
||||
|
||||
if (!text && (!blocks || !Array.isArray(blocks) || blocks.length === 0)) {
|
||||
throw new Error('Either Message or Block Kit blocks must be provided');
|
||||
}
|
||||
|
||||
const blockList: (KnownBlock | Block)[] = [];
|
||||
|
||||
|
||||
if (text && (!blocks || !Array.isArray(blocks) || blocks.length === 0)) {
|
||||
blockList.push({ type: 'section', text: { type: 'mrkdwn', text } });
|
||||
}
|
||||
|
||||
if(blocks && Array.isArray(blocks) && blocks.length > 0) {
|
||||
blockList.push(...(blocks as unknown as (KnownBlock | Block)[]))
|
||||
}
|
||||
|
||||
if(mentionOriginFlow) {
|
||||
(blockList as KnownBlock[])?.push({ type: 'context', elements: [
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": `Message sent by <${new URL(context.server.publicUrl).origin}/projects/${context.project.id}/flows/${context.flows.current.id}|this flow>.`
|
||||
}
|
||||
] })
|
||||
}
|
||||
|
||||
return slackSendMessage({
|
||||
token,
|
||||
text: text || undefined,
|
||||
username,
|
||||
profilePicture,
|
||||
conversationId: channel,
|
||||
threadTs: threadTs ? processMessageTimestamp(threadTs) : undefined,
|
||||
file,
|
||||
blocks: blockList.length > 0 ? blockList : undefined,
|
||||
replyBroadcast,
|
||||
unfurlLinks,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { slackAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { singleSelectChannelInfo, slackChannel } from '../common/props';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const setChannelTopicAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'set-channel-topic',
|
||||
displayName: 'Set Channel Topic',
|
||||
description: 'Sets the topic on a selected channel.',
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
topic: Property.LongText({
|
||||
displayName: 'Topic',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { channel, topic } = context.propsValue;
|
||||
const client = new WebClient(context.auth.access_token);
|
||||
|
||||
return await client.conversations.setTopic({
|
||||
channel,
|
||||
topic,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
import { z } from 'zod';
|
||||
import { propsValidation } from '@activepieces/pieces-common';
|
||||
|
||||
export const setUserStatusAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-set-user-status',
|
||||
displayName: 'Set User Status',
|
||||
description: "Sets a user's custom status",
|
||||
props: {
|
||||
text: Property.ShortText({
|
||||
displayName: 'Text',
|
||||
required: true,
|
||||
}),
|
||||
emoji: Property.ShortText({
|
||||
displayName: 'Emoji',
|
||||
required: false,
|
||||
description:
|
||||
'Emoji shortname (standard or custom), e.g. :tada: or :train:',
|
||||
}),
|
||||
expiration: Property.Number({
|
||||
displayName: 'Expires at',
|
||||
description: 'Unix timestamp - if not set, the status will not expire',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
await propsValidation.validateZod(propsValue, {
|
||||
text: z.string().max(100),
|
||||
});
|
||||
|
||||
const client = new WebClient(auth.data['authed_user']?.access_token);
|
||||
return await client.users.profile.set({
|
||||
profile: {
|
||||
status_text: propsValue.text,
|
||||
status_emoji: propsValue.emoji,
|
||||
status_expiration: propsValue.expiration,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../..';
|
||||
import { blocks, singleSelectChannelInfo, slackChannel } from '../common/props';
|
||||
import { processMessageTimestamp } from '../common/utils';
|
||||
import { Block,KnownBlock, WebClient } from '@slack/web-api';
|
||||
|
||||
export const updateMessage = createAction({
|
||||
// auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication,
|
||||
name: 'updateMessage',
|
||||
displayName: 'Update message',
|
||||
description: 'Update an existing message',
|
||||
auth: slackAuth,
|
||||
props: {
|
||||
info: singleSelectChannelInfo,
|
||||
channel: slackChannel(true),
|
||||
ts: Property.ShortText({
|
||||
displayName: 'Message Timestamp',
|
||||
description:
|
||||
'Please provide the timestamp of the message you wish to update, such as `1710304378.475129`. Alternatively, you can easily obtain the message link by clicking on the three dots next to the message and selecting the `Copy link` option.',
|
||||
required: true,
|
||||
}),
|
||||
text: Property.LongText({
|
||||
displayName: 'Message',
|
||||
description: 'The updated text of your message',
|
||||
required: true,
|
||||
}),
|
||||
blocks,
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const messageTimestamp = processMessageTimestamp(propsValue.ts);
|
||||
if (!messageTimestamp) {
|
||||
throw new Error('Invalid Timestamp Value.');
|
||||
}
|
||||
const client = new WebClient(auth.access_token);
|
||||
|
||||
|
||||
const blockList = propsValue.blocks ?[{ type: 'section', text: { type: 'mrkdwn', text:propsValue.text } }, ...(propsValue.blocks as unknown as (KnownBlock | Block)[])] :undefined
|
||||
|
||||
return await client.chat.update({
|
||||
channel: propsValue.channel,
|
||||
ts: messageTimestamp,
|
||||
text: propsValue.text,
|
||||
blocks: blockList,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { slackAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
|
||||
export const updateProfileAction = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'slack-update-profile',
|
||||
displayName: 'Update Profile',
|
||||
description: 'Update basic profile field such as name or title.',
|
||||
props: {
|
||||
firstName: Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: false,
|
||||
}),
|
||||
lastName: Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: false,
|
||||
}),
|
||||
email: Property.ShortText({
|
||||
displayName: 'Email',
|
||||
description: `Changing a user's email address will send an email to both the old and new addresses, and also post a slackbot message to the user informing them of the change.`,
|
||||
required: false,
|
||||
}),
|
||||
userId: Property.ShortText({
|
||||
displayName: 'User',
|
||||
description:
|
||||
'ID of user to change. This argument may only be specified by admins on paid teams.You can use **Find User by Email** action to retrieve ID.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const client = new WebClient(auth.data['authed_user']?.access_token);
|
||||
return client.users.profile.set({
|
||||
profile: {
|
||||
first_name: propsValue.firstName,
|
||||
last_name: propsValue.lastName,
|
||||
email: propsValue.email,
|
||||
},
|
||||
user: propsValue.userId,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { slackAuth } from '../../index';
|
||||
import { WebClient } from '@slack/web-api';
|
||||
import {
|
||||
slackChannel,
|
||||
} from '../common/props';
|
||||
|
||||
export const uploadFile = createAction({
|
||||
auth: slackAuth,
|
||||
name: 'uploadFile',
|
||||
displayName: 'Upload file',
|
||||
description: 'Upload file without sharing it to a channel or user',
|
||||
props: {
|
||||
file: Property.File({
|
||||
displayName: 'Attachment',
|
||||
required: true,
|
||||
}),
|
||||
title: Property.ShortText({
|
||||
displayName: 'Title',
|
||||
required: false,
|
||||
}),
|
||||
filename: Property.ShortText({
|
||||
displayName: 'Filename',
|
||||
required: false,
|
||||
}),
|
||||
channel: slackChannel(false),
|
||||
},
|
||||
async run(context) {
|
||||
const token = context.auth.access_token;
|
||||
const { file, title, filename, channel } = context.propsValue;
|
||||
const client = new WebClient(token);
|
||||
return await client.files.uploadV2({
|
||||
file_uploads: [{ file: file.data, filename: filename || file.filename }],
|
||||
title: title,
|
||||
channel_id: channel,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
|
||||
import { UsersListResponse, WebClient } from '@slack/web-api';
|
||||
import {slackAuth} from '../../index';
|
||||
const slackChannelBotInstruction = `
|
||||
Please make sure add the bot to the channel by following these steps:
|
||||
1. Type /invite in the channel's chat.
|
||||
2. Click on Add apps to this channel.
|
||||
3. Search for and add the bot.
|
||||
`;
|
||||
|
||||
export const multiSelectChannelInfo = Property.MarkDown({
|
||||
value:
|
||||
slackChannelBotInstruction +
|
||||
`\n**Note**: If you can't find the channel in the dropdown list (which fetches up to 2000 channels), please click on the **(F)** and type the channel ID directly in an array like this: \`{\`{ ['your_channel_id_1', 'your_channel_id_2', ...] \`}\`}`,
|
||||
});
|
||||
|
||||
export const singleSelectChannelInfo = Property.MarkDown({
|
||||
value:
|
||||
slackChannelBotInstruction +
|
||||
`\n**Note**: If you can't find the channel in the dropdown list (which fetches up to 2000 channels), please click on the **(F)** and type the channel ID directly.
|
||||
`,
|
||||
});
|
||||
|
||||
export const slackChannel = <R extends boolean>(required: R) =>
|
||||
Property.Dropdown<string, R,typeof slackAuth>({
|
||||
auth: slackAuth,
|
||||
displayName: 'Channel',
|
||||
description:
|
||||
"You can get the Channel ID by right-clicking on the channel and selecting 'View Channel Details.'",
|
||||
required,
|
||||
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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const username = Property.ShortText({
|
||||
displayName: 'Username',
|
||||
description: 'The username of the bot',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const profilePicture = Property.ShortText({
|
||||
displayName: 'Profile Picture',
|
||||
description: 'The profile picture of the bot',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const threadTs = Property.ShortText({
|
||||
displayName: 'Reply to Thread (Thread Message Link/Timestamp)',
|
||||
description:
|
||||
'Provide the ts (timestamp) or link value of the **parent** message to make this message a reply. Do not use the ts value of the reply itself; use its parent instead. For example `1710304378.475129`.Alternatively, you can easily obtain the message link by clicking on the three dots next to the parent message and selecting the `Copy link` option.',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const mentionOriginFlow = Property.Checkbox({
|
||||
displayName: 'Mention flow of origin?',
|
||||
description:
|
||||
'If checked, adds a mention at the end of the Slack message to indicate which flow sent the notification, with a link to said flow.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
export const blocks = Property.Json({
|
||||
displayName: 'Block Kit blocks',
|
||||
description: 'See https://api.slack.com/block-kit for specs',
|
||||
required: false,
|
||||
defaultValue: []
|
||||
});
|
||||
|
||||
export const userId = Property.Dropdown<string,true,typeof slackAuth>({
|
||||
auth: slackAuth,
|
||||
displayName: 'User',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
async options({ auth }) {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'connect slack account',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const accessToken = (auth as OAuth2PropertyValue).access_token;
|
||||
|
||||
const client = new WebClient(accessToken);
|
||||
const users: { label: string; value: string }[] = [];
|
||||
for await (const page of client.paginate('users.list', {
|
||||
limit: 1000, // Only limits page size, not total number of results
|
||||
})) {
|
||||
const response = page as UsersListResponse;
|
||||
if (response.members) {
|
||||
users.push(
|
||||
...response.members
|
||||
.filter((member) => !member.deleted)
|
||||
.map((member) => {
|
||||
return { label: member.name || '', value: member.id || '' };
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
disabled: false,
|
||||
placeholder: 'Select User',
|
||||
options: users,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const text = Property.LongText({
|
||||
displayName: 'Message',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const actions = Property.Array({
|
||||
displayName: 'Action Buttons',
|
||||
required: true,
|
||||
properties: {
|
||||
label: Property.ShortText({
|
||||
displayName: 'Label',
|
||||
required: true,
|
||||
}),
|
||||
style: Property.StaticDropdown({
|
||||
displayName: 'Style',
|
||||
required: false,
|
||||
defaultValue: null,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Default', value: null },
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Danger', value: 'danger' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export async function getChannels(accessToken: string) {
|
||||
const client = new WebClient(accessToken);
|
||||
const channels: { label: string; value: string }[] = [];
|
||||
const CHANNELS_LIMIT = 2000;
|
||||
|
||||
let cursor;
|
||||
do {
|
||||
const response = await client.conversations.list({
|
||||
types: 'public_channel,private_channel',
|
||||
exclude_archived: true,
|
||||
limit: 1000,
|
||||
cursor,
|
||||
});
|
||||
|
||||
if (response.channels) {
|
||||
channels.push(
|
||||
...response.channels.map((channel) => {
|
||||
return { label: channel.name || '', value: channel.id || '' };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
cursor = response.response_metadata?.next_cursor;
|
||||
} while (cursor && channels.length < CHANNELS_LIMIT);
|
||||
|
||||
return channels;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { processMessageTimestamp, slackSendMessage } from './utils';
|
||||
import {
|
||||
assertNotNullOrUndefined,
|
||||
ExecutionType,
|
||||
PauseType,
|
||||
} from '@activepieces/shared';
|
||||
import { ChatPostMessageResponse } from '@slack/web-api';
|
||||
|
||||
export const requestAction = async (conversationId: string, context: any) => {
|
||||
const { actions } = context.propsValue;
|
||||
assertNotNullOrUndefined(actions, 'actions');
|
||||
|
||||
if (!actions.length) {
|
||||
throw new Error(`Must have at least one button action`);
|
||||
}
|
||||
|
||||
const actionTextToIds = actions.map(
|
||||
({ label, style }: { label: string; style: string }) => {
|
||||
if (!label) {
|
||||
throw new Error(`Button text for the action cannot be empty`);
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
style,
|
||||
actionId: encodeURI(label as string),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
if (context.executionType === ExecutionType.BEGIN) {
|
||||
context.run.pause({
|
||||
pauseMetadata: {
|
||||
type: PauseType.WEBHOOK,
|
||||
actions: actionTextToIds.map(
|
||||
(action: { actionId: string }) => action.actionId
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const token = context.auth.access_token;
|
||||
const { text, username, profilePicture } = context.propsValue;
|
||||
|
||||
assertNotNullOrUndefined(token, 'token');
|
||||
assertNotNullOrUndefined(text, 'text');
|
||||
|
||||
const actionElements = actionTextToIds.map(
|
||||
(action: { label: string; style: string; actionId: string }) => {
|
||||
const actionLink = context.generateResumeUrl({
|
||||
queryParams: { action: action.actionId },
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: action.label,
|
||||
},
|
||||
...(action.style && {style: action.style}),
|
||||
value: actionLink,
|
||||
action_id: action.actionId,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const messageResponse: ChatPostMessageResponse = await slackSendMessage({
|
||||
token,
|
||||
text: `${context.propsValue.text}`,
|
||||
username,
|
||||
profilePicture,
|
||||
threadTs: context.propsValue.threadTs
|
||||
? processMessageTimestamp(context.propsValue.threadTs)
|
||||
: undefined,
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${context.propsValue.text}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
block_id: 'actions',
|
||||
elements: actionElements,
|
||||
},
|
||||
],
|
||||
conversationId: conversationId,
|
||||
});
|
||||
|
||||
return {
|
||||
action: actionTextToIds.at(0) || 'N/A',
|
||||
payload: {
|
||||
type: 'action_blocks',
|
||||
user: {
|
||||
id: messageResponse.message?.user,
|
||||
username: 'user.name',
|
||||
name: 'john.smith',
|
||||
team_id: messageResponse.message?.team,
|
||||
},
|
||||
container: {
|
||||
type: 'message',
|
||||
message_ts: messageResponse.ts,
|
||||
channel_id: messageResponse.channel,
|
||||
is_ephemeral: false,
|
||||
},
|
||||
trigger_id: 'trigger_id',
|
||||
team: {
|
||||
id: messageResponse.message?.team,
|
||||
domain: 'team_name',
|
||||
},
|
||||
channel: {
|
||||
id: messageResponse.channel,
|
||||
name: '#channel',
|
||||
},
|
||||
message: messageResponse.message,
|
||||
state: {},
|
||||
actions: [
|
||||
{
|
||||
action_id: 'action_id',
|
||||
block_id: 'actions',
|
||||
value: 'resume_url',
|
||||
style: 'primary',
|
||||
type: 'button',
|
||||
action_ts: 'action_ts',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const payloadQueryParams = context.resumePayload.queryParams as {
|
||||
action: string;
|
||||
};
|
||||
|
||||
return {
|
||||
action: decodeURI(payloadQueryParams.action),
|
||||
payload: context.resumePayload.body,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import { ApFile } from '@activepieces/pieces-framework';
|
||||
import { Block, WebClient } from '@slack/web-api';
|
||||
|
||||
export const slackSendMessage = async ({
|
||||
text,
|
||||
conversationId,
|
||||
username,
|
||||
profilePicture,
|
||||
blocks,
|
||||
threadTs,
|
||||
token,
|
||||
file,
|
||||
replyBroadcast,
|
||||
unfurlLinks,
|
||||
}: SlackSendMessageParams) => {
|
||||
const client = new WebClient(token);
|
||||
|
||||
if (file) {
|
||||
return await client.files.uploadV2({
|
||||
channel_id: conversationId,
|
||||
initial_comment: text,
|
||||
thread_ts: threadTs,
|
||||
file_uploads: [
|
||||
{
|
||||
file: file.data,
|
||||
filename: file.filename,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
const messageParams: any = {
|
||||
text,
|
||||
channel: conversationId,
|
||||
username,
|
||||
icon_url: profilePicture,
|
||||
blocks: blocks as Block[],
|
||||
thread_ts: threadTs,
|
||||
};
|
||||
|
||||
if (replyBroadcast) {
|
||||
messageParams.reply_broadcast = replyBroadcast;
|
||||
}
|
||||
|
||||
if (unfurlLinks === false) {
|
||||
messageParams.unfurl_links = false;
|
||||
}
|
||||
|
||||
return await client.chat.postMessage(messageParams);
|
||||
}
|
||||
};
|
||||
|
||||
type SlackSendMessageParams = {
|
||||
token: string;
|
||||
conversationId: string;
|
||||
username?: string;
|
||||
profilePicture?: string;
|
||||
blocks?: unknown[] | Record<string, any>;
|
||||
text?: string;
|
||||
file?: ApFile;
|
||||
threadTs?: string;
|
||||
replyBroadcast?: boolean;
|
||||
unfurlLinks?: boolean;
|
||||
};
|
||||
|
||||
export function processMessageTimestamp(input: string) {
|
||||
// Regular expression to match a URL containing the timestamp
|
||||
const urlRegex = /\/p(\d+)(\d{6})$/;
|
||||
// Check if the input is a URL
|
||||
const urlMatch = input.match(urlRegex);
|
||||
if (urlMatch) {
|
||||
const timestamp = `${urlMatch[1]}.${urlMatch[2]}`;
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
// Check if the input is already in the desired format
|
||||
const timestampRegex = /^(\d+)\.(\d{6})$/;
|
||||
const timestampMatch = input.match(timestampRegex);
|
||||
if (timestampMatch) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getFirstFiveOrAll(array: unknown[]) {
|
||||
if (array.length <= 5) {
|
||||
return array;
|
||||
} else {
|
||||
return array.slice(0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a message text to extract command and arguments
|
||||
*/
|
||||
export function parseCommand(
|
||||
text: string,
|
||||
botUserId: string,
|
||||
validCommands: string[]
|
||||
): { command: string; args: string[] } | null {
|
||||
if (!botUserId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the message mentions the bot
|
||||
const mentionRegex = new RegExp(`<@${botUserId}>\\s+(.+)`, 's');
|
||||
const mentionMatch = text.match(mentionRegex);
|
||||
|
||||
if (!mentionMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the text after the mention
|
||||
const commandText = mentionMatch[1].trim();
|
||||
|
||||
// Split into command and arguments (first word is command, rest are args)
|
||||
const parts = commandText.split(/\s+/);
|
||||
const command = parts[0].toLowerCase();
|
||||
const args = parts.slice(1);
|
||||
|
||||
// Check if it's a valid command
|
||||
if (!validCommands.includes(command)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
command,
|
||||
args,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user