Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e68de1f
Update Slack actions to support Slack v2 OAuth
js07 Oct 13, 2025
2f2719a
pass through as_user unless false and a bot token is available
js07 Oct 13, 2025
e5597ae
refactor: simplify special case handling of API error
js07 Oct 13, 2025
3aa0773
make addToChannel required for Send Message actions
js07 Oct 13, 2025
2126d8e
don't throw error when adding app to channels fails
js07 Oct 13, 2025
8eb39ec
delete New Direct Message trigger
js07 Oct 13, 2025
ce6dba8
delete the Verify Slack Signature action
js07 Oct 13, 2025
8b99a4e
Use assistant search API in Find Message action
js07 Oct 13, 2025
2476e2c
Support configuring base URL for Slack API requests
js07 Oct 13, 2025
074b23e
update pnpm-lock.yaml
js07 Oct 13, 2025
ccc2353
minor refactor of bot token usage
js07 Oct 13, 2025
8717ff3
Bump version numbers for Slack components to reflect recent updates
js07 Oct 13, 2025
325a164
bump Slack app minor version
js07 Oct 13, 2025
18a1e8f
Merge branch 'master' into slack-v2-support
js07 Oct 14, 2025
2df5de6
address review feedback
js07 Oct 16, 2025
42e6bb2
create Slack v2 app and components from Slack v1
js07 Oct 19, 2025
2ae0dcb
Merge branch 'master' into slack-v2-support
js07 Oct 19, 2025
fc32b4b
revert changes to the Slack app and components
js07 Oct 19, 2025
5dbafa7
revert formatting changes to unrelated app files
js07 Oct 19, 2025
135030d
update slack v2 component keys from slack- to slack_v2-
js07 Oct 19, 2025
cfeb6ed
add Get Current User action
js07 Oct 19, 2025
97ed7f6
revert fallback and message normalization in Find Message action
js07 Oct 19, 2025
f417859
update pnpm-lock.yaml
js07 Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import slack from "../../slack.app.mjs";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix incorrect import path.

This file imports from "../../slack.app.mjs" while all other Slack v2 actions import from "../../slack_v2.app.mjs". This inconsistency will likely cause a module resolution error since this is a Slack v2 component.

Apply this diff:

-import slack from "../../slack.app.mjs";
+import slack from "../../slack_v2.app.mjs";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import slack from "../../slack.app.mjs";
import slack from "../../slack_v2.app.mjs";
🤖 Prompt for AI Agents
In components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs around
line 1, the import path uses "../../slack.app.mjs" which is inconsistent with
other Slack v2 actions; update the import to point to "../../slack_v2.app.mjs"
so the module resolves correctly and matches the v2 component convention.


export default {
key: "slack_v2-add-emoji-reaction",
name: "Add Emoji Reaction",
description: "Add an emoji reaction to a message. [See the documentation](https://api.slack.com/methods/reactions.add)",
version: "0.0.17",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
type: "action",
props: {
slack,
conversation: {
propDefinition: [
slack,
"conversation",
],
description: "Channel where the message to add reaction to was posted.",
},
timestamp: {
propDefinition: [
slack,
"messageTs",
],
description: "Timestamp of the message to add reaction to. e.g. `1403051575.000407`.",
},
icon_emoji: {
propDefinition: [
slack,
"icon_emoji",
],
description: "Provide an emoji to use as the icon for this reaction. E.g. `fire`",
optional: false,
},
},
async run({ $ }) {
const response = await this.slack.addReactions({
channel: this.conversation,
timestamp: this.timestamp,
name: this.icon_emoji,
});
$.export("$summary", `Successfully added ${this.icon_emoji} emoji reaction.`);
return response;
},
};
92 changes: 92 additions & 0 deletions components/slack_v2/actions/approve-workflow/approve-workflow.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import slack from "../../slack_v2.app.mjs";
import constants from "../../common/constants.mjs";

export default {
key: "slack_v2-approve-workflow",
name: "Approve Workflow",
description: "Suspend the workflow until approved by a Slack message. [See the documentation](https://pipedream.com/docs/code/nodejs/rerun#flowsuspend)",
version: "0.0.6",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
type: "action",
props: {
slack,
channelType: {
type: "string",
label: "Channel Type",
description: "The type of channel to send to. User/Direct Message (im), Group (mpim), Private Channel or Public Channel",
async options() {
return constants.CHANNEL_TYPE_OPTIONS;
},
},
conversation: {
propDefinition: [
slack,
"conversation",
(c) => ({
types: c.channelType === "Channels"
? [
constants.CHANNEL_TYPE.PUBLIC,
constants.CHANNEL_TYPE.PRIVATE,
]
: [
c.channelType,
],
}),
],
},
message: {
type: "string",
label: "Message",
description: "Text to include with the Approve and Cancel Buttons",
},
},
async run({ $ }) {
const {
resume_url, cancel_url,
} = $.flow.suspend();

const response = await this.slack.postChatMessage({
text: "Click here to approve or cancel workflow",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: this.message,
},
},
{
type: "actions",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: "Approve",
},
style: "primary",
url: resume_url,
},
{
type: "button",
text: {
type: "plain_text",
text: "Cancel",
},
style: "danger",
url: cancel_url,
},
],
},
],
channel: this.conversation,
});

$.export("$summary", "Successfully sent message");
return response;
},
};
38 changes: 38 additions & 0 deletions components/slack_v2/actions/archive-channel/archive-channel.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import slack from "../../slack_v2.app.mjs";
import constants from "../../common/constants.mjs";

export default {
key: "slack_v2-archive-channel",
name: "Archive Channel",
description: "Archive a channel. [See the documentation](https://api.slack.com/methods/conversations.archive)",
version: "0.0.25",
annotations: {
destructiveHint: true,
openWorldHint: true,
readOnlyHint: false,
},
type: "action",
props: {
slack,
conversation: {
propDefinition: [
slack,
"conversation",
() => ({
types: [
constants.CHANNEL_TYPE.PUBLIC,
constants.CHANNEL_TYPE.PRIVATE,
constants.CHANNEL_TYPE.MPIM,
],
}),
],
},
},
async run({ $ }) {
const response = await this.slack.archiveConversations({
channel: this.conversation,
});
$.export("$summary", "Successfully archived channel.");
return response;
},
};
182 changes: 182 additions & 0 deletions components/slack_v2/actions/common/build-blocks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import common from "./send-message.mjs";

export default {
props: {
passArrayOrConfigure: {
type: "string",
label: "Add Blocks - Reference Existing Blocks Array or Configure Manually?",
description: "Would you like to reference an array of blocks from a previous step (for example, `{{steps.blocks.$return_value}}`), or configure them in this action?",
options: [
{
label: "Reference an array of blocks",
value: "array",
},
{
label: "Configure blocks individually (maximum 5 blocks)",
value: "configure",
},
],
optional: true,
reloadProps: true,
},
},
methods: {
// This adds a visual separator in the props form between each block
separator() {
return `
---
`;
},
createBlockProp(type, label, description) {
return {
type,
label,
description: `${description} ${this.separator()}`,
};
},
createBlock(type, text) {
if (type === "section") {
return {
type: "section",
text: {
type: "mrkdwn",
text,
},
};
} else if (type === "context") {
const elements = Array.isArray(text)
? text.map((t) => ({
type: "mrkdwn",
text: t,
}))
: [
{
type: "mrkdwn",
text,
},
];
return {
type: "context",
elements,
};
} else if (type === "link_button") {
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36)
.substr(2, 9)}`, // Generates a random action_id
}));

Comment on lines +65 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate link_button input shape and guard keys.

Object.keys(text) will throw if text isn’t a plain object; also clarify error early.

-      } else if (type === "link_button") {
-        const buttons = Object.keys(text).map((buttonText) => ({
+      } else if (type === "link_button") {
+        if (!text || typeof text !== "object" || Array.isArray(text)) {
+          throw new Error("Link Button expects an object mapping button text → URL");
+        }
+        const buttons = Object.keys(text).map((buttonText) => ({
           type: "button",
@@
-          action_id: `actionId-${Math.random().toString(36)
-            .substr(2, 9)}`,  // Generates a random action_id
+          action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`,  // Generates a random action_id
         }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36)
.substr(2, 9)}`, // Generates a random action_id
}));
if (!text || typeof text !== "object" || Array.isArray(text)) {
throw new Error("Link Button expects an object mapping button text → URL");
}
const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
text: {
type: "plain_text",
text: buttonText,
emoji: true,
},
url: text[buttonText], // Access the URL using buttonText as the key
action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id
}));
🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 65 to 76,
the code uses Object.keys(text) without validating that text is a non-null plain
object which will throw for non-objects; update to first guard the input (if
typeof text !== "object" || text === null || Array.isArray(text) then throw a
clear TypeError or return an empty array), then iterate using
Object.entries(text) and filter to only own string key/value pairs (e.g. filter
([k,v]) => typeof k === "string" && typeof v === "string") before mapping to
buttons, and keep generating action_id safely (use slice instead of substr if
desired).

return {
type: "actions",
elements: buttons,
};
}
},
},
async additionalProps(existingProps) {
await common.additionalProps.call(this, existingProps);
const props = {};
const sectionDescription = "Add a **section** block to your message and configure with plain text or mrkdwn. See [Slack's docs](https://api.slack.com/reference/block-kit/blocks?ref=bk#section) for more info.";
const contextDescription = "Add a **context** block to your message and configure with plain text or mrkdwn. Define multiple items if you'd like multiple elements in the context block. See [Slack's docs](https://api.slack.com/reference/block-kit/blocks?ref=bk#context) for more info.";
const linkButtonDescription = "Add a **link button** to your message. Enter the button text as the key and the link URL as the value. Configure multiple buttons in the array to render them inline, or add additional Button Link blocks to render them vertically. See [Slack's docs](https://api.slack.com/reference/block-kit/blocks?ref=bk#actions) for more info.";
const propsSection = this.createBlockProp("string", "Section Block Text", sectionDescription);
const propsContext = this.createBlockProp("string[]", "Context Block Text", contextDescription);
const propsLinkButton = this.createBlockProp("object", "Link Button", linkButtonDescription);

if (!this.passArrayOrConfigure) {
return props;
}
if (this.passArrayOrConfigure == "array") {
props.blocks = {
type: common.props.slack.propDefinitions.blocks.type,
label: common.props.slack.propDefinitions.blocks.label,
description: common.props.slack.propDefinitions.blocks.description,
};
} else {
Comment on lines +97 to +103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use strict equality for mode checks.

Avoid coercion. Switch to === in both places.

-    if (this.passArrayOrConfigure == "array") {
+    if (this.passArrayOrConfigure === "array") {
       props.blocks = {
@@
-    if (this.passArrayOrConfigure == "array") {
-      blocks = this.blocks;
+    if (this.passArrayOrConfigure === "array") {
+      blocks = this.blocks;
     } else {

Also applies to: 160-164

🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 97-103 and
also at 160-164, the code uses loose equality (==) for mode checks which can
cause unintended type coercion; change those comparisons to strict equality
(===) in both locations so the checks compare both value and type (e.g., replace
`== "array"` with `=== "array"` and the corresponding else-if/other check
similarly), run tests/lint to ensure no further occurrences remain.

props.blockType = {
type: "string",
label: "Block Type",
description: "Select the type of block to add. Refer to [Slack's docs](https://api.slack.com/reference/block-kit/blocks) for more info.",
options: [
{
label: "Section",
value: "section",
},
{
label: "Context",
value: "context",
},
{
label: "Link Button",
value: "link_button",
},
],
reloadProps: true,
};}
let currentBlockType = this.blockType;
for (let i = 1; i <= 5; i++) {
if (currentBlockType === "section") {
props[`section${i}`] = propsSection;
} else if (currentBlockType === "context") {
props[`context${i}`] = propsContext;
} else if (currentBlockType === "link_button") {
props[`linkButton${i}`] = propsLinkButton;
}

if (i < 5 && currentBlockType) { // Check if currentBlockType is set before adding nextBlockType
props[`nextBlockType${i}`] = {
type: "string",
label: "Next Block Type",
options: [
{
label: "Section",
value: "section",
},
{
label: "Context",
value: "context",
},
{
label: "Link Button",
value: "link_button",
},
],
optional: true,
reloadProps: true,
};
currentBlockType = this[`nextBlockType${i}`];
}
}
return props;
},
async run() {
let blocks = [];
if (this.passArrayOrConfigure == "array") {
blocks = this.blocks;
} else {
for (let i = 1; i <= 5; i++) {
if (this[`section${i}`]) {
blocks.push(this.createBlock("section", this[`section${i}`]));
}

if (this[`context${i}`]) {
blocks.push(this.createBlock("context", this[`context${i}`]));
}

if (this[`linkButton${i}`]) {
blocks.push(this.createBlock("link_button", this[`linkButton${i}`]));
}
}
}
return blocks;
},
};

Loading
Loading