From e68de1f64d947c061a347206446ea1b06c007621 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 01/27] Update Slack actions to support Slack v2 OAuth - List users by real name in dropdowns - List users by real name in action summary - List files using bot token if available - Get files using bot token if available - Show configuration error if bot is not a member of the channel - Add `addToChannel` prop to Send Message actions - Check user's access to channel in message actions - Use RTS API to search messages in Find Message action - List only channels, not DMs, in 'file' actions - List only channels, not DMs, in 'message' triggers - Handle permission error fetching message in New Reaction trigger Context: In Slack's v2 OAuth flow, user access tokens and bot access tokens are issued with separate scopes. User tokens are used to perform actions on behalf of a user, while bot tokens are used for actions performed by the app itself. To send a message to a private channel or DM, the bot must be a member of that channel. Due to permission restrictions, some endpoints require bot tokens for access, such as viewing messages in DMs and listing files. As a result, access to messages and files in DMs is no longer supported. --- .../slack/actions/common/send-message.mjs | 27 +-- .../actions/find-message/find-message.mjs | 53 ++++-- .../slack/actions/get-file/get-file.mjs | 20 +++ .../slack/actions/list-files/list-files.mjs | 20 +++ .../actions/list-replies/list-replies.mjs | 8 + .../send-large-message/send-large-message.mjs | 17 +- .../send-message-to-user-or-group.mjs | 7 + .../update-group-members.mjs | 4 +- components/slack/slack.app.mjs | 169 ++++++++++++++++-- .../new-keyword-mention.mjs | 44 +---- .../new-message-in-channels.mjs | 7 + .../new-reaction-added/new-reaction-added.mjs | 12 +- .../new-user-mention/new-user-mention.mjs | 7 + 13 files changed, 297 insertions(+), 98 deletions(-) diff --git a/components/slack/actions/common/send-message.mjs b/components/slack/actions/common/send-message.mjs index ff923367dbdbb..4fa0a8e0d9c36 100644 --- a/components/slack/actions/common/send-message.mjs +++ b/components/slack/actions/common/send-message.mjs @@ -9,6 +9,13 @@ export default { "as_user", ], }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", + ], + optional: true, + }, post_at: { propDefinition: [ slack, @@ -181,6 +188,14 @@ export default { }, }, async run({ $ }) { + const channelId = await this.getChannelId(); + + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + channelId, + ]); + } + let blocks = this.blocks; if (!blocks) { @@ -216,7 +231,7 @@ export default { const obj = { text: this.text, - channel: await this.getChannelId(), + channel: channelId, attachments: this.attachments, unfurl_links: this.unfurl_links, unfurl_media: this.unfurl_media, @@ -241,15 +256,7 @@ export default { const { channel } = await this.slack.conversationsInfo({ channel: resp.channel, }); - let channelName = `#${channel?.name}`; - if (channel.is_im) { - const { profile } = await this.slack.getUserProfile({ - user: channel.user, - }); - channelName = `@${profile.real_name}`; - } else if (channel.is_mpim) { - channelName = `@${channel.purpose.value}`; - } + const channelName = await this.slack.getChannelDisplayName(channel); $.export("$summary", `Successfully sent a message to ${channelName}`); return resp; }, diff --git a/components/slack/actions/find-message/find-message.mjs b/components/slack/actions/find-message/find-message.mjs index 85d5bb317c9b2..0633261f6a7bf 100644 --- a/components/slack/actions/find-message/find-message.mjs +++ b/components/slack/actions/find-message/find-message.mjs @@ -1,9 +1,10 @@ +import { axios } from "@pipedream/platform"; import slack from "../../slack.app.mjs"; export default { key: "slack-find-message", name: "Find Message", - description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/search.messages)", + description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)", version: "0.0.26", annotations: { destructiveHint: false, @@ -19,18 +20,11 @@ export default { "query", ], }, - teamId: { - propDefinition: [ - slack, - "team", - ], - optional: true, - }, maxResults: { type: "integer", label: "Max Results", description: "The maximum number of messages to return", - default: 100, + default: 20, optional: true, }, sort: { @@ -54,26 +48,51 @@ export default { optional: true, }, }, + methods: { + async searchMessages($, params) { + const data = await axios($, { + method: "POST", + url: "https://slack.com/api/assistant.search.context", + data: { + query: params.query, + sort: params.sort, + sort_dir: params.sort_dir, + cursor: params.cursor, + channel_types: params.channel_types, + }, + headers: { + "Authorization": `Bearer ${this.slack.getToken()}`, + "Content-Type": "application/json", + }, + }); + if (!data.ok) { + throw new Error(data.error || "An error occurred while searching messages"); + } + return data; + }, + }, async run({ $ }) { const matches = []; const params = { query: this.query, - team_id: this.teamId, sort: this.sort, sort_dir: this.sortDirection, - page: 1, + channel_types: "public_channel,private_channel", }; - let hasMore; + let cursor; do { - const { messages } = await this.slack.searchMessages(params); - matches.push(...messages.matches); + if (cursor) { + params.cursor = cursor; + } + const response = await this.searchMessages($, params); + const messages = response.results?.messages || []; + matches.push(...messages); if (matches.length >= this.maxResults) { break; } - hasMore = messages.matches?.length; - params.page++; - } while (hasMore); + cursor = response.response_metadata?.next_cursor; + } while (cursor); if (matches.length > this.maxResults) { matches.length = this.maxResults; diff --git a/components/slack/actions/get-file/get-file.mjs b/components/slack/actions/get-file/get-file.mjs index 9fc1afaa35b6a..0e47812aa6848 100644 --- a/components/slack/actions/get-file/get-file.mjs +++ b/components/slack/actions/get-file/get-file.mjs @@ -1,3 +1,4 @@ +import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { @@ -17,6 +18,19 @@ export default { propDefinition: [ slack, "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", ], }, file: { @@ -30,6 +44,12 @@ export default { }, }, async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + const response = await this.slack.getFileInfo({ file: this.file, }); diff --git a/components/slack/actions/list-files/list-files.mjs b/components/slack/actions/list-files/list-files.mjs index 72881dd96edfb..7b4abe72a79c7 100644 --- a/components/slack/actions/list-files/list-files.mjs +++ b/components/slack/actions/list-files/list-files.mjs @@ -1,3 +1,4 @@ +import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { @@ -17,6 +18,19 @@ export default { propDefinition: [ slack, "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", ], }, team_id: { @@ -47,6 +61,12 @@ export default { }, }, async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + const allFiles = []; const params = { channel: this.conversation, diff --git a/components/slack/actions/list-replies/list-replies.mjs b/components/slack/actions/list-replies/list-replies.mjs index 28cc4f750a0c6..c36e5c45833f1 100644 --- a/components/slack/actions/list-replies/list-replies.mjs +++ b/components/slack/actions/list-replies/list-replies.mjs @@ -1,3 +1,4 @@ +import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { @@ -17,7 +18,14 @@ export default { propDefinition: [ slack, "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), ], + description: "Select a public or private channel", }, timestamp: { propDefinition: [ diff --git a/components/slack/actions/send-large-message/send-large-message.mjs b/components/slack/actions/send-large-message/send-large-message.mjs index 9d0ee386a6dd5..59dac250101dd 100644 --- a/components/slack/actions/send-large-message/send-large-message.mjs +++ b/components/slack/actions/send-large-message/send-large-message.mjs @@ -35,6 +35,12 @@ export default { ...common.props, }, async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + if (this.include_sent_via_pipedream_flag) { const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`; @@ -79,19 +85,10 @@ export default { } else { response = await this.slack.postChatMessage(obj); } - const { channel } = await this.slack.conversationsInfo({ channel: response.channel, }); - let channelName = `#${channel?.name}`; - if (channel.is_im) { - const { profile } = await this.slack.getUserProfile({ - user: channel.user, - }); - channelName = `@${profile.real_name}`; - } else if (channel.is_mpim) { - channelName = `@${channel.purpose.value}`; - } + const channelName = await this.slack.getChannelDisplayName(channel); $.export("$summary", `Successfully sent a message to ${channelName}`); return response; }, diff --git a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs index ecbf1965e6077..f33e456e8726c 100644 --- a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +++ b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -52,6 +52,13 @@ export default { ], }, ...common.props, + // eslint-disable-next-line pipedream/props-label, pipedream/props-description + addToChannel: { + type: "boolean", + ...common.props.addToChannel, + disabled: true, + hidden: true, + }, }, methods: { ...common.methods, diff --git a/components/slack/actions/update-group-members/update-group-members.mjs b/components/slack/actions/update-group-members/update-group-members.mjs index fdb927619b3ac..cb36d77b070d2 100644 --- a/components/slack/actions/update-group-members/update-group-members.mjs +++ b/components/slack/actions/update-group-members/update-group-members.mjs @@ -51,8 +51,8 @@ export default { async run({ $ }) { const { userGroup, - usersToAdd, - usersToRemove, + usersToAdd = [], + usersToRemove = [], team, } = this; let { users } = await this.slack.listGroupMembers({ diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index 401d853f0f6cb..f87408711f1ed 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -2,6 +2,7 @@ import { WebClient } from "@slack/web-api"; import constants from "./common/constants.mjs"; import get from "lodash/get.js"; import retry from "async-retry"; +import { ConfigurationError } from "@pipedream/platform"; export default { type: "app", @@ -27,11 +28,11 @@ export default { conversationsResp.conversations = conversationsResp.conversations .filter((c) => members.includes(c.user || c.id)); } - const userIds = conversationsResp.conversations.map(({ user }) => user); - const userNames = await this.userNameLookup(userIds); + const userIds = conversationsResp.conversations.map(({ user }) => user).filter(Boolean); + const realNames = await this.realNameLookup(userIds); return { - options: conversationsResp.conversations.map((c) => ({ - label: `@${userNames[c.user]}`, + options: conversationsResp.conversations.filter((c) => c.user).map((c) => ({ + label: `${realNames[c.user]}`, value: c.user || c.id, })), context: { @@ -120,24 +121,41 @@ export default { } } const conversationsResp = await this.availableConversations(types.join(), cursor, true); - let conversations, userNames; + let conversations, userIds, userNames, realNames; if (types.includes("im")) { conversations = conversationsResp.conversations; - const userIds = conversations.map(({ user }) => user); - userNames = await this.userNameLookup(userIds); + userIds = conversations.map(({ user }) => user).filter(Boolean); } else { conversations = conversationsResp.conversations.filter((c) => !c.is_im); } + if (types.includes("mpim")) { + userNames = [ + ...new Set(conversations.filter((c) => c.is_mpim).map((c) => c.purpose.value) + .map((v) => v.match(/@[\w.-]+/g) || []) + .flat() + .map((u) => u.slice(1))), + ]; + } + if ((userIds?.length > 0) || (userNames?.length > 0)) { + // Look up real names for userIds and userNames at the same time to + // minimize number of API calls. + realNames = await this.realNameLookup(userIds, userNames); + } + return { options: conversations.map((c) => { if (c.is_im) { return { - label: `Direct messaging with: @${userNames[c.user]}`, + label: `Direct messaging with: ${realNames[c.user]}`, value: c.id, }; } else if (c.is_mpim) { + const usernames = c.purpose.value.match(/@[\w.-]+/g) || []; + const realnames = usernames.map((u) => realNames[u.slice(1)] || u); return { - label: c.purpose.value, + label: realnames.length + ? `Group messaging with: ${realnames.join(", ")}` + : c.purpose.value, value: c.id, }; } else { @@ -277,6 +295,7 @@ export default { page: page + 1, count: constants.LIMIT, throwRateLimitError: true, + as_bot: true, }); return files?.map(({ id: value, name: label, @@ -447,6 +466,12 @@ export default { default: 1, optional: true, }, + addToChannel: { + type: "boolean", + label: "Add app to channel automatically?", + description: "If `true`, the app will be added to the specified non-DM channel(s) automatically. If `false`, you must add the app to the channel manually. Defaults to `true`.", + default: true, + }, }, methods: { getChannelLabel(resource) { @@ -464,29 +489,80 @@ export default { mySlackId() { return this.$auth.oauth_uid; }, - getToken() { - return this.$auth.oauth_access_token; + getToken(opts = {}) { + return (opts.as_bot && this.$auth.bot_token) + ? this.$auth.bot_token + : this.$auth.oauth_access_token; + }, + async getChannelDisplayName(channel) { + if (channel.user) { + try { + const { profile } = await this.getUserProfile({ + user: channel.user, + }); + return `@${profile.real_name || profile?.real_name}`; + } catch { + return "user"; + } + } else if (channel.is_mpim) { + try { + const { members } = await this.listChannelMembers({ + channel: channel.id, + }); + const users = await Promise.all(members.map((m) => this.getUserProfile({ + user: m, + }))); + const realNames = users.map((u) => u.profile?.real_name || u.real_name); + return `Group Messaging with: ${realNames.join(", ")}`; + } catch { + return `Group Messaging with: ${channel.purpose.value}`; + } + } + return `#${channel?.name}`; }, /** * Returns a Slack Web Client object authenticated with the user's access * token */ - sdk() { - return new WebClient(this.getToken(), { + sdk(opts = {}) { + return new WebClient(this.getToken(opts), { rejectRateLimitedCalls: true, }); }, async makeRequest({ - method = "", throwRateLimitError = false, ...args + method = "", throwRateLimitError = false, as_user, as_bot, ...args } = {}) { + as_bot = as_user === false || as_bot; + const props = method.split("."); const sdk = props.reduce((reduction, prop) => - reduction[prop], this.sdk()); + reduction[prop], this.sdk({ + as_bot, + })); - const response = await this._withRetries(() => sdk(args), throwRateLimitError); + let response; + try { + response = await this._withRetries(() => sdk(args), throwRateLimitError); + } catch (error) { + if ([ + "not_in_channel", + "channel_not_found", + ].includes(error?.data?.error) && as_bot) { + // If method starts with chat, include the part about "As User" + // Otherwise, just say "Ensure the bot is a member of the channel" + if (method.startsWith("chat.")) { + throw new ConfigurationError(`${error} + Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user. + `); + } + throw new ConfigurationError(`${error} + Ensure the bot is a member of the channel. + `); + } + throw `${error}`; + } if (!response.ok) { - console.log(`Error in response with method ${method}`, response.error); throw response.error; } return response; @@ -509,7 +585,7 @@ export default { throw error; } } - bail(`${error}`); + bail(error); } }, retryOpts); }, @@ -565,6 +641,61 @@ export default { } while (cursor && Object.keys(userNames).length < ids.length); return userNames; }, + async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { + let cursor; + const realNames = {}; + do { + const { + members: users, + response_metadata: { next_cursor: nextCursor }, + } = await this.usersList({ + limit: constants.LIMIT, + cursor, + throwRateLimitError, + ...args, + }); + + for (const user of users) { + if (ids.includes(user.id)) { + realNames[user.id] = user.profile.real_name; + } + if (usernames.includes(user.name)) { + realNames[user.name] = user.profile.real_name; + } + } + + cursor = nextCursor; + } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); + return realNames; + }, + async maybeAddAppToChannels(channelIds = []) { + if (!this.$auth.bot_token) return; + const { + bot_id, user_id, + } = await this.authTest({ + as_bot: true, + }); + if (!bot_id) { + throw new Error("Could not get bot ID. Make sure the Slack app has a bot user."); + } + // XXX: Trying to add the app to DM or group DM channels results in the + // error: method_not_supported_for_channel_type + for (const channel of channelIds) { + try { + await this.inviteToConversation({ + channel, + users: user_id, + }); + } catch (error) { + if (![ + "method_not_supported_for_channel_type", + "already_in_channel", + ].some((msg) => (error.data?.error || error.message || error)?.includes(msg))) { + throw error; + } + } + } + }, /** * Checks authentication & identity. * @param {*} args Arguments object @@ -801,6 +932,7 @@ export default { args.count ||= constants.LIMIT; return this.makeRequest({ method: "files.list", + as_bot: true, ...args, }); }, @@ -814,6 +946,7 @@ export default { getFileInfo(args = {}) { return this.makeRequest({ method: "files.info", + as_bot: true, ...args, }); }, diff --git a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs index 567abdb4806a8..fffe5e9be0f3b 100644 --- a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs +++ b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs @@ -1,6 +1,7 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; export default { ...common, @@ -16,6 +17,12 @@ export default { propDefinition: [ common.props.slack, "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), ], type: "string[]", label: "Channels", @@ -45,45 +52,8 @@ export default { ], }, }, - hooks: { - ...common.hooks, - async deploy() { - // emit historical events - const messages = await this.getMatches({ - query: this.keyword, - sort: "timestamp", - }); - const filteredMessages = this.conversations?.length > 0 - ? messages.filter((message) => this.conversations.includes(message.channel.id)) - : messages; - await this.emitHistoricalEvents(filteredMessages.slice(-25).reverse()); - }, - }, methods: { ...common.methods, - async getMatches(params) { - return (await this.slack.searchMessages(params)).messages.matches || []; - }, - async emitHistoricalEvents(messages) { - for (const message of messages) { - const event = await this.processEvent({ - ...message, - subtype: message.subtype || constants.SUBTYPE.PD_HISTORY_MESSAGE, - }); - if (event) { - if (!event.client_msg_id) { - event.pipedream_msg_id = `pd_${Date.now()}_${Math.random().toString(36) - .substr(2, 10)}`; - } - - this.$emit(event, { - id: event.client_msg_id || event.pipedream_msg_id, - summary: this.getSummary(event), - ts: event.event_ts || Date.now(), - }); - } - } - }, getSummary() { return "New keyword mention received"; }, diff --git a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs index 0de59c7a78749..e28866664d789 100644 --- a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs +++ b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs @@ -1,6 +1,7 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; export default { ...common, @@ -16,6 +17,12 @@ export default { propDefinition: [ common.props.slack, "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), ], type: "string[]", label: "Channels", diff --git a/components/slack/sources/new-reaction-added/new-reaction-added.mjs b/components/slack/sources/new-reaction-added/new-reaction-added.mjs index 6444e3fd9b57b..069113f447d90 100644 --- a/components/slack/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack/sources/new-reaction-added/new-reaction-added.mjs @@ -97,10 +97,14 @@ export default { event.itemUserInfo = itemUserResponse.user; } - event.message = await this.getMessage({ - channel: event.item.channel, - event_ts: event.item.ts, - }); + try { + event.message = await this.getMessage({ + channel: event.item.channel, + event_ts: event.item.ts, + }); + } catch (err) { + console.log("Error fetching message:", err); + } return event; }, diff --git a/components/slack/sources/new-user-mention/new-user-mention.mjs b/components/slack/sources/new-user-mention/new-user-mention.mjs index 5f4371d84f6dc..f40ca64363d64 100644 --- a/components/slack/sources/new-user-mention/new-user-mention.mjs +++ b/components/slack/sources/new-user-mention/new-user-mention.mjs @@ -1,6 +1,7 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; export default { ...common, @@ -16,6 +17,12 @@ export default { propDefinition: [ common.props.slack, "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), ], type: "string[]", label: "Channels", From 2f2719a8c0a07d6b61fb76d185f4380b8feea7e7 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 02/27] pass through as_user unless false and a bot token is available This allows us to support both existing and new Slack connections created with Slack's v2 OAuth flow, with or without the deprecated perspectival chat:write:user and chat:write:bot scopes: | Slack auth | Token Types | Scopes | as_user Values | |------------|-------------|-----------------------|-----------------| | v1 | user | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write | true | In v2, when the chat:write scope is requested, it replaces chat:write:bot and chat:write:user. Without the chat:write:bot scope, as_user cannot be false; user tokens always post as the user, and bot tokens always post as the bot. In v2, with or without the chat:write:bot scope, we can use the bot token if as_user is false since it will have permission to post as itself. And we MAY pass through as_user if it's true since Slack allows it even when it's superfluous. However, we MUST pass through as_user if it's true AND the user token still has the chat:write:bot scope since otherwise the message will post as the bot user instead of the user. See: https://docs.slack.dev/reference/methods/chat.postMessage/#legacy_as_user --- components/slack/slack.app.mjs | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index f87408711f1ed..a36b7bc429a60 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -295,7 +295,6 @@ export default { page: page + 1, count: constants.LIMIT, throwRateLimitError: true, - as_bot: true, }); return files?.map(({ id: value, name: label, @@ -490,7 +489,8 @@ export default { return this.$auth.oauth_uid; }, getToken(opts = {}) { - return (opts.as_bot && this.$auth.bot_token) + // Use bot token if asBot is true and available, otherwise use user token. + return (opts.asBot && this.$auth.bot_token) ? this.$auth.bot_token : this.$auth.oauth_access_token; }, @@ -530,14 +530,23 @@ export default { }); }, async makeRequest({ - method = "", throwRateLimitError = false, as_user, as_bot, ...args + method = "", throwRateLimitError = false, asBot = false, as_user, ...args } = {}) { - as_bot = as_user === false || as_bot; + const botTokenAvailable = Boolean(this.$auth.bot_token); + // Passing as_user as false with a v2 user token lacking the deprecated + // `chat:write:bot` scope, results in an error. So if as_user is false and + // there's a bot token available, we should use the bot token and omit + // as_user. Otherwise, use the user token and pass as_user through. + if (as_user === false && botTokenAvailable) { + asBot = true; + } else { + args.as_user = as_user; + } const props = method.split("."); const sdk = props.reduce((reduction, prop) => reduction[prop], this.sdk({ - as_bot, + asBot, })); let response; @@ -547,7 +556,7 @@ export default { if ([ "not_in_channel", "channel_not_found", - ].includes(error?.data?.error) && as_bot) { + ].includes(error?.data?.error) && asBot) { // If method starts with chat, include the part about "As User" // Otherwise, just say "Ensure the bot is a member of the channel" if (method.startsWith("chat.")) { @@ -673,7 +682,7 @@ export default { const { bot_id, user_id, } = await this.authTest({ - as_bot: true, + asBot: true, }); if (!bot_id) { throw new Error("Could not get bot ID. Make sure the Slack app has a bot user."); @@ -932,7 +941,9 @@ export default { args.count ||= constants.LIMIT; return this.makeRequest({ method: "files.list", - as_bot: true, + // Use bot token, if available, since the required `files:read` scope + // is only requested for bot tokens in the Pipedream app. + asBot: true, ...args, }); }, @@ -946,7 +957,9 @@ export default { getFileInfo(args = {}) { return this.makeRequest({ method: "files.info", - as_bot: true, + // Use bot token, if available, since the required `files:read` scope + // is only requested for bot tokens in the Pipedream app. + asBot: true, ...args, }); }, From e5597aed843b737f9f447b4003d520218e600e01 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 03/27] refactor: simplify special case handling of API error --- components/slack/slack.app.mjs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index a36b7bc429a60..fce4cca0aec77 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -530,13 +530,13 @@ export default { }); }, async makeRequest({ - method = "", throwRateLimitError = false, asBot = false, as_user, ...args + method = "", throwRateLimitError = false, asBot, as_user, ...args } = {}) { const botTokenAvailable = Boolean(this.$auth.bot_token); // Passing as_user as false with a v2 user token lacking the deprecated - // `chat:write:bot` scope, results in an error. So if as_user is false and - // there's a bot token available, we should use the bot token and omit - // as_user. Otherwise, use the user token and pass as_user through. + // `chat:write:bot` scope results in an error. If as_user is false and a + // bot token is available, use the bot token and omit as_user. Otherwise, + // pass as_user through. if (as_user === false && botTokenAvailable) { asBot = true; } else { @@ -556,19 +556,13 @@ export default { if ([ "not_in_channel", "channel_not_found", - ].includes(error?.data?.error) && asBot) { - // If method starts with chat, include the part about "As User" - // Otherwise, just say "Ensure the bot is a member of the channel" - if (method.startsWith("chat.")) { - throw new ConfigurationError(`${error} - Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user. - `); - } - throw new ConfigurationError(`${error} - Ensure the bot is a member of the channel. - `); + ].some((errorType) => error.includes(errorType)) && asBot) { + const followUp = method.startsWith("chat.") + ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." + : "Ensure the bot is a member of the channel."; + throw new ConfigurationError(`${error}\n${followUp}`); } - throw `${error}`; + throw error; } if (!response.ok) { @@ -594,7 +588,7 @@ export default { throw error; } } - bail(error); + bail(`${error}`); } }, retryOpts); }, @@ -699,7 +693,7 @@ export default { if (![ "method_not_supported_for_channel_type", "already_in_channel", - ].some((msg) => (error.data?.error || error.message || error)?.includes(msg))) { + ].some((errorType) => error.includes(errorType))) { throw error; } } From 3aa07731299eacb084470014c91ca528d737ee99 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 04/27] make addToChannel required for Send Message actions so it is shown in the component form by default so users aren't suprised by the new invite behavior --- components/slack/actions/common/send-message.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/slack/actions/common/send-message.mjs b/components/slack/actions/common/send-message.mjs index 4fa0a8e0d9c36..37c1574a8196d 100644 --- a/components/slack/actions/common/send-message.mjs +++ b/components/slack/actions/common/send-message.mjs @@ -14,7 +14,6 @@ export default { slack, "addToChannel", ], - optional: true, }, post_at: { propDefinition: [ From 2126d8e1230dd2337fadf8fb61ad0671b9c10554 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 05/27] don't throw error when adding app to channels fails --- components/slack/slack.app.mjs | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index fce4cca0aec77..f6a1bd22f57fb 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -673,30 +673,30 @@ export default { }, async maybeAddAppToChannels(channelIds = []) { if (!this.$auth.bot_token) return; - const { - bot_id, user_id, - } = await this.authTest({ - asBot: true, - }); - if (!bot_id) { - throw new Error("Could not get bot ID. Make sure the Slack app has a bot user."); - } - // XXX: Trying to add the app to DM or group DM channels results in the - // error: method_not_supported_for_channel_type - for (const channel of channelIds) { - try { - await this.inviteToConversation({ - channel, - users: user_id, - }); - } catch (error) { - if (![ - "method_not_supported_for_channel_type", - "already_in_channel", - ].some((errorType) => error.includes(errorType))) { - throw error; + try { + const { + bot_id, user_id, + } = await this.authTest({ + asBot: true, + }); + if (!bot_id) { + console.log("Skipping adding Slack app to channels: bot ID unavailable."); + return; + } + for (const channel of channelIds) { + try { + // Note: Trying to add the app to DM or group DM channels results in + // the error: method_not_supported_for_channel_type + await this.inviteToConversation({ + channel, + users: user_id, + }); + } catch (error) { + console.log(`Unable to add Slack app to channel ${channel}: ${error}`); } } + } catch (error) { + console.log(`Unable to add Slack app to channels: ${error}`); } }, /** From 8eb39ec0de644c4e85cc68ec4546e360f329cca7 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 06/27] delete New Direct Message trigger it's no longer supported for new auths since they will not have the im:history scope --- .../new-direct-message/new-direct-message.mjs | 53 ------------------- .../sources/new-direct-message/test-event.mjs | 28 ---------- 2 files changed, 81 deletions(-) delete mode 100644 components/slack/sources/new-direct-message/new-direct-message.mjs delete mode 100644 components/slack/sources/new-direct-message/test-event.mjs diff --git a/components/slack/sources/new-direct-message/new-direct-message.mjs b/components/slack/sources/new-direct-message/new-direct-message.mjs deleted file mode 100644 index 389c183951a5d..0000000000000 --- a/components/slack/sources/new-direct-message/new-direct-message.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import common from "../common/base.mjs"; -import sampleEmit from "./test-event.mjs"; - -export default { - ...common, - key: "slack-new-direct-message", - name: "New Direct Message (Instant)", - version: "1.0.23", - description: "Emit new event when a message was posted in a direct message channel", - type: "source", - dedupe: "unique", - props: { - ...common.props, - // eslint-disable-next-line pipedream/props-description,pipedream/props-label - slackApphook: { - type: "$.interface.apphook", - appProp: "slack", - async eventNames() { - return [ - "message.im", - ]; - }, - }, - ignoreBot: { - propDefinition: [ - common.props.slack, - "ignoreBot", - ], - }, - ignoreSelf: { - type: "boolean", - label: "Ignore Messages from Yourself", - description: "Ignores messages sent to yourself", - default: false, - optional: true, - }, - }, - methods: { - ...common.methods, - getSummary() { - return "New direct message received"; - }, - processEvent(event) { - if ((this.ignoreSelf && event.user == this.slack.mySlackId()) - || ((this.ignoreBot) && (event.subtype === "bot_message" || event.bot_id)) - || (event.subtype === "message_changed")) { - return; - } - return event; - }, - }, - sampleEmit, -}; diff --git a/components/slack/sources/new-direct-message/test-event.mjs b/components/slack/sources/new-direct-message/test-event.mjs deleted file mode 100644 index d19486ed235f0..0000000000000 --- a/components/slack/sources/new-direct-message/test-event.mjs +++ /dev/null @@ -1,28 +0,0 @@ -export default { - "user": "USLACKBOT", - "type": "message", - "ts": "1716401124.947359", - "text": "Feeling great!", - "team": "TS8319547", - "blocks": [ - { - "type": "rich_text", - "block_id": "bid/", - "elements": [ - { - "type": "rich_text_section", - "elements": [ - { - "type": "text", - "text": "Feeling great!" - } - ] - } - ] - } - ], - "channel": "DS676Q73J", - "event_ts": "1716401124.947359", - "channel_type": "im", - "pipedream_msg_id": "pd_1716401126905_tjxu6josgz" -} \ No newline at end of file From ce6dba8539fff1b7d00a5d1fbc3a31e7995f0000 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 07/27] delete the Verify Slack Signature action as requested by Slack --- .../verify-slack-signature.mjs | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 components/slack/actions/verify-slack-signature/verify-slack-signature.mjs diff --git a/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs b/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs deleted file mode 100644 index 15997e6d2c906..0000000000000 --- a/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import crypto from "crypto"; -import slack from "../../slack.app.mjs"; - -export default { - key: "slack-verify-slack-signature", - name: "Verify Slack Signature", - description: "Verifying requests from Slack, slack signs its requests using a secret that's unique to your app. [See the documentation](https://api.slack.com/authentication/verifying-requests-from-slack)", - version: "0.0.17", - annotations: { - destructiveHint: false, - openWorldHint: true, - readOnlyHint: true, - }, - type: "action", - props: { - slack, - slackSigningSecret: { - type: "string", - label: "Signing Secret", - description: "Slack [Signing Secret](https://api.slack.com/authentication/verifying-requests-from-slack#:~:text=Slack%20Signing%20Secret%2C%20available%20in%20the%20app%20admin%20panel%20under%20Basic%20Info.), available in the app admin panel under Basic Info.", - secret: true, - }, - slackSignature: { - type: "string", - label: "X-Slack-Signature", - description: "Slack signature (from X-Slack-Signature header).", - }, - slackRequestTimestamp: { - type: "string", - label: "X-Slack-Request-Timestamp", - description: "Slack request timestamp (from X-Slack-Request-Timestamp header).", - }, - requestBody: { - type: "any", - label: "Request Body", - description: "The body of the request to be verified.", - }, - }, - async run({ $ }) { - const { - slackSignature, - slackRequestTimestamp, - requestBody, - slackSigningSecret, - } = this; - const requestBodyStr = typeof (requestBody) === "string" ? - requestBody : - JSON.stringify(requestBody); - const sigBaseString = `v0:${slackRequestTimestamp}:${requestBodyStr}`; - const sha256Hex = crypto.createHmac("sha256", slackSigningSecret) - .update(sigBaseString, "utf8") - .digest("hex"); - const mySignature = `v0=${sha256Hex}`; - if (crypto.timingSafeEqual(Buffer.from(mySignature, "utf8"), Buffer.from(slackSignature, "utf8"))) { - $.export("$summary", `Successfully verified the request with "${slackSignature}" signature`); - return { - success: true, - }; - } - $.export("$summary", "Slack signature mismatch with provided properties, it may be a configuration issue."); - return { - success: false, - }; - }, -}; From 8b99a4ede97aebbde6a5a8cb1585a2a0175256a9 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 08/27] Use assistant search API in Find Message action Fallback to search.messages API method if missing scopese --- .../actions/find-message/find-message.mjs | 184 ++++++++++++++---- components/slack/slack.app.mjs | 6 + 2 files changed, 151 insertions(+), 39 deletions(-) diff --git a/components/slack/actions/find-message/find-message.mjs b/components/slack/actions/find-message/find-message.mjs index 0633261f6a7bf..89facad0a911b 100644 --- a/components/slack/actions/find-message/find-message.mjs +++ b/components/slack/actions/find-message/find-message.mjs @@ -1,11 +1,10 @@ -import { axios } from "@pipedream/platform"; import slack from "../../slack.app.mjs"; export default { key: "slack-find-message", name: "Find Message", description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)", - version: "0.0.26", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, @@ -49,53 +48,160 @@ export default { }, }, methods: { - async searchMessages($, params) { - const data = await axios($, { - method: "POST", - url: "https://slack.com/api/assistant.search.context", - data: { - query: params.query, - sort: params.sort, - sort_dir: params.sort_dir, - cursor: params.cursor, - channel_types: params.channel_types, - }, - headers: { - "Authorization": `Bearer ${this.slack.getToken()}`, - "Content-Type": "application/json", - }, - }); - if (!data.ok) { - throw new Error(data.error || "An error occurred while searching messages"); + normalizeAssistantMatch(match) { + if (!match || typeof match !== "object") { + return match; } - return data; + const { + author_user_id: authorUserId, + team_id: teamId, + channel_id: channelId, + message_ts: messageTs, + content, + permalink, + is_author_bot: isAuthorBot, + message, + channel, + ...rest + } = match; + const baseMessage = typeof message === "object" + ? message + : {}; + const channelInfo = channel && typeof channel === "object" + ? { + ...channel, + id: channel.id || channelId, + } + : channelId + ? { + id: channelId, + } + : undefined; + const normalized = { + type: "message", + user: authorUserId, + team: teamId, + ts: messageTs, + text: content, + permalink, + channel: channelInfo, + ...baseMessage, + ...rest, + }; + if (isAuthorBot !== undefined && normalized.is_author_bot === undefined) { + normalized.is_author_bot = isAuthorBot; + } + if (normalized.text == null) { + normalized.text = baseMessage.text || content; + } + if (normalized.ts == null) { + normalized.ts = baseMessage.ts || messageTs; + } + if (!normalized.channel && baseMessage.channel) { + normalized.channel = baseMessage.channel; + } else if (normalized.channel && baseMessage.channel && typeof baseMessage.channel === "object") { + normalized.channel = { + ...normalized.channel, + ...baseMessage.channel, + }; + } + return normalized; + }, + async searchWithAssistant(baseParams, maxResults) { + const matches = []; + let cursor; + + do { + const response = await this.slack.assistantSearch({ + ...baseParams, + channel_types: "public_channel,private_channel", + cursor, + }); + const messages = (response.results?.messages || []) + .map((item) => this.normalizeAssistantMatch(item)); + matches.push(...messages); + cursor = response.response_metadata?.next_cursor; + } while (cursor && matches.length < maxResults); + + return matches.slice(0, maxResults); + }, + async searchWithSearchMessages(baseParams, maxResults) { + const matches = []; + let page = 1; + const count = Math.min(Math.max(maxResults, 1), 100); + + while (matches.length < maxResults) { + const response = await this.slack.searchMessages({ + ...baseParams, + count, + page, + }); + const pageMatches = response.messages?.matches || []; + matches.push(...pageMatches); + + if (matches.length >= maxResults) { + break; + } + + const pagination = response.messages?.pagination; + const paging = response.messages?.paging; + const hasMore = pagination + ? pagination.page < pagination.page_count + : paging + ? paging.page < paging.pages + : false; + + if (!hasMore) { + break; + } + + page += 1; + } + + return matches.slice(0, maxResults); + }, + shouldFallbackToSearchMessages(error) { + const errorCode = typeof error === "string" + ? error + : error?.data?.error || error?.message; + + if (!errorCode.includes("missing_scope")) { + return false; + } + + const providedSources = [ + error?.data?.provided, + error?.provided, + error?.original?.data?.provided, + ].filter(Boolean); + + const providedScopes = providedSources + .flatMap((value) => Array.isArray(value) + ? value + : String(value).split(",")) + .map((scope) => scope.trim()) + .filter(Boolean); + + return providedScopes.includes("search:read"); }, }, async run({ $ }) { - const matches = []; - const params = { + const maxResults = Math.max(this.maxResults ?? 20, 1); + const baseParams = { query: this.query, sort: this.sort, sort_dir: this.sortDirection, - channel_types: "public_channel,private_channel", }; - let cursor; + let matches; - do { - if (cursor) { - params.cursor = cursor; + try { + matches = await this.searchWithAssistant(baseParams, maxResults); + } catch (error) { + if (this.shouldFallbackToSearchMessages(error)) { + matches = await this.searchWithSearchMessages(baseParams, maxResults); + } else { + throw error; } - const response = await this.searchMessages($, params); - const messages = response.results?.messages || []; - matches.push(...messages); - if (matches.length >= this.maxResults) { - break; - } - cursor = response.response_metadata?.next_cursor; - } while (cursor); - - if (matches.length > this.maxResults) { - matches.length = this.maxResults; } $.export("$summary", `Found ${matches.length} matching message${matches.length === 1 diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index f6a1bd22f57fb..a45934522960e 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -884,6 +884,12 @@ export default { ...args, }); }, + assistantSearch(args = {}) { + args.count ||= constants.LIMIT; + return this.sdk().apiCall("assistant.search.context", { + ...args, + }); + }, /** * Lists reactions made by a user. * User Scopes: `reactions:read` From 2476e2c1fb5a02080cdb61e3f92a6210f7fcf9c2 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:45:18 -0400 Subject: [PATCH 09/27] Support configuring base URL for Slack API requests Future-proofs Slack components to support proxying requests --- components/slack/slack.app.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index a45934522960e..1f295bcdaa8a6 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -527,6 +527,7 @@ export default { sdk(opts = {}) { return new WebClient(this.getToken(opts), { rejectRateLimitedCalls: true, + slackApiUrl: this.$auth.base_url, }); }, async makeRequest({ From 074b23e0afd3e3cb29e0018b5cd564da7abcfe28 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:46:05 -0400 Subject: [PATCH 10/27] update pnpm-lock.yaml --- pnpm-lock.yaml | 113 +++++-------------------------------------------- 1 file changed, 11 insertions(+), 102 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c2c2be2551f9..15821de01c8a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2044,8 +2044,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/browserflow: - specifiers: {} + components/browserflow: {} components/browserhub: dependencies: @@ -2138,8 +2137,7 @@ importers: specifier: ^1.3.0 version: 1.6.6 - components/bump_sh: - specifiers: {} + components/bump_sh: {} components/bunnycdn: dependencies: @@ -2498,8 +2496,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/chatbase: - specifiers: {} + components/chatbase: {} components/chatbot: dependencies: @@ -4872,8 +4869,7 @@ importers: components/fastfield_mobile_forms: {} - components/fathom: - specifiers: {} + components/fathom: {} components/fatture_in_cloud: {} @@ -5862,8 +5858,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/google_books: - specifiers: {} + components/google_books: {} components/google_calendar: dependencies: @@ -8820,8 +8815,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/microsoft_bookings: - specifiers: {} + components/microsoft_bookings: {} components/microsoft_dataverse: {} @@ -8863,8 +8857,7 @@ importers: components/microsoft_graph_api_daemon_app: {} - components/microsoft_graph_security: - specifiers: {} + components/microsoft_graph_security: {} components/microsoft_onedrive: dependencies: @@ -9271,9 +9264,6 @@ importers: mysql2: specifier: ^3.9.2 version: 3.11.4 - mysql2-promise: - specifier: ^0.1.4 - version: 0.1.4 uuid: specifier: ^8.3.2 version: 8.3.2 @@ -23309,9 +23299,6 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - ansicolors@0.2.1: - resolution: {integrity: sha512-tOIuy1/SK/dr94ZA0ckDohKXNeBNqZ4us6PjMVLs5h1w2GBB6uPtOknp2+VF4F/zcy9LI70W+Z+pE2Soajky1w==} - ansicolors@0.3.2: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} @@ -23754,9 +23741,6 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - bn.js@2.0.0: - resolution: {integrity: sha512-NmOLApC80+n+P28y06yHgwGlOCkq/X4jRh5s590959FZXSrM+I/61h0xxuIaYsg0mD44mEAZYG/rnclWuRoz+A==} - bn.js@4.12.1: resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} @@ -23984,10 +23968,6 @@ packages: caniuse-lite@1.0.30001683: resolution: {integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==} - cardinal@0.4.4: - resolution: {integrity: sha512-3MxV0o9wOpQcobrcSrRpaSxlYkohCcZu0ytOjJUww/Yo/223q4Ecloo7odT+M0SI5kPgb1JhvSaF4EEuVXOLAQ==} - hasBin: true - cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true @@ -25049,9 +25029,6 @@ packages: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} - double-ended-queue@2.0.0-0: - resolution: {integrity: sha512-t5ouWOpItmHrm0J0+bX/cFrIjBFWnJkk5LbIJq6bbU/M4aLX2c3LrM4QYsBptwvlPe3WzdpQefQ0v1pe/A5wjg==} - dropbox@10.34.0: resolution: {integrity: sha512-5jb5/XzU0fSnq36/hEpwT5/QIep7MgqKuxghEG44xCu7HruOAjPdOb3x0geXv5O/hd0nHpQpWO+r5MjYTpMvJg==} engines: {node: '>=0.10.3'} @@ -25526,11 +25503,6 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esprima@1.0.4: - resolution: {integrity: sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA==} - engines: {node: '>=0.4.0'} - hasBin: true - esprima@1.2.2: resolution: {integrity: sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==} engines: {node: '>=0.4.0'} @@ -28255,9 +28227,6 @@ packages: resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} engines: {node: 20 || >=22} - lru-cache@2.5.0: - resolution: {integrity: sha512-dVmQmXPBlTgFw77hm60ud//l2bCuDKkqC2on1EBoM7s9Urm9IQDrnujwZ93NFnAq0dVZ0HBXTS7PwEG+YE7+EQ==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -28836,14 +28805,6 @@ packages: resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} engines: {node: '>=0.8.0'} - mysql2-promise@0.1.4: - resolution: {integrity: sha512-/h8ubU/36aIPpbfB6CENw9ZdbzIhZMZOIbstJUHVKp4J9JBRSLScrYImVx+3yZilgag732UhpQMMK5+ktdhLCw==} - engines: {node: '>=0.10.0'} - - mysql2@0.15.8: - resolution: {integrity: sha512-3x5o6C20bfwJYPSoT74MOoad7/chJoq4qXHDL5VAuRBBrIyErovLoj04Dz/5EY9X2kTxWSGNiTegtxpROTd2YQ==} - engines: {node: '>= 0.8'} - mysql2@3.11.4: resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} engines: {node: '>= 8.0'} @@ -28851,9 +28812,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - named-placeholders@0.1.3: - resolution: {integrity: sha512-Mt79RtxZ6MYTIEemPGv/YDKpbuavcAyGHb0r37xB2mnE5jej3uBzc4+nzOeoZ4nZiii1M32URKt9IjkSTZAmTA==} - named-placeholders@1.1.3: resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} engines: {node: '>=12.0.0'} @@ -30235,9 +30193,6 @@ packages: resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} engines: {node: '>=18'} - readable-stream@1.0.33: - resolution: {integrity: sha512-72KxhcKi8bAvHP/cyyWSP+ODS5ef0DIRs0OzrhGXw31q41f19aoELCbvd42FjhpyEDxQMRiiC5rq9rfE5PzTqg==} - readable-stream@1.1.14: resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} @@ -30278,9 +30233,6 @@ packages: recurly@3.30.0: resolution: {integrity: sha512-SVW5pke3MLe+QkIx3Y+NJD8UfR30eBrM90Vkrv6o3FvDPZBvSNpSadTay1SzLo+SmM31rBSeqELyX4zBDTd/Nw==} - redeyed@0.4.4: - resolution: {integrity: sha512-pnk1vsaNLu1UAAClKsImKz9HjBvg9i8cbRqTRzJbiCjGF0fZSMqpdcA5W3juO3c4etFvTrabECkq9wjC45ZyxA==} - redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} @@ -31361,22 +31313,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@10.0.0: resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==} @@ -42834,8 +42786,6 @@ snapshots: dependencies: entities: 2.2.0 - ansicolors@0.2.1: {} - ansicolors@0.3.2: {} ansis@4.1.0: {} @@ -43475,8 +43425,6 @@ snapshots: bluebird@3.7.2: {} - bn.js@2.0.0: {} - bn.js@4.12.1: {} bn.js@5.2.1: {} @@ -43748,11 +43696,6 @@ snapshots: caniuse-lite@1.0.30001683: {} - cardinal@0.4.4: - dependencies: - ansicolors: 0.2.1 - redeyed: 0.4.4 - cardinal@2.1.1: dependencies: ansicolors: 0.3.2 @@ -44846,8 +44789,6 @@ snapshots: dotenv@8.6.0: {} - double-ended-queue@2.0.0-0: {} - dropbox@10.34.0(@types/node-fetch@2.6.12): dependencies: '@types/node-fetch': 2.6.12 @@ -45630,8 +45571,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 - esprima@1.0.4: {} - esprima@1.2.2: {} esprima@4.0.1: {} @@ -49242,8 +49181,6 @@ snapshots: lru-cache@11.0.2: {} - lru-cache@2.5.0: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -50052,19 +49989,6 @@ snapshots: rimraf: 2.4.5 optional: true - mysql2-promise@0.1.4: - dependencies: - mysql2: 0.15.8 - q: 1.5.1 - - mysql2@0.15.8: - dependencies: - bn.js: 2.0.0 - cardinal: 0.4.4 - double-ended-queue: 2.0.0-0 - named-placeholders: 0.1.3 - readable-stream: 1.0.33 - mysql2@3.11.4: dependencies: aws-ssl-profiles: 1.1.2 @@ -50083,10 +50007,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - named-placeholders@0.1.3: - dependencies: - lru-cache: 2.5.0 - named-placeholders@1.1.3: dependencies: lru-cache: 7.18.3 @@ -51987,13 +51907,6 @@ snapshots: type-fest: 4.41.0 unicorn-magic: 0.1.0 - readable-stream@1.0.33: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - readable-stream@1.1.14: dependencies: core-util-is: 1.0.3 @@ -52051,10 +51964,6 @@ snapshots: recurly@3.30.0: {} - redeyed@0.4.4: - dependencies: - esprima: 1.0.4 - redeyed@2.1.1: dependencies: esprima: 4.0.1 From ccc23538088a4317cb8110b1e9ffa8971e9601cf Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:28:10 -0400 Subject: [PATCH 11/27] minor refactor of bot token usage add/update logged messages when unable to add app to channel --- components/slack/slack.app.mjs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index 1f295bcdaa8a6..670ba2731c5da 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -490,9 +490,14 @@ export default { }, getToken(opts = {}) { // Use bot token if asBot is true and available, otherwise use user token. - return (opts.asBot && this.$auth.bot_token) - ? this.$auth.bot_token - : this.$auth.oauth_access_token; + const botToken = this.getBotToken(); + const userToken = this.$auth.oauth_access_token; + return (opts.asBot && botToken) + ? botToken + : userToken; + }, + getBotToken() { + return this.$auth.bot_token; }, async getChannelDisplayName(channel) { if (channel.user) { @@ -533,12 +538,11 @@ export default { async makeRequest({ method = "", throwRateLimitError = false, asBot, as_user, ...args } = {}) { - const botTokenAvailable = Boolean(this.$auth.bot_token); // Passing as_user as false with a v2 user token lacking the deprecated // `chat:write:bot` scope results in an error. If as_user is false and a // bot token is available, use the bot token and omit as_user. Otherwise, // pass as_user through. - if (as_user === false && botTokenAvailable) { + if (as_user === false && Boolean(this.getBotToken())) { asBot = true; } else { args.as_user = as_user; @@ -673,7 +677,10 @@ export default { return realNames; }, async maybeAddAppToChannels(channelIds = []) { - if (!this.$auth.bot_token) return; + if (!this.getBotToken()) { + console.log("Skipping adding app to channels: bot unavailable."); + return; + } try { const { bot_id, user_id, @@ -681,7 +688,7 @@ export default { asBot: true, }); if (!bot_id) { - console.log("Skipping adding Slack app to channels: bot ID unavailable."); + console.log("Skipping adding app to channels: bot not found."); return; } for (const channel of channelIds) { @@ -693,11 +700,11 @@ export default { users: user_id, }); } catch (error) { - console.log(`Unable to add Slack app to channel ${channel}: ${error}`); + console.log(`Unable to add app to channel ${channel}: ${error}`); } } } catch (error) { - console.log(`Unable to add Slack app to channels: ${error}`); + console.log(`Unable to add app to channels: ${error}`); } }, /** From 8717ff3c3d91eb164cc921c45f556127c2e5981f Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:40:13 -0400 Subject: [PATCH 12/27] Bump version numbers for Slack components to reflect recent updates The following components have new minor versions: - delete-message - find-message - get-file - list-files - reply-to-a-message - send-block-kit-message - send-large-message - send-message - send-message-advanced - send-message-to-channel - send-message-to-user-or-group - update-message - new-keyword-mention - new-message-in-channels - new-user-mention All other Slack components have new patch versions. --- .../slack/actions/add-emoji-reaction/add-emoji-reaction.mjs | 2 +- components/slack/actions/approve-workflow/approve-workflow.mjs | 2 +- components/slack/actions/archive-channel/archive-channel.mjs | 2 +- components/slack/actions/create-channel/create-channel.mjs | 2 +- components/slack/actions/create-reminder/create-reminder.mjs | 2 +- components/slack/actions/delete-file/delete-file.mjs | 2 +- components/slack/actions/delete-message/delete-message.mjs | 2 +- .../slack/actions/find-user-by-email/find-user-by-email.mjs | 2 +- components/slack/actions/get-file/get-file.mjs | 2 +- .../actions/invite-user-to-channel/invite-user-to-channel.mjs | 2 +- components/slack/actions/kick-user/kick-user.mjs | 2 +- components/slack/actions/list-channels/list-channels.mjs | 2 +- components/slack/actions/list-files/list-files.mjs | 2 +- .../slack/actions/list-group-members/list-group-members.mjs | 2 +- .../actions/list-members-in-channel/list-members-in-channel.mjs | 2 +- components/slack/actions/list-replies/list-replies.mjs | 2 +- components/slack/actions/list-users/list-users.mjs | 2 +- .../slack/actions/reply-to-a-message/reply-to-a-message.mjs | 2 +- .../actions/send-block-kit-message/send-block-kit-message.mjs | 2 +- .../slack/actions/send-large-message/send-large-message.mjs | 2 +- .../actions/send-message-advanced/send-message-advanced.mjs | 2 +- .../actions/send-message-to-channel/send-message-to-channel.mjs | 2 +- .../send-message-to-user-or-group.mjs | 2 +- components/slack/actions/send-message/send-message.mjs | 2 +- .../actions/set-channel-description/set-channel-description.mjs | 2 +- .../slack/actions/set-channel-topic/set-channel-topic.mjs | 2 +- components/slack/actions/set-status/set-status.mjs | 2 +- .../slack/actions/update-group-members/update-group-members.mjs | 2 +- components/slack/actions/update-message/update-message.mjs | 2 +- components/slack/actions/update-profile/update-profile.mjs | 2 +- components/slack/actions/upload-file/upload-file.mjs | 2 +- .../slack/sources/new-channel-created/new-channel-created.mjs | 2 +- .../new-interaction-event-received.mjs | 2 +- .../slack/sources/new-keyword-mention/new-keyword-mention.mjs | 2 +- .../sources/new-message-in-channels/new-message-in-channels.mjs | 2 +- .../slack/sources/new-reaction-added/new-reaction-added.mjs | 2 +- .../slack/sources/new-saved-message/new-saved-message.mjs | 2 +- components/slack/sources/new-user-added/new-user-added.mjs | 2 +- components/slack/sources/new-user-mention/new-user-mention.mjs | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) diff --git a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs index 010ee53ed3e6c..86e55b66dbd53 100644 --- a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs +++ b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -4,7 +4,7 @@ export default { key: "slack-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.16", + version: "0.0.17", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/approve-workflow/approve-workflow.mjs b/components/slack/actions/approve-workflow/approve-workflow.mjs index fb8a147c59b85..3c2a5403bf9ad 100644 --- a/components/slack/actions/approve-workflow/approve-workflow.mjs +++ b/components/slack/actions/approve-workflow/approve-workflow.mjs @@ -5,7 +5,7 @@ export default { key: "slack-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.5", + version: "0.0.6", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/archive-channel/archive-channel.mjs b/components/slack/actions/archive-channel/archive-channel.mjs index 9ce69641c5627..2bf96a5aae7c7 100644 --- a/components/slack/actions/archive-channel/archive-channel.mjs +++ b/components/slack/actions/archive-channel/archive-channel.mjs @@ -5,7 +5,7 @@ export default { key: "slack-archive-channel", name: "Archive Channel", description: "Archive a channel. [See the documentation](https://api.slack.com/methods/conversations.archive)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/create-channel/create-channel.mjs b/components/slack/actions/create-channel/create-channel.mjs index d14ea45272547..79698261c6bb1 100644 --- a/components/slack/actions/create-channel/create-channel.mjs +++ b/components/slack/actions/create-channel/create-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-channel", name: "Create a Channel", description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)", - version: "0.0.25", + version: "0.0.26", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/create-reminder/create-reminder.mjs b/components/slack/actions/create-reminder/create-reminder.mjs index 7d3ce551d2ffc..44c00aed8045b 100644 --- a/components/slack/actions/create-reminder/create-reminder.mjs +++ b/components/slack/actions/create-reminder/create-reminder.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-reminder", name: "Create Reminder", description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)", - version: "0.0.25", + version: "0.0.26", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/delete-file/delete-file.mjs b/components/slack/actions/delete-file/delete-file.mjs index 0648c96c71ba5..5ef199841fb80 100644 --- a/components/slack/actions/delete-file/delete-file.mjs +++ b/components/slack/actions/delete-file/delete-file.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-file", name: "Delete File", description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/delete-message/delete-message.mjs b/components/slack/actions/delete-message/delete-message.mjs index 7dd9b92861992..6a3642bb64e41 100644 --- a/components/slack/actions/delete-message/delete-message.mjs +++ b/components/slack/actions/delete-message/delete-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-message", name: "Delete Message", description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)", - version: "0.0.24", + version: "0.1.0", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/find-user-by-email/find-user-by-email.mjs b/components/slack/actions/find-user-by-email/find-user-by-email.mjs index cc12d5d439bf0..e1f35afe907ac 100644 --- a/components/slack/actions/find-user-by-email/find-user-by-email.mjs +++ b/components/slack/actions/find-user-by-email/find-user-by-email.mjs @@ -4,7 +4,7 @@ export default { key: "slack-find-user-by-email", name: "Find User by Email", description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/get-file/get-file.mjs b/components/slack/actions/get-file/get-file.mjs index 0e47812aa6848..601eb9528e7a5 100644 --- a/components/slack/actions/get-file/get-file.mjs +++ b/components/slack/actions/get-file/get-file.mjs @@ -5,7 +5,7 @@ export default { key: "slack-get-file", name: "Get File", description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)", - version: "0.0.24", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs index 4d2e8240a4ae3..28f08457fb2a2 100644 --- a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs +++ b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-invite-user-to-channel", name: "Invite User to Channel", description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/kick-user/kick-user.mjs b/components/slack/actions/kick-user/kick-user.mjs index eacd9d761f9a7..2a3cabc3696db 100644 --- a/components/slack/actions/kick-user/kick-user.mjs +++ b/components/slack/actions/kick-user/kick-user.mjs @@ -5,7 +5,7 @@ export default { key: "slack-kick-user", name: "Kick User", description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/list-channels/list-channels.mjs b/components/slack/actions/list-channels/list-channels.mjs index ef00493f71f77..27606438156ff 100644 --- a/components/slack/actions/list-channels/list-channels.mjs +++ b/components/slack/actions/list-channels/list-channels.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-channels", name: "List Channels", description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-files/list-files.mjs b/components/slack/actions/list-files/list-files.mjs index 7b4abe72a79c7..48be1110dd452 100644 --- a/components/slack/actions/list-files/list-files.mjs +++ b/components/slack/actions/list-files/list-files.mjs @@ -5,7 +5,7 @@ export default { key: "slack-list-files", name: "List Files", description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)", - version: "0.0.52", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-group-members/list-group-members.mjs b/components/slack/actions/list-group-members/list-group-members.mjs index a1ba13acdf935..0ff26898a734c 100644 --- a/components/slack/actions/list-group-members/list-group-members.mjs +++ b/components/slack/actions/list-group-members/list-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-group-members", name: "List Group Members", description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs index 63981dcf5b29b..ff038cf536b26 100644 --- a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs +++ b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-members-in-channel", name: "List Members in Channel", description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-replies/list-replies.mjs b/components/slack/actions/list-replies/list-replies.mjs index c36e5c45833f1..413ac8aefddbc 100644 --- a/components/slack/actions/list-replies/list-replies.mjs +++ b/components/slack/actions/list-replies/list-replies.mjs @@ -5,7 +5,7 @@ export default { key: "slack-list-replies", name: "List Replies", description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-users/list-users.mjs b/components/slack/actions/list-users/list-users.mjs index f4d7733cf7eb0..758d47f8f215e 100644 --- a/components/slack/actions/list-users/list-users.mjs +++ b/components/slack/actions/list-users/list-users.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-users", name: "List Users", description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs index 6054fcd4efa19..167c1b081a690 100644 --- a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs +++ b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-reply-to-a-message", name: "Reply to a Message Thread", description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.1.29", + version: "0.2.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs index f78c9489ce809..7fa53d5fc6559 100644 --- a/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs +++ b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-block-kit-message", name: "Build and Send a Block Kit Message", description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).", - version: "0.4.5", + version: "0.5.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-large-message/send-large-message.mjs b/components/slack/actions/send-large-message/send-large-message.mjs index 59dac250101dd..474230475d486 100644 --- a/components/slack/actions/send-large-message/send-large-message.mjs +++ b/components/slack/actions/send-large-message/send-large-message.mjs @@ -5,7 +5,7 @@ export default { key: "slack-send-large-message", name: "Send a Large Message (3000+ characters)", description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.0.24", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message-advanced/send-message-advanced.mjs b/components/slack/actions/send-message-advanced/send-message-advanced.mjs index ad2d604633db7..6b8df73332ac4 100644 --- a/components/slack/actions/send-message-advanced/send-message-advanced.mjs +++ b/components/slack/actions/send-message-advanced/send-message-advanced.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-message-advanced", name: "Send Message (Advanced)", description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.0.7", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs index 55d1119349614..ac00b08c5e5b8 100644 --- a/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs +++ b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs @@ -6,7 +6,7 @@ export default { key: "slack-send-message-to-channel", name: "Send Message to Channel", description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.0.5", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs index f33e456e8726c..60834909dec8a 100644 --- a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +++ b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-message-to-user-or-group", name: "Send Message to User or Group", description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.0.5", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message/send-message.mjs b/components/slack/actions/send-message/send-message.mjs index 06dc22d8b7f7a..e8a67df460932 100644 --- a/components/slack/actions/send-message/send-message.mjs +++ b/components/slack/actions/send-message/send-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-send-message", name: "Send Message", description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.0.20", + version: "0.1.0", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/set-channel-description/set-channel-description.mjs b/components/slack/actions/set-channel-description/set-channel-description.mjs index 7a544b28c6362..7567d090efd75 100644 --- a/components/slack/actions/set-channel-description/set-channel-description.mjs +++ b/components/slack/actions/set-channel-description/set-channel-description.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-description", name: "Set Channel Description", description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/set-channel-topic/set-channel-topic.mjs b/components/slack/actions/set-channel-topic/set-channel-topic.mjs index 9f12041184de0..fa7a5b50de83a 100644 --- a/components/slack/actions/set-channel-topic/set-channel-topic.mjs +++ b/components/slack/actions/set-channel-topic/set-channel-topic.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-topic", name: "Set Channel Topic", description: "Set the topic on a selected channel. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/set-status/set-status.mjs b/components/slack/actions/set-status/set-status.mjs index 10f0069f743fd..954317d4f9487 100644 --- a/components/slack/actions/set-status/set-status.mjs +++ b/components/slack/actions/set-status/set-status.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-status", name: "Set Status", description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/update-group-members/update-group-members.mjs b/components/slack/actions/update-group-members/update-group-members.mjs index cb36d77b070d2..79b78ca6b1a0c 100644 --- a/components/slack/actions/update-group-members/update-group-members.mjs +++ b/components/slack/actions/update-group-members/update-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-group-members", name: "Update Groups Members", description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/update-message/update-message.mjs b/components/slack/actions/update-message/update-message.mjs index 4f797ffabb3af..7ff12d7b56427 100644 --- a/components/slack/actions/update-message/update-message.mjs +++ b/components/slack/actions/update-message/update-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-message", name: "Update Message", description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)", - version: "0.1.24", + version: "0.2.0", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/update-profile/update-profile.mjs b/components/slack/actions/update-profile/update-profile.mjs index e3c871d7d0246..270bc6f78474b 100644 --- a/components/slack/actions/update-profile/update-profile.mjs +++ b/components/slack/actions/update-profile/update-profile.mjs @@ -5,7 +5,7 @@ export default { key: "slack-update-profile", name: "Update Profile", description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.24", + version: "0.0.25", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/upload-file/upload-file.mjs b/components/slack/actions/upload-file/upload-file.mjs index 56240f1c4b169..ddc1926d77a8b 100644 --- a/components/slack/actions/upload-file/upload-file.mjs +++ b/components/slack/actions/upload-file/upload-file.mjs @@ -8,7 +8,7 @@ export default { key: "slack-upload-file", name: "Upload File", description: "Upload a file. [See the documentation](https://api.slack.com/messaging/files#uploading_files)", - version: "0.1.2", + version: "0.1.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/sources/new-channel-created/new-channel-created.mjs b/components/slack/sources/new-channel-created/new-channel-created.mjs index 7eb2ff9085f4a..9d1df1076dc05 100644 --- a/components/slack/sources/new-channel-created/new-channel-created.mjs +++ b/components/slack/sources/new-channel-created/new-channel-created.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-channel-created", name: "New Channel Created (Instant)", - version: "0.0.10", + version: "0.0.11", description: "Emit new event when a new channel is created.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs index 75b100e999b45..4ec49f2be9653 100644 --- a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs +++ b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { name: "New Interaction Events (Instant)", - version: "0.0.20", + version: "0.0.21", key: "slack-new-interaction-event-received", description: "Emit new events on new Slack [interactivity events](https://api.slack.com/interactivity) sourced from [Block Kit interactive elements](https://api.slack.com/interactivity/components), [Slash commands](https://api.slack.com/interactivity/slash-commands), or [Shortcuts](https://api.slack.com/interactivity/shortcuts).", type: "source", diff --git a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs index fffe5e9be0f3b..c6323e562670e 100644 --- a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs +++ b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs @@ -7,7 +7,7 @@ export default { ...common, key: "slack-new-keyword-mention", name: "New Keyword Mention (Instant)", - version: "0.0.8", + version: "0.1.0", description: "Emit new event when a specific keyword is mentioned in a channel", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs index e28866664d789..72c9ef83bb751 100644 --- a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs +++ b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs @@ -7,7 +7,7 @@ export default { ...common, key: "slack-new-message-in-channels", name: "New Message In Channels (Instant)", - version: "1.0.25", + version: "1.1.0", description: "Emit new event when a new message is posted to one or more channels", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-reaction-added/new-reaction-added.mjs b/components/slack/sources/new-reaction-added/new-reaction-added.mjs index 069113f447d90..aaf4edf973db0 100644 --- a/components/slack/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack/sources/new-reaction-added/new-reaction-added.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-reaction-added", name: "New Reaction Added (Instant)", - version: "1.1.26", + version: "1.2.0", description: "Emit new event when a member has added an emoji reaction to a message", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-saved-message/new-saved-message.mjs b/components/slack/sources/new-saved-message/new-saved-message.mjs index fe0f907aabf84..2d7a94e5c5b95 100644 --- a/components/slack/sources/new-saved-message/new-saved-message.mjs +++ b/components/slack/sources/new-saved-message/new-saved-message.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-saved-message", name: "New Saved Message (Instant)", - version: "0.0.6", + version: "0.0.7", description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-user-added/new-user-added.mjs b/components/slack/sources/new-user-added/new-user-added.mjs index 80bde255eb668..03db742683377 100644 --- a/components/slack/sources/new-user-added/new-user-added.mjs +++ b/components/slack/sources/new-user-added/new-user-added.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-user-added", name: "New User Added (Instant)", - version: "0.0.4", + version: "0.0.5", description: "Emit new event when a new member joins a workspace.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-user-mention/new-user-mention.mjs b/components/slack/sources/new-user-mention/new-user-mention.mjs index f40ca64363d64..15efc422c870d 100644 --- a/components/slack/sources/new-user-mention/new-user-mention.mjs +++ b/components/slack/sources/new-user-mention/new-user-mention.mjs @@ -7,7 +7,7 @@ export default { ...common, key: "slack-new-user-mention", name: "New User Mention (Instant)", - version: "0.0.8", + version: "0.1.0", description: "Emit new event when a username or specific keyword is mentioned in a channel", type: "source", dedupe: "unique", From 325a1645f97176df5314c7dcaba3471cfa194bab Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:51:58 -0400 Subject: [PATCH 13/27] bump Slack app minor version --- components/slack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/slack/package.json b/components/slack/package.json index 1c5aae17dca63..2e1c6cf76a6dc 100644 --- a/components/slack/package.json +++ b/components/slack/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/slack", - "version": "0.10.2", + "version": "0.11.0", "description": "Pipedream Slack Components", "main": "slack.app.mjs", "keywords": [ From 2df5de6791eeabddcd29b3ab2241f65a7c342870 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:04:21 -0400 Subject: [PATCH 14/27] address review feedback guard against unexpected types --- components/slack/actions/find-message/find-message.mjs | 2 +- components/slack/slack.app.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/slack/actions/find-message/find-message.mjs b/components/slack/actions/find-message/find-message.mjs index 89facad0a911b..927ab5d33b9f6 100644 --- a/components/slack/actions/find-message/find-message.mjs +++ b/components/slack/actions/find-message/find-message.mjs @@ -165,7 +165,7 @@ export default { ? error : error?.data?.error || error?.message; - if (!errorCode.includes("missing_scope")) { + if (!errorCode?.includes("missing_scope")) { return false; } diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index 670ba2731c5da..ebe0650a41164 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -561,7 +561,7 @@ export default { if ([ "not_in_channel", "channel_not_found", - ].some((errorType) => error.includes(errorType)) && asBot) { + ].some((errorType) => `${error}`.includes(errorType)) && asBot) { const followUp = method.startsWith("chat.") ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." : "Ensure the bot is a member of the channel."; From 42e6bb26a06ea2f196fbb28ef30c362fa9970034 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:09:57 -0400 Subject: [PATCH 15/27] create Slack v2 app and components from Slack v1 --- .../add-emoji-reaction/add-emoji-reaction.mjs | 48 + .../approve-workflow/approve-workflow.mjs | 92 ++ .../archive-channel/archive-channel.mjs | 38 + .../slack_v2/actions/common/build-blocks.mjs | 182 +++ .../slack_v2/actions/common/send-message.mjs | 262 ++++ .../actions/create-channel/create-channel.mjs | 40 + .../create-reminder/create-reminder.mjs | 52 + .../actions/delete-file/delete-file.mjs | 39 + .../actions/delete-message/delete-message.mjs | 45 + .../actions/find-message/find-message.mjs | 213 ++++ .../find-user-by-email/find-user-by-email.mjs | 32 + .../slack_v2/actions/get-file/get-file.mjs | 59 + .../invite-user-to-channel.mjs | 45 + .../slack_v2/actions/kick-user/kick-user.mjs | 56 + .../actions/list-channels/list-channels.mjs | 52 + .../actions/list-files/list-files.mjs | 93 ++ .../list-group-members/list-group-members.mjs | 68 + .../list-members-in-channel.mjs | 71 ++ .../actions/list-replies/list-replies.mjs | 74 ++ .../actions/list-users/list-users.mjs | 60 + .../reply-to-a-message/reply-to-a-message.mjs | 54 + .../send-block-kit-message.mjs | 48 + .../send-large-message/send-large-message.mjs | 95 ++ .../send-message-advanced.mjs | 75 ++ .../send-message-to-channel.mjs | 45 + .../send-message-to-user-or-group.mjs | 85 ++ .../actions/send-message/send-message.mjs | 56 + .../set-channel-description.mjs | 37 + .../set-channel-topic/set-channel-topic.mjs | 37 + .../actions/set-status/set-status.mjs | 49 + .../update-group-members.mjs | 72 ++ .../actions/update-message/update-message.mjs | 59 + .../actions/update-profile/update-profile.mjs | 92 ++ .../actions/upload-file/upload-file.mjs | 105 ++ components/slack_v2/common/constants.mjs | 31 + components/slack_v2/package.json | 22 + components/slack_v2/slack_v2.app.mjs | 1107 +++++++++++++++++ components/slack_v2/sources/common/base.mjs | 184 +++ .../slack_v2/sources/common/constants.mjs | 58 + .../new-channel-created.mjs | 32 + .../new-channel-created/test-event.mjs | 44 + .../new-interaction-event-received/README.md | 85 ++ .../new-interaction-event-received.mjs | 105 ++ .../test-event.mjs | 86 ++ .../new-keyword-mention.mjs | 99 ++ .../new-keyword-mention/test-event.mjs | 28 + .../new-message-in-channels.mjs | 106 ++ .../new-message-in-channels/test-event.mjs | 45 + .../new-reaction-added/new-reaction-added.mjs | 113 ++ .../sources/new-reaction-added/test-event.mjs | 193 +++ .../new-saved-message/new-saved-message.mjs | 32 + .../sources/new-saved-message/test-event.mjs | 37 + .../sources/new-user-added/new-user-added.mjs | 32 + .../sources/new-user-added/test-event.mjs | 48 + .../new-user-mention/new-user-mention.mjs | 124 ++ .../sources/new-user-mention/test-event.mjs | 28 + 56 files changed, 5169 insertions(+) create mode 100644 components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs create mode 100644 components/slack_v2/actions/approve-workflow/approve-workflow.mjs create mode 100644 components/slack_v2/actions/archive-channel/archive-channel.mjs create mode 100644 components/slack_v2/actions/common/build-blocks.mjs create mode 100644 components/slack_v2/actions/common/send-message.mjs create mode 100644 components/slack_v2/actions/create-channel/create-channel.mjs create mode 100644 components/slack_v2/actions/create-reminder/create-reminder.mjs create mode 100644 components/slack_v2/actions/delete-file/delete-file.mjs create mode 100644 components/slack_v2/actions/delete-message/delete-message.mjs create mode 100644 components/slack_v2/actions/find-message/find-message.mjs create mode 100644 components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs create mode 100644 components/slack_v2/actions/get-file/get-file.mjs create mode 100644 components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs create mode 100644 components/slack_v2/actions/kick-user/kick-user.mjs create mode 100644 components/slack_v2/actions/list-channels/list-channels.mjs create mode 100644 components/slack_v2/actions/list-files/list-files.mjs create mode 100644 components/slack_v2/actions/list-group-members/list-group-members.mjs create mode 100644 components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs create mode 100644 components/slack_v2/actions/list-replies/list-replies.mjs create mode 100644 components/slack_v2/actions/list-users/list-users.mjs create mode 100644 components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs create mode 100644 components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs create mode 100644 components/slack_v2/actions/send-large-message/send-large-message.mjs create mode 100644 components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs create mode 100644 components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs create mode 100644 components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs create mode 100644 components/slack_v2/actions/send-message/send-message.mjs create mode 100644 components/slack_v2/actions/set-channel-description/set-channel-description.mjs create mode 100644 components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs create mode 100644 components/slack_v2/actions/set-status/set-status.mjs create mode 100644 components/slack_v2/actions/update-group-members/update-group-members.mjs create mode 100644 components/slack_v2/actions/update-message/update-message.mjs create mode 100644 components/slack_v2/actions/update-profile/update-profile.mjs create mode 100644 components/slack_v2/actions/upload-file/upload-file.mjs create mode 100644 components/slack_v2/common/constants.mjs create mode 100644 components/slack_v2/package.json create mode 100644 components/slack_v2/slack_v2.app.mjs create mode 100644 components/slack_v2/sources/common/base.mjs create mode 100644 components/slack_v2/sources/common/constants.mjs create mode 100644 components/slack_v2/sources/new-channel-created/new-channel-created.mjs create mode 100644 components/slack_v2/sources/new-channel-created/test-event.mjs create mode 100644 components/slack_v2/sources/new-interaction-event-received/README.md create mode 100644 components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs create mode 100644 components/slack_v2/sources/new-interaction-event-received/test-event.mjs create mode 100644 components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs create mode 100644 components/slack_v2/sources/new-keyword-mention/test-event.mjs create mode 100644 components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs create mode 100644 components/slack_v2/sources/new-message-in-channels/test-event.mjs create mode 100644 components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs create mode 100644 components/slack_v2/sources/new-reaction-added/test-event.mjs create mode 100644 components/slack_v2/sources/new-saved-message/new-saved-message.mjs create mode 100644 components/slack_v2/sources/new-saved-message/test-event.mjs create mode 100644 components/slack_v2/sources/new-user-added/new-user-added.mjs create mode 100644 components/slack_v2/sources/new-user-added/test-event.mjs create mode 100644 components/slack_v2/sources/new-user-mention/new-user-mention.mjs create mode 100644 components/slack_v2/sources/new-user-mention/test-event.mjs diff --git a/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs new file mode 100644 index 0000000000000..1a2e2ed28bd1e --- /dev/null +++ b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -0,0 +1,48 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-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; + }, +}; diff --git a/components/slack_v2/actions/approve-workflow/approve-workflow.mjs b/components/slack_v2/actions/approve-workflow/approve-workflow.mjs new file mode 100644 index 0000000000000..79f4098cc307a --- /dev/null +++ b/components/slack_v2/actions/approve-workflow/approve-workflow.mjs @@ -0,0 +1,92 @@ +import slack from "../../slack_v2.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "slack-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; + }, +}; diff --git a/components/slack_v2/actions/archive-channel/archive-channel.mjs b/components/slack_v2/actions/archive-channel/archive-channel.mjs new file mode 100644 index 0000000000000..4488dc84de8e5 --- /dev/null +++ b/components/slack_v2/actions/archive-channel/archive-channel.mjs @@ -0,0 +1,38 @@ +import slack from "../../slack_v2.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "slack-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; + }, +}; diff --git a/components/slack_v2/actions/common/build-blocks.mjs b/components/slack_v2/actions/common/build-blocks.mjs new file mode 100644 index 0000000000000..fdbc9a70e4b17 --- /dev/null +++ b/components/slack_v2/actions/common/build-blocks.mjs @@ -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 + })); + + 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 { + 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; + }, +}; + diff --git a/components/slack_v2/actions/common/send-message.mjs b/components/slack_v2/actions/common/send-message.mjs new file mode 100644 index 0000000000000..b8ee311f1fb43 --- /dev/null +++ b/components/slack_v2/actions/common/send-message.mjs @@ -0,0 +1,262 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + props: { + slack, + as_user: { + propDefinition: [ + slack, + "as_user", + ], + }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", + ], + }, + post_at: { + propDefinition: [ + slack, + "post_at", + ], + }, + include_sent_via_pipedream_flag: { + type: "boolean", + optional: true, + default: true, + label: "Include link to Pipedream", + description: "Defaults to `true`, includes a link to Pipedream at the end of your Slack message.", + }, + customizeBotSettings: { + type: "boolean", + label: "Customize Bot Settings", + description: "Customize the username and/or icon of the Bot", + optional: true, + reloadProps: true, + }, + username: { + propDefinition: [ + slack, + "username", + ], + hidden: true, + }, + icon_emoji: { + propDefinition: [ + slack, + "icon_emoji", + ], + hidden: true, + }, + icon_url: { + propDefinition: [ + slack, + "icon_url", + ], + hidden: true, + }, + replyToThread: { + type: "boolean", + label: "Reply to Thread", + description: "Reply to an existing thread", + optional: true, + reloadProps: true, + }, + thread_ts: { + propDefinition: [ + slack, + "messageTs", + ], + description: "Provide another message's `ts` value to make this message a reply (e.g., if triggering on new Slack messages, enter `{{event.ts}}`). Avoid using a reply's `ts` value; use its parent instead. e.g. `1403051575.000407`.", + optional: true, + hidden: true, + }, + thread_broadcast: { + propDefinition: [ + slack, + "thread_broadcast", + ], + hidden: true, + }, + addMessageMetadata: { + type: "boolean", + label: "Add Message Metadata", + description: "Set the metadata event type and payload", + optional: true, + reloadProps: true, + }, + metadata_event_type: { + propDefinition: [ + slack, + "metadata_event_type", + ], + hidden: true, + }, + metadata_event_payload: { + propDefinition: [ + slack, + "metadata_event_payload", + ], + hidden: true, + }, + configureUnfurlSettings: { + type: "boolean", + label: "Configure Unfurl Settings", + description: "Configure settings for unfurling links and media", + optional: true, + reloadProps: true, + }, + unfurl_links: { + propDefinition: [ + slack, + "unfurl_links", + ], + hidden: true, + }, + unfurl_media: { + propDefinition: [ + slack, + "unfurl_media", + ], + hidden: true, + }, + }, + async additionalProps(props) { + if (this.conversation && this.replyToThread) { + props.thread_ts.hidden = false; + props.thread_broadcast.hidden = false; + } + if (this.customizeBotSettings) { + props.username.hidden = false; + props.icon_emoji.hidden = false; + props.icon_url.hidden = false; + } + if (this.addMessageMetadata) { + props.metadata_event_type.hidden = false; + props.metadata_event_payload.hidden = false; + } + if (this.configureUnfurlSettings) { + props.unfurl_links.hidden = false; + props.unfurl_media.hidden = false; + } + return {}; + }, + methods: { + _makeSentViaPipedreamBlock() { + const workflowId = process.env.PIPEDREAM_WORKFLOW_ID; + const baseLink = "https://pipedream.com"; + const linkText = !workflowId + ? "Pipedream Connect" + : "Pipedream"; + + const link = !workflowId + ? `${baseLink}/connect` + : `${baseLink}/@/${workflowId}?o=a&a=slack`; + + return { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": `Sent via <${link}|${linkText}>`, + }, + ], + }; + }, + _makeTextBlock(mrkdwn = true) { + const { text } = this; + let serializedText = text; + // The Slack SDK expects the value of text's "text" property to be a string. If this.text is + // anything other than string, number, or boolean, then encode it as a JSON string. + if (typeof text !== "string" && typeof text !== "number" && typeof text !== "boolean") { + serializedText = JSON.stringify(text); + } + return { + "type": "section", + "text": { + "type": mrkdwn + ? "mrkdwn" + : "plain_text", + "text": serializedText, + }, + }; + }, + getChannelId() { + return this.conversation ?? this.reply_channel; + }, + }, + async run({ $ }) { + const channelId = await this.getChannelId(); + + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + channelId, + ]); + } + + let blocks = this.blocks; + + if (!blocks) { + blocks = [ + this._makeTextBlock(this.mrkdwn), + ]; + } else if (typeof blocks === "string") { + blocks = JSON.parse(blocks); + } + + if (this.include_sent_via_pipedream_flag) { + const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); + blocks.push(sentViaPipedreamText); + } + + let metadataEventPayload; + + if (this.metadata_event_type) { + + if (typeof this.metadata_event_payload === "string") { + try { + metadataEventPayload = JSON.parse(this.metadata_event_payload); + } catch (error) { + throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); + } + } + + this.metadata = { + event_type: this.metadata_event_type, + event_payload: metadataEventPayload, + }; + } + + const obj = { + text: this.text, + channel: channelId, + attachments: this.attachments, + unfurl_links: this.unfurl_links, + unfurl_media: this.unfurl_media, + parse: this.parse, + as_user: this.as_user, + username: this.username, + icon_emoji: this.icon_emoji, + icon_url: this.icon_url, + mrkdwn: this.mrkdwn, + blocks, + link_names: this.link_names, + reply_broadcast: this.thread_broadcast, + thread_ts: this.thread_ts, + metadata: this.metadata || null, + }; + + if (this.post_at) { + obj.post_at = Math.floor(new Date(this.post_at).getTime() / 1000); + return await this.slack.scheduleMessage(obj); + } + const resp = await this.slack.postChatMessage(obj); + const { channel } = await this.slack.conversationsInfo({ + channel: resp.channel, + }); + const channelName = await this.slack.getChannelDisplayName(channel); + $.export("$summary", `Successfully sent a message to ${channelName}`); + return resp; + }, +}; diff --git a/components/slack_v2/actions/create-channel/create-channel.mjs b/components/slack_v2/actions/create-channel/create-channel.mjs new file mode 100644 index 0000000000000..22fb4896c577f --- /dev/null +++ b/components/slack_v2/actions/create-channel/create-channel.mjs @@ -0,0 +1,40 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-create-channel", + name: "Create a Channel", + description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)", + version: "0.0.26", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + channelName: { + label: "Channel name", + description: "Name of the public or private channel to create", + type: "string", + }, + isPrivate: { + label: "Is private?", + type: "boolean", + description: "`false` by default. Pass `true` to create a private channel instead of a public one.", + default: false, + optional: true, + }, + }, + async run({ $ }) { + // parse name + const name = this.channelName.replace(/\s+/g, "-").toLowerCase(); + + const response = await this.slack.createConversations({ + name, + is_private: this.isPrivate, + }); + $.export("$summary", `Successfully created channel ${this.channelName}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/create-reminder/create-reminder.mjs b/components/slack_v2/actions/create-reminder/create-reminder.mjs new file mode 100644 index 0000000000000..70f029f39c300 --- /dev/null +++ b/components/slack_v2/actions/create-reminder/create-reminder.mjs @@ -0,0 +1,52 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-create-reminder", + name: "Create Reminder", + description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)", + version: "0.0.26", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + text: { + propDefinition: [ + slack, + "text", + ], + }, + timestamp: { + type: "string", + label: "Timestamp", + description: "When this reminder should happen: the Unix timestamp (up to five years from now), the number of seconds until the reminder (if within 24 hours), or a natural language description (Ex. in 15 minutes, or every Thursday)", + }, + team_id: { + propDefinition: [ + slack, + "team", + ], + optional: true, + }, + user: { + propDefinition: [ + slack, + "user", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.slack.addReminders({ + text: this.text, + team_id: this.team_id, + time: this.timestamp, + user: this.user, + }); + $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/delete-file/delete-file.mjs b/components/slack_v2/actions/delete-file/delete-file.mjs new file mode 100644 index 0000000000000..daa1cd53c54c4 --- /dev/null +++ b/components/slack_v2/actions/delete-file/delete-file.mjs @@ -0,0 +1,39 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-delete-file", + name: "Delete File", + description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)", + version: "0.0.25", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + file: { + propDefinition: [ + slack, + "file", + (c) => ({ + channel: c.conversation, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.slack.deleteFiles({ + file: this.file, + }); + $.export("$summary", `Successfully deleted file with ID ${this.file}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/delete-message/delete-message.mjs b/components/slack_v2/actions/delete-message/delete-message.mjs new file mode 100644 index 0000000000000..d4abaebb2404b --- /dev/null +++ b/components/slack_v2/actions/delete-message/delete-message.mjs @@ -0,0 +1,45 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-delete-message", + name: "Delete Message", + description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)", + version: "0.1.0", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + timestamp: { + propDefinition: [ + slack, + "messageTs", + ], + }, + as_user: { + propDefinition: [ + slack, + "as_user", + ], + description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.", + }, + }, + async run({ $ }) { + const response = await this.slack.deleteMessage({ + channel: this.conversation, + ts: this.timestamp, + as_user: this.as_user, + }); + $.export("$summary", "Successfully deleted message."); + return response; + }, +}; diff --git a/components/slack_v2/actions/find-message/find-message.mjs b/components/slack_v2/actions/find-message/find-message.mjs new file mode 100644 index 0000000000000..81ac2b7eff7d4 --- /dev/null +++ b/components/slack_v2/actions/find-message/find-message.mjs @@ -0,0 +1,213 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-find-message", + name: "Find Message", + description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + query: { + propDefinition: [ + slack, + "query", + ], + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of messages to return", + default: 20, + optional: true, + }, + sort: { + type: "string", + label: "Sort", + description: "Return matches sorted by either `score` or `timestamp`", + options: [ + "score", + "timestamp", + ], + optional: true, + }, + sortDirection: { + type: "string", + label: "Sort Direction", + description: "Sort ascending (asc) or descending (desc)`", + options: [ + "desc", + "asc", + ], + optional: true, + }, + }, + methods: { + normalizeAssistantMatch(match) { + if (!match || typeof match !== "object") { + return match; + } + const { + author_user_id: authorUserId, + team_id: teamId, + channel_id: channelId, + message_ts: messageTs, + content, + permalink, + is_author_bot: isAuthorBot, + message, + channel, + ...rest + } = match; + const baseMessage = typeof message === "object" + ? message + : {}; + const channelInfo = channel && typeof channel === "object" + ? { + ...channel, + id: channel.id || channelId, + } + : channelId + ? { + id: channelId, + } + : undefined; + const normalized = { + type: "message", + user: authorUserId, + team: teamId, + ts: messageTs, + text: content, + permalink, + channel: channelInfo, + ...baseMessage, + ...rest, + }; + if (isAuthorBot !== undefined && normalized.is_author_bot === undefined) { + normalized.is_author_bot = isAuthorBot; + } + if (normalized.text == null) { + normalized.text = baseMessage.text || content; + } + if (normalized.ts == null) { + normalized.ts = baseMessage.ts || messageTs; + } + if (!normalized.channel && baseMessage.channel) { + normalized.channel = baseMessage.channel; + } else if (normalized.channel && baseMessage.channel && typeof baseMessage.channel === "object") { + normalized.channel = { + ...normalized.channel, + ...baseMessage.channel, + }; + } + return normalized; + }, + async searchWithAssistant(baseParams, maxResults) { + const matches = []; + let cursor; + + do { + const response = await this.slack.assistantSearch({ + ...baseParams, + channel_types: "public_channel,private_channel", + cursor, + }); + const messages = (response.results?.messages || []) + .map((item) => this.normalizeAssistantMatch(item)); + matches.push(...messages); + cursor = response.response_metadata?.next_cursor; + } while (cursor && matches.length < maxResults); + + return matches.slice(0, maxResults); + }, + async searchWithSearchMessages(baseParams, maxResults) { + const matches = []; + let page = 1; + const count = Math.min(Math.max(maxResults, 1), 100); + + while (matches.length < maxResults) { + const response = await this.slack.searchMessages({ + ...baseParams, + count, + page, + }); + const pageMatches = response.messages?.matches || []; + matches.push(...pageMatches); + + if (matches.length >= maxResults) { + break; + } + + const pagination = response.messages?.pagination; + const paging = response.messages?.paging; + const hasMore = pagination + ? pagination.page < pagination.page_count + : paging + ? paging.page < paging.pages + : false; + + if (!hasMore) { + break; + } + + page += 1; + } + + return matches.slice(0, maxResults); + }, + shouldFallbackToSearchMessages(error) { + const errorCode = typeof error === "string" + ? error + : error?.data?.error || error?.message; + + if (!errorCode?.includes("missing_scope")) { + return false; + } + + const providedSources = [ + error?.data?.provided, + error?.provided, + error?.original?.data?.provided, + ].filter(Boolean); + + const providedScopes = providedSources + .flatMap((value) => Array.isArray(value) + ? value + : String(value).split(",")) + .map((scope) => scope.trim()) + .filter(Boolean); + + return providedScopes.includes("search:read"); + }, + }, + async run({ $ }) { + const maxResults = Math.max(this.maxResults ?? 20, 1); + const baseParams = { + query: this.query, + sort: this.sort, + sort_dir: this.sortDirection, + }; + let matches; + + try { + matches = await this.searchWithAssistant(baseParams, maxResults); + } catch (error) { + if (this.shouldFallbackToSearchMessages(error)) { + matches = await this.searchWithSearchMessages(baseParams, maxResults); + } else { + throw error; + } + } + + $.export("$summary", `Found ${matches.length} matching message${matches.length === 1 + ? "" + : "s"}`); + + return matches; + }, +}; diff --git a/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs b/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs new file mode 100644 index 0000000000000..42741b153a845 --- /dev/null +++ b/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs @@ -0,0 +1,32 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-find-user-by-email", + name: "Find User by Email", + description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + email: { + propDefinition: [ + slack, + "email", + ], + }, + }, + async run({ $ }) { + const response = await this.slack.lookupUserByEmail({ + email: this.email, + }); + if (response.ok) { + $.export("$summary", `Successfully found user with ID ${response.user.id}`); + } + return response; + }, +}; diff --git a/components/slack_v2/actions/get-file/get-file.mjs b/components/slack_v2/actions/get-file/get-file.mjs new file mode 100644 index 0000000000000..27de0cfa81287 --- /dev/null +++ b/components/slack_v2/actions/get-file/get-file.mjs @@ -0,0 +1,59 @@ +import constants from "../../common/constants.mjs"; +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-get-file", + name: "Get File", + description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", + ], + }, + file: { + propDefinition: [ + slack, + "file", + (c) => ({ + channel: c.conversation, + }), + ], + }, + }, + async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + + const response = await this.slack.getFileInfo({ + file: this.file, + }); + $.export("$summary", `Successfully retrieved file with ID ${this.file}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs new file mode 100644 index 0000000000000..750eebe996b4f --- /dev/null +++ b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -0,0 +1,45 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-invite-user-to-channel", + name: "Invite User to Channel", + description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + user: { + propDefinition: [ + slack, + "user", + ], + }, + }, + async run({ $ }) { + try { + const response = await this.slack.inviteToConversation({ + channel: this.conversation, + users: this.user, + }); + $.export("$summary", `Successfully invited user ${this.user} to channel with ID ${this.conversation}`); + return response; + } catch (error) { + if (error.includes("already_in_channel")) { + $.export("$summary", `The user ${this.user} is already in the channel`); + return; + } + throw error; + } + }, +}; diff --git a/components/slack_v2/actions/kick-user/kick-user.mjs b/components/slack_v2/actions/kick-user/kick-user.mjs new file mode 100644 index 0000000000000..48af52c4607d3 --- /dev/null +++ b/components/slack_v2/actions/kick-user/kick-user.mjs @@ -0,0 +1,56 @@ +import slack from "../../slack_v2.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "slack-kick-user", + name: "Kick User", + description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)", + 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, + ], + }), + ], + }, + user: { + propDefinition: [ + slack, + "user", + (c) => ({ + channelId: c.conversation, + }), + ], + }, + }, + async run({ $ }) { + try { + const response = await this.slack.kickUserFromConversation({ + channel: this.conversation, + user: this.user, + }); + $.export("$summary", `Successfully kicked user ${this.user} from channel with ID ${this.conversation}`); + return response; + } catch (error) { + if (error.includes("not_in_channel")) { + $.export("$summary", `The user ${this.user} is not in the channel`); + return; + } + throw error; + } + }, +}; diff --git a/components/slack_v2/actions/list-channels/list-channels.mjs b/components/slack_v2/actions/list-channels/list-channels.mjs new file mode 100644 index 0000000000000..e00ca0f16e459 --- /dev/null +++ b/components/slack_v2/actions/list-channels/list-channels.mjs @@ -0,0 +1,52 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-channels", + name: "List Channels", + description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + const allChannels = []; + const params = { + limit: this.pageSize, + }; + let page = 0; + + do { + const { + channels, response_metadata: { next_cursor: nextCursor }, + } = await this.slack.conversationsList(params); + allChannels.push(...channels); + params.cursor = nextCursor; + page++; + } while (params.cursor && page < this.numPages); + + $.export("$summary", `Successfully found ${allChannels.length} channel${allChannels.length === 1 + ? "" + : "s"}`); + return { + channels: allChannels, + }; + }, +}; diff --git a/components/slack_v2/actions/list-files/list-files.mjs b/components/slack_v2/actions/list-files/list-files.mjs new file mode 100644 index 0000000000000..788fb6be3a329 --- /dev/null +++ b/components/slack_v2/actions/list-files/list-files.mjs @@ -0,0 +1,93 @@ +import constants from "../../common/constants.mjs"; +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-files", + name: "List Files", + description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + addToChannel: { + propDefinition: [ + slack, + "addToChannel", + ], + }, + team_id: { + propDefinition: [ + slack, + "team", + ], + optional: true, + }, + user: { + propDefinition: [ + slack, + "user", + ], + optional: true, + }, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + + const allFiles = []; + const params = { + channel: this.conversation, + user: this.user, + team_id: this.team_id, + page: 1, + count: this.pageSize, + }; + let hasMore; + + do { + const { files } = await this.slack.listFiles(params); + allFiles.push(...files); + hasMore = files.length; + params.page++; + } while (hasMore && params.page <= this.numPages); + + $.export("$summary", `Successfully retrieved ${allFiles.length} file${allFiles.length === 1 + ? "" + : "s"}`); + + return allFiles; + }, +}; diff --git a/components/slack_v2/actions/list-group-members/list-group-members.mjs b/components/slack_v2/actions/list-group-members/list-group-members.mjs new file mode 100644 index 0000000000000..f0942fa2e4f66 --- /dev/null +++ b/components/slack_v2/actions/list-group-members/list-group-members.mjs @@ -0,0 +1,68 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-group-members", + name: "List Group Members", + description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)", + version: "0.0.10", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + userGroup: { + propDefinition: [ + slack, + "userGroup", + ], + }, + team: { + propDefinition: [ + slack, + "team", + ], + optional: true, + description: "Encoded team id where the user group exists, required if org token is used.", + }, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + const members = []; + const params = { + usergroup: this.userGroup, + team_id: this.team, + limit: this.pageSize, + }; + let page = 0; + + do { + const { + users, response_metadata: { next_cursor: nextCursor }, + } = await this.slack.listGroupMembers(params); + members.push(...users); + params.cursor = nextCursor; + page++; + } while (params.cursor && page < this.numPages); + + if (members?.length) { + $.export("$summary", `Successfully retrieved ${members.length} user${members.length === 1 + ? "" + : "s"}`); + } + return members; + }, +}; diff --git a/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs b/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs new file mode 100644 index 0000000000000..aab1cfb64f69c --- /dev/null +++ b/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs @@ -0,0 +1,71 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-members-in-channel", + name: "List Members in Channel", + description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + returnUsernames: { + type: "boolean", + label: "Return Usernames", + description: "Optionally, return usernames in addition to IDs", + optional: true, + }, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + let channelMembers = []; + const params = { + channel: this.conversation, + limit: this.pageSize, + }; + let page = 0; + + do { + const { + members, response_metadata: { next_cursor: nextCursor }, + } = await this.slack.listChannelMembers(params); + channelMembers.push(...members); + params.cursor = nextCursor; + page++; + } while (params.cursor && page < this.numPages); + + if (this.returnUsernames) { + const usernames = await this.slack.userNameLookup(channelMembers); + channelMembers = channelMembers?.map((id) => ({ + id, + username: usernames[id], + })) || []; + } + + $.export("$summary", `Successfully retrieved ${channelMembers.length} member${channelMembers.length === 1 + ? "" + : "s"}`); + return channelMembers; + }, +}; diff --git a/components/slack_v2/actions/list-replies/list-replies.mjs b/components/slack_v2/actions/list-replies/list-replies.mjs new file mode 100644 index 0000000000000..407c09d4023ad --- /dev/null +++ b/components/slack_v2/actions/list-replies/list-replies.mjs @@ -0,0 +1,74 @@ +import constants from "../../common/constants.mjs"; +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-replies", + name: "List Replies", + description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + timestamp: { + propDefinition: [ + slack, + "messageTs", + ], + }, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + const replies = []; + const params = { + channel: this.conversation, + ts: this.timestamp, + limit: this.pageSize, + }; + let page = 0; + + do { + const { + messages, response_metadata: { next_cursor: nextCursor }, + } = await this.slack.getConversationReplies(params); + replies.push(...messages); + params.cursor = nextCursor; + page++; + } while (params.cursor && page < this.numPages); + + $.export("$summary", `Successfully retrieved ${replies.length} reply message${replies.length === 1 + ? "" + : "s"}`); + return { + messages: replies, + }; + }, +}; diff --git a/components/slack_v2/actions/list-users/list-users.mjs b/components/slack_v2/actions/list-users/list-users.mjs new file mode 100644 index 0000000000000..d84671fb82f22 --- /dev/null +++ b/components/slack_v2/actions/list-users/list-users.mjs @@ -0,0 +1,60 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-list-users", + name: "List Users", + description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)", + version: "0.0.25", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + teamId: { + propDefinition: [ + slack, + "team", + ], + optional: true, + }, + pageSize: { + propDefinition: [ + slack, + "pageSize", + ], + }, + numPages: { + propDefinition: [ + slack, + "numPages", + ], + }, + }, + async run({ $ }) { + const users = []; + const params = { + team_id: this.teamId, + limit: this.pageSize, + }; + let page = 0; + + do { + const { + members, response_metadata: { next_cursor: nextCursor }, + } = await this.slack.usersList(params); + users.push(...members); + params.cursor = nextCursor; + page++; + } while (params.cursor && page < this.numPages); + + $.export("$summary", `Successfully retrieved ${users.length} user${users.length === 1 + ? "" + : "s"}`); + return { + members: users, + }; + }, +}; diff --git a/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs b/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs new file mode 100644 index 0000000000000..c7d94a22d7b7f --- /dev/null +++ b/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs @@ -0,0 +1,54 @@ +import slack from "../../slack_v2.app.mjs"; +import common from "../common/send-message.mjs"; + +export default { + ...common, + key: "slack-reply-to-a-message", + name: "Reply to a Message Thread", + description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", + version: "0.2.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + text: { + propDefinition: [ + slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + slack, + "mrkdwn", + ], + }, + ...common.props, + replyToThread: { + ...common.props.replyToThread, + hidden: true, + }, + thread_ts: { + propDefinition: [ + slack, + "messageTs", + ], + }, + thread_broadcast: { + propDefinition: [ + slack, + "thread_broadcast", + ], + }, + }, +}; diff --git a/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs b/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs new file mode 100644 index 0000000000000..7fa53d5fc6559 --- /dev/null +++ b/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs @@ -0,0 +1,48 @@ +import buildBlocks from "../common/build-blocks.mjs"; +import common from "../common/send-message.mjs"; + +export default { + ...common, + ...buildBlocks, + key: "slack-send-block-kit-message", + name: "Build and Send a Block Kit Message", + description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).", + version: "0.5.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + ], + }, + text: { + type: "string", + label: "Notification Text", + description: "Optionally provide a string for Slack to display as the new message notification (if you do not provide this, notification will be blank).", + optional: true, + }, + ...common.props, + ...buildBlocks.props, + }, + methods: { + ...common.methods, + ...buildBlocks.methods, + async getGeneratedBlocks() { + return await buildBlocks.run.call(this); // call buildBlocks.run with the current context + }, + }, + async run({ $ }) { + this.blocks = await this.getGeneratedBlocks(); // set the blocks prop for common.run to use + const resp = await common.run.call(this, { + $, + }); // call common.run with the current context + return resp; + }, +}; diff --git a/components/slack_v2/actions/send-large-message/send-large-message.mjs b/components/slack_v2/actions/send-large-message/send-large-message.mjs new file mode 100644 index 0000000000000..474230475d486 --- /dev/null +++ b/components/slack_v2/actions/send-large-message/send-large-message.mjs @@ -0,0 +1,95 @@ +import common from "../common/send-message.mjs"; + +export default { + ...common, + key: "slack-send-large-message", + name: "Send a Large Message (3000+ characters)", + description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + ], + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + }, + async run({ $ }) { + if (this.addToChannel) { + await this.slack.maybeAddAppToChannels([ + this.conversation, + ]); + } + + if (this.include_sent_via_pipedream_flag) { + const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); + this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`; + } + + let metadataEventPayload; + + if (this.metadata_event_type) { + if (typeof this.metadata_event_payload === "string") { + try { + metadataEventPayload = JSON.parse(this.metadata_event_payload); + } catch (error) { + throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); + } + } + + this.metadata = { + event_type: this.metadata_event_type, + event_payload: metadataEventPayload, + }; + } + + const obj = { + text: this.text, + channel: this.conversation, + as_user: this.as_user, + username: this.username, + icon_emoji: this.icon_emoji, + icon_url: this.icon_url, + mrkdwn: this.mrkdwn, + metadata: this.metadata || null, + reply_broadcast: this.thread_broadcast, + thread_ts: this.thread_ts, + unfurl_links: this.unfurl_links, + unfurl_media: this.unfurl_media, + }; + + let response; + if (this.post_at) { + obj.post_at = this.post_at; + response = await this.slack.scheduleMessage(obj); + } else { + response = await this.slack.postChatMessage(obj); + } + const { channel } = await this.slack.conversationsInfo({ + channel: response.channel, + }); + const channelName = await this.slack.getChannelDisplayName(channel); + $.export("$summary", `Successfully sent a message to ${channelName}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs new file mode 100644 index 0000000000000..6b8df73332ac4 --- /dev/null +++ b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs @@ -0,0 +1,75 @@ +import common from "../common/send-message.mjs"; +import buildBlocks from "../common/build-blocks.mjs"; + +export default { + ...common, + ...buildBlocks, + key: "slack-send-message-advanced", + name: "Send Message (Advanced)", + description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + ], + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + description: "If you're using `blocks`, this is used as a fallback string to display in notifications. If you aren't, this is the main body text of the message. It can be formatted as plain text, or with mrkdwn.", + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + attachments: { + propDefinition: [ + common.props.slack, + "attachments", + ], + }, + parse: { + propDefinition: [ + common.props.slack, + "parse", + ], + }, + link_names: { + propDefinition: [ + common.props.slack, + "link_names", + ], + }, + ...common.props, + ...buildBlocks.props, + }, + methods: { + ...common.methods, + ...buildBlocks.methods, + async getGeneratedBlocks() { + return await buildBlocks.run.call(this); // call buildBlocks.run with the current context + }, + }, + async run({ $ }) { + if (this.passArrayOrConfigure) { + this.blocks = await this.getGeneratedBlocks(); // set the blocks prop for common.run to use + } + const resp = await common.run.call(this, { + $, + }); // call common.run with the current context + return resp; + }, +}; diff --git a/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs b/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs new file mode 100644 index 0000000000000..ac00b08c5e5b8 --- /dev/null +++ b/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs @@ -0,0 +1,45 @@ +import common from "../common/send-message.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-send-message-to-channel", + name: "Send Message to Channel", + description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + description: "Select a public or private channel", + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + }, +}; diff --git a/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs new file mode 100644 index 0000000000000..60834909dec8a --- /dev/null +++ b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -0,0 +1,85 @@ +import common from "../common/send-message.mjs"; +import constants from "../../common/constants.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + ...common, + key: "slack-send-message-to-user-or-group", + name: "Send Message to User or Group", + description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.props.slack, + users: { + propDefinition: [ + common.props.slack, + "user", + ], + type: "string[]", + label: "Users", + description: "Select the user(s) to message", + optional: true, + }, + conversation: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + constants.CHANNEL_TYPE.MPIM, + ], + }), + ], + description: "Select the group to message", + optional: true, + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + // eslint-disable-next-line pipedream/props-label, pipedream/props-description + addToChannel: { + type: "boolean", + ...common.props.addToChannel, + disabled: true, + hidden: true, + }, + }, + methods: { + ...common.methods, + openConversation(args = {}) { + return this.slack.makeRequest({ + method: "conversations.open", + ...args, + }); + }, + async getChannelId() { + if (!this.conversation && !this.users?.length) { + throw new ConfigurationError("Must select a group or user(s) to message"); + } + + if (this.conversation) { + return this.conversation; + } + const { channel: { id } } = await this.openConversation({ + users: this.users.join(), + }); + return id; + }, + }, +}; diff --git a/components/slack_v2/actions/send-message/send-message.mjs b/components/slack_v2/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..e8a67df460932 --- /dev/null +++ b/components/slack_v2/actions/send-message/send-message.mjs @@ -0,0 +1,56 @@ +import common from "../common/send-message.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-send-message", + name: "Send Message", + description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", + version: "0.1.0", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack: common.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: [ + common.props.slack, + "conversation", + (c) => ({ + types: c.channelType === "Channels" + ? [ + constants.CHANNEL_TYPE.PUBLIC, + constants.CHANNEL_TYPE.PRIVATE, + ] + : [ + c.channelType, + ], + }), + ], + }, + text: { + propDefinition: [ + common.props.slack, + "text", + ], + }, + mrkdwn: { + propDefinition: [ + common.props.slack, + "mrkdwn", + ], + }, + ...common.props, + }, +}; diff --git a/components/slack_v2/actions/set-channel-description/set-channel-description.mjs b/components/slack_v2/actions/set-channel-description/set-channel-description.mjs new file mode 100644 index 0000000000000..2e6214350c7af --- /dev/null +++ b/components/slack_v2/actions/set-channel-description/set-channel-description.mjs @@ -0,0 +1,37 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-set-channel-description", + name: "Set Channel Description", + description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)", + version: "0.0.10", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + purpose: { + propDefinition: [ + slack, + "purpose", + ], + }, + }, + async run({ $ }) { + const response = await this.slack.setChannelDescription({ + channel: this.conversation, + purpose: this.purpose, + }); + $.export("$summary", `Successfully set description for channel with ID ${this.conversation}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs b/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs new file mode 100644 index 0000000000000..020f455c4870b --- /dev/null +++ b/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs @@ -0,0 +1,37 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-set-channel-topic", + name: "Set Channel Topic", + description: "Set the topic on a selected channel. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", + version: "0.0.25", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + topic: { + propDefinition: [ + slack, + "topic", + ], + }, + }, + async run({ $ }) { + const response = await this.slack.setChannelTopic({ + channel: this.conversation, + topic: this.topic, + }); + $.export("$summary", `Successfully set topic for channel with ID ${this.conversation}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/set-status/set-status.mjs b/components/slack_v2/actions/set-status/set-status.mjs new file mode 100644 index 0000000000000..69df31b962c6e --- /dev/null +++ b/components/slack_v2/actions/set-status/set-status.mjs @@ -0,0 +1,49 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-set-status", + name: "Set Status", + description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)", + version: "0.0.10", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + statusText: { + type: "string", + label: "Status Text", + description: "The displayed text", + }, + statusEmoji: { + propDefinition: [ + slack, + "icon_emoji", + ], + label: "Status Emoji", + description: "The emoji to display with the status", + optional: true, + }, + statusExpiration: { + type: "string", + label: "Status Expiration", + description: "The datetime of when the status will expire in ISO 8601 format. (Example: `2014-01-01T00:00:00Z`)", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.slack.updateProfile({ + profile: { + status_text: this.statusText, + status_emoji: this.statusEmoji && `:${this.statusEmoji}:`, + status_expiration: this.statusExpiration + && Math.floor(new Date(this.statusExpiration).getTime() / 1000), + }, + }); + $.export("$summary", "Successfully updated status."); + return response; + }, +}; diff --git a/components/slack_v2/actions/update-group-members/update-group-members.mjs b/components/slack_v2/actions/update-group-members/update-group-members.mjs new file mode 100644 index 0000000000000..1414f0ba0592a --- /dev/null +++ b/components/slack_v2/actions/update-group-members/update-group-members.mjs @@ -0,0 +1,72 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-update-group-members", + name: "Update Groups Members", + description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", + version: "0.0.10", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + userGroup: { + propDefinition: [ + slack, + "userGroup", + ], + }, + usersToAdd: { + propDefinition: [ + slack, + "user", + ], + type: "string[]", + label: "Users to Add", + description: "A list of encoded user IDs that represent the users to add to the group.", + optional: true, + }, + usersToRemove: { + propDefinition: [ + slack, + "user", + ], + type: "string[]", + label: "Users to Remove", + description: "A list of encoded user IDs that represent the users to remove from the group.", + optional: true, + }, + team: { + propDefinition: [ + slack, + "team", + ], + optional: true, + description: "Encoded team id where the user group exists, required if org token is used.", + }, + }, + async run({ $ }) { + const { + userGroup, + usersToAdd = [], + usersToRemove = [], + team, + } = this; + let { users } = await this.slack.listGroupMembers({ + usergroup: userGroup, + team_id: team, + }); + users = users.filter((user) => !usersToRemove.includes(user)); + users.push(...usersToAdd); + const response = await this.slack.updateGroupMembers({ + usergroup: userGroup, + users, + team_id: team, + }); + $.export("$summary", `Successfully updated members of group with ID ${this.userGroup}`); + return response; + }, +}; diff --git a/components/slack_v2/actions/update-message/update-message.mjs b/components/slack_v2/actions/update-message/update-message.mjs new file mode 100644 index 0000000000000..d15d456eed7aa --- /dev/null +++ b/components/slack_v2/actions/update-message/update-message.mjs @@ -0,0 +1,59 @@ +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-update-message", + name: "Update Message", + description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)", + version: "0.2.0", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + timestamp: { + propDefinition: [ + slack, + "messageTs", + ], + }, + text: { + propDefinition: [ + slack, + "text", + ], + }, + as_user: { + propDefinition: [ + slack, + "as_user", + ], + description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.", + }, + attachments: { + propDefinition: [ + slack, + "attachments", + ], + }, + }, + async run({ $ }) { + const response = await this.slack.updateMessage({ + ts: this.timestamp, + text: this.text, + channel: this.conversation, + as_user: this.as_user, + attachments: this.attachments, + }); + $.export("$summary", "Successfully updated message"); + return response; + }, +}; diff --git a/components/slack_v2/actions/update-profile/update-profile.mjs b/components/slack_v2/actions/update-profile/update-profile.mjs new file mode 100644 index 0000000000000..ae11c92c959d7 --- /dev/null +++ b/components/slack_v2/actions/update-profile/update-profile.mjs @@ -0,0 +1,92 @@ +import slack from "../../slack_v2.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "slack-update-profile", + name: "Update Profile", + description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", + version: "0.0.25", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + displayName: { + type: "string", + label: "Display Name", + description: "The display name the user has chosen to identify themselves by in their workspace profile", + optional: true, + }, + firstName: { + type: "string", + label: "First Name", + description: "The user's first name", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "The user's last name", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The user's phone number, in any format", + optional: true, + }, + pronouns: { + type: "string", + label: "Pronouns", + description: "The user's pronouns", + optional: true, + }, + title: { + type: "string", + label: "Title", + description: "The user's title", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The user's email address. You cannot update your own email using this method. This field can only be changed by admins for users on paid teams.", + optional: true, + }, + user: { + propDefinition: [ + slack, + "user", + ], + description: "ID of user to change. This argument may only be specified by admins on paid teams.", + optional: true, + }, + }, + async run({ $ }) { + if (!this.displayName + && !this.firstName + && !this.lastName + && !this.phone + && !this.pronouns + && !this.title + ) { + throw new ConfigurationError("Please provide at least one value to update"); + } + const response = await this.slack.updateProfile({ + profile: { + display_name: this.displayName, + first_name: this.firstName, + last_name: this.lastName, + phone: this.phone, + pronouns: this.pronouns, + title: this.title, + }, + user: this.user, + }); + $.export("$summary", "Successfully updated profile"); + return response; + }, +}; diff --git a/components/slack_v2/actions/upload-file/upload-file.mjs b/components/slack_v2/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..d5f752507b322 --- /dev/null +++ b/components/slack_v2/actions/upload-file/upload-file.mjs @@ -0,0 +1,105 @@ +import { + ConfigurationError, axios, getFileStreamAndMetadata, +} from "@pipedream/platform"; +import FormData from "form-data"; +import slack from "../../slack_v2.app.mjs"; + +export default { + key: "slack-upload-file", + name: "Upload File", + description: "Upload a file. [See the documentation](https://api.slack.com/messaging/files#uploading_files)", + version: "0.1.3", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + type: "action", + props: { + slack, + conversation: { + propDefinition: [ + slack, + "conversation", + ], + }, + content: { + propDefinition: [ + slack, + "content", + ], + }, + initialComment: { + description: "Will be added as an initial comment before the image", + propDefinition: [ + slack, + "initial_comment", + ], + optional: true, + }, + syncDir: { + type: "dir", + accessMode: "read", + sync: true, + optional: true, + }, + }, + async run({ $ }) { + const { + stream, metadata, + } = await getFileStreamAndMetadata(this.content); + + const filename = this.content.split("/").pop(); + + // Get an upload URL from Slack + const getUploadUrlResponse = await this.slack.getUploadUrl({ + filename, + length: metadata.size, + }); + + if (!getUploadUrlResponse.ok) { + throw new ConfigurationError(`Error getting upload URL: ${JSON.stringify(getUploadUrlResponse)}`); + } + + const { + upload_url: uploadUrl, file_id: fileId, + } = getUploadUrlResponse; + + // Upload the file to the provided URL + const formData = new FormData(); + formData.append("file", stream, { + contentType: metadata.contentType, + knownLength: metadata.size, + filename: metadata.name, + }); + formData.append("filename", filename); + + await axios($, { + url: uploadUrl, + data: formData, + method: "POST", + headers: { + ...formData.getHeaders(), + Authorization: `Bearer ${this.slack.getToken()}`, + }, + }); + + // Complete the file upload process in Slack + const completeUploadResponse = await this.slack.completeUpload({ + channel_id: this.conversation, + initial_comment: this.initialComment, + files: [ + { + id: fileId, + }, + ], + }); + + if (!completeUploadResponse.ok) { + throw new Error(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`); + } + + $.export("$summary", "Successfully uploaded file"); + return completeUploadResponse; + }, +}; diff --git a/components/slack_v2/common/constants.mjs b/components/slack_v2/common/constants.mjs new file mode 100644 index 0000000000000..7d64f48d573d7 --- /dev/null +++ b/components/slack_v2/common/constants.mjs @@ -0,0 +1,31 @@ +const MAX_RESOURCES = 800; +const LIMIT = 250; + +const CHANNEL_TYPE = { + PUBLIC: "public_channel", + PRIVATE: "private_channel", + MPIM: "mpim", + IM: "im", +}; + +const CHANNEL_TYPE_OPTIONS = [ + { + label: "Channels", + value: "Channels", + }, + { + label: "Group", + value: CHANNEL_TYPE.MPIM, + }, + { + label: "User / Direct Message", + value: CHANNEL_TYPE.IM, + }, +]; + +export default { + MAX_RESOURCES, + LIMIT, + CHANNEL_TYPE, + CHANNEL_TYPE_OPTIONS, +}; diff --git a/components/slack_v2/package.json b/components/slack_v2/package.json new file mode 100644 index 0000000000000..da173e0513ba4 --- /dev/null +++ b/components/slack_v2/package.json @@ -0,0 +1,22 @@ +{ + "name": "@pipedream/slack_v2", + "version": "0.0.1", + "description": "Pipedream Slack_v2 Components", + "main": "slack_v2.app.mjs", + "keywords": [ + "pipedream", + "slack_v2", + "slack" + ], + "homepage": "https://pipedream.com/apps/slack_v2", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0", + "@slack/web-api": "^7.9.0", + "async-retry": "^1.3.3", + "lodash": "^4.17.21" + } +} diff --git a/components/slack_v2/slack_v2.app.mjs b/components/slack_v2/slack_v2.app.mjs new file mode 100644 index 0000000000000..570a503e828b5 --- /dev/null +++ b/components/slack_v2/slack_v2.app.mjs @@ -0,0 +1,1107 @@ +import { WebClient } from "@slack/web-api"; +import constants from "./common/constants.mjs"; +import get from "lodash/get.js"; +import retry from "async-retry"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + type: "app", + app: "slack_v2", + propDefinitions: { + user: { + type: "string", + label: "User", + description: "Select a user", + async options({ + prevContext, channelId, + }) { + const types = [ + "im", + ]; + let conversationsResp + = await this.availableConversations(types.join(), prevContext.cursor, true); + if (channelId) { + const { members } = await this.listChannelMembers({ + channel: channelId, + throwRateLimitError: true, + }); + conversationsResp.conversations = conversationsResp.conversations + .filter((c) => members.includes(c.user || c.id)); + } + const userIds = conversationsResp.conversations.map(({ user }) => user).filter(Boolean); + const realNames = await this.realNameLookup(userIds); + return { + options: conversationsResp.conversations.filter((c) => c.user).map((c) => ({ + label: `${realNames[c.user]}`, + value: c.user || c.id, + })), + context: { + cursor: conversationsResp.cursor, + }, + }; + }, + }, + group: { + type: "string", + label: "Group", + description: "Select a group", + async options({ prevContext }) { + let { cursor } = prevContext; + const types = [ + "mpim", + ]; + const resp = await this.availableConversations(types.join(), cursor, true); + return { + options: resp.conversations.map((c) => { + return { + label: c.purpose.value, + value: c.id, + }; + }), + context: { + cursor: resp.cursor, + }, + }; + }, + }, + userGroup: { + type: "string", + label: "User Group", + description: "The encoded ID of the User Group.", + async options() { + const { usergroups } = await this.usergroupsList({ + throwRateLimitError: true, + }); + return usergroups.map((c) => ({ + label: c.name, + value: c.id, + })); + }, + }, + reminder: { + type: "string", + label: "Reminder", + description: "Select a reminder", + async options() { + const { reminders } = await this.remindersList({ + throwRateLimitError: true, + }); + return reminders.map((c) => ({ + label: c.text, + value: c.id, + })); + }, + }, + conversation: { + type: "string", + label: "Channel", + description: "Select a public or private channel, or a user or group", + async options({ + prevContext, types, + }) { + let { cursor } = prevContext; + if (prevContext?.types) { + types = prevContext.types; + } + if (types == null) { + const { response_metadata: { scopes } } = await this.authTest({ + throwRateLimitError: true, + }); + types = [ + "public_channel", + ]; + if (scopes.includes("groups:read")) { + types.push("private_channel"); + } + if (scopes.includes("mpim:read")) { + types.push("mpim"); + } + if (scopes.includes("im:read")) { + types.push("im"); + } + } + const conversationsResp = await this.availableConversations(types.join(), cursor, true); + let conversations, userIds, userNames, realNames; + if (types.includes("im")) { + conversations = conversationsResp.conversations; + userIds = conversations.map(({ user }) => user).filter(Boolean); + } else { + conversations = conversationsResp.conversations.filter((c) => !c.is_im); + } + if (types.includes("mpim")) { + userNames = [ + ...new Set(conversations.filter((c) => c.is_mpim).map((c) => c.purpose.value) + .map((v) => v.match(/@[\w.-]+/g) || []) + .flat() + .map((u) => u.slice(1))), + ]; + } + if ((userIds?.length > 0) || (userNames?.length > 0)) { + // Look up real names for userIds and userNames at the same time to + // minimize number of API calls. + realNames = await this.realNameLookup(userIds, userNames); + } + + return { + options: conversations.map((c) => { + if (c.is_im) { + return { + label: `Direct messaging with: ${realNames[c.user]}`, + value: c.id, + }; + } else if (c.is_mpim) { + const usernames = c.purpose.value.match(/@[\w.-]+/g) || []; + const realnames = usernames.map((u) => realNames[u.slice(1)] || u); + return { + label: realnames.length + ? `Group messaging with: ${realnames.join(", ")}` + : c.purpose.value, + value: c.id, + }; + } else { + return { + label: `${c.is_private + ? "Private" + : "Public"} channel: ${c.name}`, + value: c.id, + }; + } + }), + context: { + types, + cursor: conversationsResp.cursor, + }, + }; + }, + }, + channelId: { + type: "string", + label: "Channel ID", + description: "Select the channel's id.", + async options({ + prevContext, + types = Object.values(constants.CHANNEL_TYPE), + channelsFilter = (channel) => channel, + excludeArchived = true, + }) { + const { + channels, + response_metadata: { next_cursor: cursor }, + } = await this.conversationsList({ + types: types.join(), + cursor: prevContext.cursor, + limit: constants.LIMIT, + exclude_archived: excludeArchived, + throwRateLimitError: true, + }); + + let userNames; + if (types.includes("im")) { + const userIds = channels.filter(({ is_im }) => is_im).map(({ user }) => user); + userNames = await this.userNameLookup(userIds); + } + + const options = channels + .filter(channelsFilter) + .map((c) => { + if (c.is_im) { + return { + label: `Direct messaging with: @${userNames[c.user]}`, + value: c.id, + }; + } else if (c.is_mpim) { + return { + label: c.purpose.value, + value: c.id, + }; + } else { + return { + label: `${c.is_private + ? "Private" + : "Public"} channel: ${c.name}`, + value: c.id, + }; + } + }); + + return { + options, + context: { + cursor, + }, + }; + }, + }, + team: { + type: "string", + label: "Team", + description: "Select a team.", + async options({ prevContext }) { + const { + teams, + response_metadata: { next_cursor: cursor }, + } = await this.authTeamsList({ + cursor: prevContext.cursor, + limit: constants.LIMIT, + throwRateLimitError: true, + }); + + return { + options: teams.map((team) => ({ + label: team.name, + value: team.id, + })), + + context: { + cursor, + }, + }; + }, + }, + messageTs: { + type: "string", + label: "Message Timestamp", + description: "Timestamp of a message. e.g. `1403051575.000407`.", + }, + text: { + type: "string", + label: "Text", + description: "Text of the message to send (see Slack's [formatting docs](https://api.slack.com/reference/surfaces/formatting)). This field is usually necessary, unless you're providing only attachments instead.", + }, + topic: { + type: "string", + label: "Topic", + description: "Text of the new channel topic.", + }, + purpose: { + type: "string", + label: "Purpose", + description: "Text of the new channel purpose.", + }, + query: { + type: "string", + label: "Query", + description: "Search query.", + }, + file: { + type: "string", + label: "File ID", + description: "Specify a file by providing its ID.", + async options({ + channel, page, + }) { + const { files } = await this.listFiles({ + channel, + page: page + 1, + count: constants.LIMIT, + throwRateLimitError: true, + }); + return files?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + attachments: { + type: "string", + label: "Attachments", + description: "A JSON-based array of structured attachments, presented as a URL-encoded string (e.g., `[{\"pretext\": \"pre-hello\", \"text\": \"text-world\"}]`).", + optional: true, + }, + unfurl_links: { + type: "boolean", + label: "Unfurl Links", + description: "Default to `false`. Pass `true` to enable unfurling of links.", + default: false, + optional: true, + }, + unfurl_media: { + type: "boolean", + label: "Unfurl Media", + description: "Defaults to `false`. Pass `true` to enable unfurling of media content.", + default: false, + optional: true, + }, + parse: { + type: "string", + label: "Parse", + description: "Change how messages are treated. Defaults to none. By default, URLs will be hyperlinked. Set `parse` to `none` to remove the hyperlinks. The behavior of `parse` is different for text formatted with `mrkdwn`. By default, or when `parse` is set to `none`, `mrkdwn` formatting is implemented. To ignore `mrkdwn` formatting, set `parse` to full.", + optional: true, + }, + as_user: { + type: "boolean", + label: "Send as User", + description: "Optionally pass `true` to post the message as the authenticated user, instead of as a bot. Defaults to `false`.", + default: false, + optional: true, + }, + mrkdwn: { + label: "Send text as Slack mrkdwn", + type: "boolean", + description: "`true` by default. Pass `false` to disable Slack markup parsing. [See docs here](https://api.slack.com/reference/surfaces/formatting)", + default: true, + optional: true, + }, + post_at: { + label: "Schedule message", + description: "Messages can only be scheduled up to 120 days in advance, and cannot be scheduled for the past. The datetime should be in ISO 8601 format. (Example: `2014-01-01T00:00:00Z`)", + type: "string", + optional: true, + }, + username: { + type: "string", + label: "Bot Username", + description: "Optionally customize your bot's user name (default is `Pipedream`). Must be used in conjunction with `Send as User` set to false, otherwise ignored.", + optional: true, + }, + blocks: { + type: "string", + label: "Blocks", + description: "Enter an array of [structured blocks](https://app.slack.com/block-kit-builder) as a URL-encoded string. E.g., `[{ \"type\": \"section\", \"text\": { \"type\": \"mrkdwn\", \"text\": \"This is a mrkdwn section block :ghost: *this is bold*, and ~this is crossed out~, and \" }}]`\n\n**Tip:** Construct your blocks in a code step, return them as an array, and then pass the return value to this step.", + optional: true, + }, + icon_emoji: { + type: "string", + label: "Icon (emoji)", + description: "Optionally provide an emoji to use as the icon for this message. E.g., `:fire:` Overrides `icon_url`. Must be used in conjunction with `Send as User` set to `false`, otherwise ignored.", + optional: true, + async options() { + return await this.getCustomEmojis({ + throwRateLimitError: true, + }); + }, + }, + content: { + label: "File Path or URL", + description: "The file to upload. Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/myFile.txt`)", + type: "string", + }, + link_names: { + type: "boolean", + label: "Link Names", + description: "Find and link channel names and usernames.", + optional: true, + }, + thread_broadcast: { + type: "boolean", + label: "Send Channel Message", + description: "If `true`, posts in the thread and channel. Used in conjunction with `Message Timestamp` and indicates whether reply should be made visible to everyone in the channel. Defaults to `false`.", + default: false, + optional: true, + }, + reply_channel: { + label: "Reply Channel or Conversation ID", + type: "string", + description: "Provide the channel or conversation ID for the thread to reply to (e.g., if triggering on new Slack messages, enter `{{event.channel}}`). If the channel does not match the thread timestamp, a new message will be posted to this channel.", + optional: true, + }, + icon_url: { + type: "string", + label: "Icon (image URL)", + description: "Optionally provide an image URL to use as the icon for this message. Must be used in conjunction with `Send as User` set to `false`, otherwise ignored.", + optional: true, + }, + initial_comment: { + type: "string", + label: "Initial Comment", + description: "The message text introducing the file", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "An email address belonging to a user in the workspace", + }, + metadata_event_type: { + type: "string", + label: "Metadata Event Type", + description: "The name of the metadata event. Example: `task_created`", + optional: true, + }, + metadata_event_payload: { + type: "string", + label: "Metadata Event Payload", + description: "The payload of the metadata event. Must be a JSON string. Example: `{ \"id\": \"11223\", \"title\": \"Redesign Homepage\"}`", + optional: true, + }, + ignoreMyself: { + type: "boolean", + label: "Ignore myself", + description: "Ignore messages from me", + default: false, + }, + keyword: { + type: "string", + label: "Keyword", + description: "Keyword to monitor", + }, + ignoreBot: { + type: "boolean", + label: "Ignore Bots", + description: "Ignore messages from bots", + default: false, + optional: true, + }, + resolveNames: { + type: "boolean", + label: "Resolve Names", + description: "Instead of returning `channel`, `team`, and `user` as IDs, return their human-readable names.", + default: false, + optional: true, + }, + pageSize: { + type: "integer", + label: "Page Size", + description: "The number of results to include in a page. Default: 250", + default: constants.LIMIT, + optional: true, + }, + numPages: { + type: "integer", + label: "Number of Pages", + description: "The number of pages to retrieve. Default: 1", + default: 1, + optional: true, + }, + addToChannel: { + type: "boolean", + label: "Add app to channel automatically?", + description: "If `true`, the app will be added to the specified non-DM channel(s) automatically. If `false`, you must add the app to the channel manually. Defaults to `true`.", + default: true, + }, + }, + methods: { + getChannelLabel(resource) { + if (resource.user) { + return `Direct Messaging with: @${resource.user.name}`; + } + + const { + is_private: isPrivate, + name, + } = resource.channel; + + return `${isPrivate && "Private" || "Public"} channel #${name}`; + }, + mySlackId() { + return this.$auth.oauth_uid; + }, + getToken(opts = {}) { + // Use bot token if asBot is true and available, otherwise use user token. + const botToken = this.getBotToken(); + const userToken = this.$auth.oauth_access_token; + return (opts.asBot && botToken) + ? botToken + : userToken; + }, + getBotToken() { + return this.$auth.bot_token; + }, + async getChannelDisplayName(channel) { + if (channel.user) { + try { + const { profile } = await this.getUserProfile({ + user: channel.user, + }); + return `@${profile.real_name || profile?.real_name}`; + } catch { + return "user"; + } + } else if (channel.is_mpim) { + try { + const { members } = await this.listChannelMembers({ + channel: channel.id, + }); + const users = await Promise.all(members.map((m) => this.getUserProfile({ + user: m, + }))); + const realNames = users.map((u) => u.profile?.real_name || u.real_name); + return `Group Messaging with: ${realNames.join(", ")}`; + } catch { + return `Group Messaging with: ${channel.purpose.value}`; + } + } + return `#${channel?.name}`; + }, + /** + * Returns a Slack Web Client object authenticated with the user's access + * token + */ + sdk(opts = {}) { + return new WebClient(this.getToken(opts), { + rejectRateLimitedCalls: true, + slackApiUrl: this.$auth.base_url, + }); + }, + async makeRequest({ + method = "", throwRateLimitError = false, asBot, as_user, ...args + } = {}) { + // Passing as_user as false with a v2 user token lacking the deprecated + // `chat:write:bot` scope results in an error. If as_user is false and a + // bot token is available, use the bot token and omit as_user. Otherwise, + // pass as_user through. + if (as_user === false && Boolean(this.getBotToken())) { + asBot = true; + } else { + args.as_user = as_user; + } + + const props = method.split("."); + const sdk = props.reduce((reduction, prop) => + reduction[prop], this.sdk({ + asBot, + })); + + let response; + try { + response = await this._withRetries(() => sdk(args), throwRateLimitError); + } catch (error) { + if ([ + "not_in_channel", + "channel_not_found", + ].some((errorType) => `${error}`.includes(errorType)) && asBot) { + const followUp = method.startsWith("chat.") + ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." + : "Ensure the bot is a member of the channel."; + throw new ConfigurationError(`${error}\n${followUp}`); + } + throw error; + } + + if (!response.ok) { + throw response.error; + } + return response; + }, + async _withRetries(apiCall, throwRateLimitError = false) { + const retryOpts = { + retries: 3, + minTimeout: 30000, + }; + return retry(async (bail) => { + try { + return await apiCall(); + } catch (error) { + const statusCode = get(error, "code"); + if (statusCode === "slack_webapi_rate_limited_error") { + if (throwRateLimitError) { + bail(`Rate limit exceeded. ${error}`); + } else { + console.log(`Rate limit exceeded. Will retry in ${retryOpts.minTimeout / 1000} seconds`); + throw error; + } + } + bail(`${error}`); + } + }, retryOpts); + }, + /** + * Returns a list of channel-like conversations in a workspace. The + * "channels" returned depend on what the calling token has access to and + * the directives placed in the types parameter. + * + * @param {string} [types] - a comma-separated list of channel types to get. + * Any combination of: `public_channel`, `private_channel`, `mpim`, `im` + * @param {string} [cursor] - a cursor returned by the previous API call, + * used to paginate through collections of data + * @returns an object containing a list of conversations and the cursor for the next + * page of conversations + */ + async availableConversations(types, cursor, throwRateLimitError = false) { + const { + channels: conversations, + response_metadata: { next_cursor: nextCursor }, + } = await this.usersConversations({ + types, + cursor, + limit: constants.LIMIT, + exclude_archived: true, + throwRateLimitError, + }); + return { + cursor: nextCursor, + conversations, + }; + }, + async userNameLookup(ids = [], throwRateLimitError = true, args = {}) { + let cursor; + const userNames = {}; + do { + const { + members: users, + response_metadata: { next_cursor: nextCursor }, + } = await this.usersList({ + limit: constants.LIMIT, + cursor, + throwRateLimitError, + ...args, + }); + + for (const user of users) { + if (ids.includes(user.id)) { + userNames[user.id] = user.name; + } + } + + cursor = nextCursor; + } while (cursor && Object.keys(userNames).length < ids.length); + return userNames; + }, + async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { + let cursor; + const realNames = {}; + do { + const { + members: users, + response_metadata: { next_cursor: nextCursor }, + } = await this.usersList({ + limit: constants.LIMIT, + cursor, + throwRateLimitError, + ...args, + }); + + for (const user of users) { + if (ids.includes(user.id)) { + realNames[user.id] = user.profile.real_name; + } + if (usernames.includes(user.name)) { + realNames[user.name] = user.profile.real_name; + } + } + + cursor = nextCursor; + } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); + return realNames; + }, + async maybeAddAppToChannels(channelIds = []) { + if (!this.getBotToken()) { + console.log("Skipping adding app to channels: bot unavailable."); + return; + } + try { + const { + bot_id, user_id, + } = await this.authTest({ + asBot: true, + }); + if (!bot_id) { + console.log("Skipping adding app to channels: bot not found."); + return; + } + for (const channel of channelIds) { + try { + // Note: Trying to add the app to DM or group DM channels results in + // the error: method_not_supported_for_channel_type + await this.inviteToConversation({ + channel, + users: user_id, + }); + } catch (error) { + console.log(`Unable to add app to channel ${channel}: ${error}`); + } + } + } catch (error) { + console.log(`Unable to add app to channels: ${error}`); + } + }, + /** + * Checks authentication & identity. + * @param {*} args Arguments object + * @returns Promise + */ + authTest(args = {}) { + return this.makeRequest({ + method: "auth.test", + ...args, + }); + }, + /** + * Lists all reminders created by or for a given user. + * @param {*} args Arguments object + * @param {string} [args.team_id] Encoded team id, required if org token is passed. + * E.g. `T1234567890` + * @returns Promise + */ + remindersList(args = {}) { + return this.makeRequest({ + method: "reminders.list", + ...args, + }); + }, + /** + * List all User Groups for a team + * @param {*} args + * @returns Promise + */ + usergroupsList(args = {}) { + return this.makeRequest({ + method: "usergroups.list", + ...args, + }); + }, + authTeamsList(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "auth.teams.list", + ...args, + }); + }, + /** + * List conversations the calling user may access. + * Bot Scopes: `channels:read` `groups:read` `im:read` `mpim:read` + * @param {UsersConversationsArguments} args Arguments object + * @param {string} [args.cursor] Pagination value e.g. (`dXNlcjpVMDYxTkZUVDI=`) + * @param {boolean} [args.exclude_archived] Set to `true` to exclude archived channels + * from the list. Defaults to `false` + * @param {number} [args.limit] Pagination value. Defaults to `250` + * @param {string} [args.team_id] Encoded team id to list users in, + * required if org token is used + * @param {string} [args.types] Mix and match channel types by providing a + * comma-separated list of any combination of `public_channel`, `private_channel`, `mpim`, `im` + * Defaults to `public_channel`. E.g. `im,mpim` + * @param {string} [args.user] Browse conversations by a specific + * user ID's membership. Non-public channels are restricted to those where the calling user + * shares membership. E.g `W0B2345D` + * @returns Promise + */ + usersConversations(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "users.conversations", + user: this.$auth.oauth_uid, + ...args, + }); + }, + /** + * Lists all users in a Slack team. + * Bot Scopes: `users:read` + * @param {UsersListArguments} args Arguments object + * @param {string} [args.cursor] Pagination value e.g. (`dXNlcjpVMDYxTkZUVDI=`) + * @param {boolean} [args.include_locale] Set this to `true` to receive the locale + * for users. Defaults to `false` + * @param {number} [args.limit] The maximum number of items to return. Defaults to `250` + * @param {string} [args.team_id] Encoded team id to list users in, + * required if org token is used + * @returns Promise + */ + usersList(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "users.list", + ...args, + }); + }, + /** + * Lists all channels in a Slack team. + * Bot Scopes: `channels:read` `groups:read` `im:read` `mpim:read` + * @param {ConversationsListArguments} args Arguments object + * @param {string} [args.cursor] Pagination value e.g. (`dXNlcjpVMDYxTkZUVDI=`) + * @param {boolean} [args.exclude_archived] Set to `true` to exclude archived channels + * from the list. Defaults to `false` + * @param {number} [args.limit] pagination value. Defaults to `250` + * @param {string} [args.team_id] encoded team id to list users in, + * required if org token is used + * @param {string} [args.types] Mix and match channel types by providing a + * comma-separated list of any combination of `public_channel`, `private_channel`, `mpim`, `im` + * Defaults to `public_channel`. E.g. `im,mpim` + * @returns Promise + */ + conversationsList(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "conversations.list", + ...args, + }); + }, + /** + * Fetches a conversation's history of messages and events. + * Bot Scopes: `channels:history` `groups:history` `im:history` `mpim:history` + * @param {ConversationsHistoryArguments} args Arguments object + * @param {string} args.channel Conversation ID to fetch history for. E.g. `C1234567890` + * @param {string} [args.cursor] Pagination value e.g. (`dXNlcjpVMDYxTkZUVDI=`) + * @param {boolean} [args.include_all_metadata] + * @param {boolean} [args.inclusive] + * @param {string} [args.latest] + * @param {number} [args.limit] + * @param {string} [args.oldest] + * @returns Promise + */ + conversationsHistory(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "conversations.history", + ...args, + }); + }, + /** + * Retrieve information about a conversation. + * Bot Scopes: `channels:read` `groups:read` `im:read` `mpim:read` + * @param {ConversationsInfoArguments} args Arguments object + * @param {string} args.channel Conversation ID to learn more about. E.g. `C1234567890` + * @param {boolean} [args.include_locale] Set this to `true` to receive the locale + * for users. Defaults to `false` + * @param {boolean} [args.include_num_members] Set to true to include the + * member count for the specified conversation. Defaults to `false` + * @returns Promise + */ + conversationsInfo(args = {}) { + return this.makeRequest({ + method: "conversations.info", + ...args, + }); + }, + /** + * Retrieve information about a conversation. + * Bot Scopes: `users:read` + * @param {UsersInfoArguments} args arguments object + * @param {string} args.user User to get info on. E.g. `W1234567890` + * @param {boolean} [args.include_locale] Set this to true to receive the locale + * for this user. Defaults to `false` + * @returns Promise + */ + usersInfo(args = {}) { + return this.makeRequest({ + method: "users.info", + ...args, + }); + }, + /** + * Searches for messages matching a query. + * User Scopes: `search:read` + * @param {SearchMessagesArguments} args Arguments object + * @param {string} args.query Search query + * @param {number} [args.count] Number of items to return per page. Default `250` + * @param {string} [args.cursor] Use this when getting results with cursormark + * pagination. For first call send `*` for subsequent calls, send the value of + * `next_cursor` returned in the previous call's results + * @param {boolean} [args.highlight] + * @param {number} [args.page] + * @param {string} [args.sort] + * @param {string} [args.sort_dir] + * @param {string} [args.team_id] Encoded team id to search in, + * required if org token is used. E.g. `T1234567890` + * @returns Promise + */ + searchMessages(args = {}) { + args.count ||= constants.LIMIT; + return this.makeRequest({ + method: "search.messages", + ...args, + }); + }, + assistantSearch(args = {}) { + args.count ||= constants.LIMIT; + return this.sdk().apiCall("assistant.search.context", { + ...args, + }); + }, + /** + * Lists reactions made by a user. + * User Scopes: `reactions:read` + * Bot Scopes: `reactions:read` + * @param {ReactionsListArguments} args Arguments object + * @param {number} [args.count] Number of items to return per page. Default `100` + * @param {string} [args.cursor] Parameter for pagination. Set cursor equal to the + * `next_cursor` attribute returned by the previous request's response_metadata. + * This parameter is optional, but pagination is mandatory: the default value simply + * fetches the first "page" of the collection. + * @param {boolean} [args.full] If true always return the complete reaction list. + * @param {number} [args.limit] The maximum number of items to return. + * Fewer than the requested number of items may be returned, even if the end of the + * list hasn't been reached. + * @param {number} [args.page] Page number of results to return. Defaults to `1`. + * @param {string} [args.team_id] Encoded team id to list reactions in, + * required if org token is used + * @param {string} [args.user] Show reactions made by this user. Defaults to the authed user. + * @returns Promise + */ + reactionsList(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "reactions.list", + ...args, + }); + }, + async getCustomEmojis(args = {}) { + const resp = await this.sdk().emoji.list({ + include_categories: true, + limit: constants.LIMIT, + ...args, + }); + + const emojis = Object.keys(resp.emoji); + for (const category of resp.categories) { + emojis.push(...category.emoji_names); + } + return emojis; + }, + listChannelMembers(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "conversations.members", + ...args, + }); + }, + listFiles(args = {}) { + args.count ||= constants.LIMIT; + return this.makeRequest({ + method: "files.list", + // Use bot token, if available, since the required `files:read` scope + // is only requested for bot tokens in the Pipedream app. + asBot: true, + ...args, + }); + }, + listGroupMembers(args = {}) { + args.limit ||= constants.LIMIT; + return this.makeRequest({ + method: "usergroups.users.list", + ...args, + }); + }, + getFileInfo(args = {}) { + return this.makeRequest({ + method: "files.info", + // Use bot token, if available, since the required `files:read` scope + // is only requested for bot tokens in the Pipedream app. + asBot: true, + ...args, + }); + }, + getUserProfile(args = {}) { + return this.makeRequest({ + method: "users.profile.get", + ...args, + }); + }, + getBotInfo(args = {}) { + return this.makeRequest({ + method: "bots.info", + ...args, + }); + }, + getTeamInfo(args = {}) { + return this.makeRequest({ + method: "team.info", + ...args, + }); + }, + getConversationReplies(args = {}) { + return this.makeRequest({ + method: "conversations.replies", + ...args, + }); + }, + addReactions(args = {}) { + return this.makeRequest({ + method: "reactions.add", + ...args, + }); + }, + postChatMessage(args = {}) { + return this.makeRequest({ + method: "chat.postMessage", + ...args, + }); + }, + archiveConversations(args = {}) { + return this.makeRequest({ + method: "conversations.archive", + ...args, + }); + }, + scheduleMessage(args = {}) { + return this.makeRequest({ + method: "chat.scheduleMessage", + ...args, + }); + }, + createConversations(args = {}) { + return this.makeRequest({ + method: "conversations.create", + ...args, + }); + }, + inviteToConversation(args = {}) { + return this.makeRequest({ + method: "conversations.invite", + ...args, + }); + }, + kickUserFromConversation(args = {}) { + return this.makeRequest({ + method: "conversations.kick", + ...args, + }); + }, + addReminders(args = {}) { + return this.makeRequest({ + method: "reminders.add", + ...args, + }); + }, + deleteFiles(args = {}) { + return this.makeRequest({ + method: "files.delete", + ...args, + }); + }, + deleteMessage(args = {}) { + return this.makeRequest({ + method: "chat.delete", + ...args, + }); + }, + lookupUserByEmail(args = {}) { + return this.makeRequest({ + method: "users.lookupByEmail", + ...args, + }); + }, + setChannelDescription(args = {}) { + return this.makeRequest({ + method: "conversations.setPurpose", + ...args, + }); + }, + setChannelTopic(args = {}) { + return this.makeRequest({ + method: "conversations.setTopic", + ...args, + }); + }, + updateProfile(args = {}) { + return this.makeRequest({ + method: "users.profile.set", + ...args, + }); + }, + updateGroupMembers(args = {}) { + return this.makeRequest({ + method: "usergroups.users.update", + ...args, + }); + }, + updateMessage(args = {}) { + return this.makeRequest({ + method: "chat.update", + ...args, + }); + }, + getUploadUrl(args = {}) { + return this.makeRequest({ + method: "files.getUploadURLExternal", + ...args, + }); + }, + completeUpload(args = {}) { + return this.makeRequest({ + method: "files.completeUploadExternal", + ...args, + }); + }, + }, +}; diff --git a/components/slack_v2/sources/common/base.mjs b/components/slack_v2/sources/common/base.mjs new file mode 100644 index 0000000000000..87c384aff53f4 --- /dev/null +++ b/components/slack_v2/sources/common/base.mjs @@ -0,0 +1,184 @@ +import slack from "../../slack_v2.app.mjs"; +import { + NAME_CACHE_MAX_SIZE, NAME_CACHE_TIMEOUT, +} from "./constants.mjs"; + +export default { + props: { + slack, + db: "$.service.db", + }, + methods: { + _getNameCache() { + return this.db.get("nameCache") ?? {}; + }, + _setNameCache(cacheObj) { + this.db.set("nameCache", cacheObj); + }, + _getLastCacheCleanup() { + return this.db.get("lastCacheCleanup") ?? 0; + }, + _setLastCacheCleanup(time) { + this.db.set("lastCacheCleanup", time); + }, + cleanCache(cacheObj) { + console.log("Initiating cache check-up..."); + const timeout = Date.now() - NAME_CACHE_TIMEOUT; + + const entries = Object.entries(cacheObj); + let cleanArr = entries.filter( + ([ + , { ts }, + ]) => ts > timeout, + ); + const diff = entries.length - cleanArr.length; + if (diff) { + console.log(`Cleaned up ${diff} outdated cache entries.`); + } + + if (cleanArr.length > NAME_CACHE_MAX_SIZE) { + console.log(`Reduced the cache from ${cleanArr.length} to ${NAME_CACHE_MAX_SIZE / 2} entries.`); + cleanArr = cleanArr.slice(NAME_CACHE_MAX_SIZE / -2); + } + + const cleanObj = Object.fromEntries(cleanArr); + return cleanObj; + }, + getCache() { + let cacheObj = this._getNameCache(); + + const lastCacheCleanup = this._getLastCacheCleanup(); + const time = Date.now(); + + const shouldCleanCache = time - lastCacheCleanup > NAME_CACHE_TIMEOUT / 2; + if (shouldCleanCache) { + cacheObj = this.cleanCache(cacheObj); + this._setLastCacheCleanup(time); + } + + return [ + cacheObj, + shouldCleanCache, + ]; + }, + async maybeCached(key, refreshVal) { + let [ + cacheObj, + wasUpdated, + ] = this.getCache(); + let record = cacheObj[key]; + const time = Date.now(); + if (!record || time - record.ts > NAME_CACHE_TIMEOUT) { + record = { + ts: time, + val: await refreshVal(), + }; + cacheObj[key] = record; + wasUpdated = true; + } + + if (wasUpdated) { + this._setNameCache(cacheObj); + } + + return record.val; + }, + async getUserName(id) { + return this.maybeCached(`users:${id}`, async () => { + const info = await this.slack.usersInfo({ + user: id, + }); + if (!info.ok) throw new Error(info.error); + return info.user.name; + }); + }, + async getRealName(id) { + return this.maybeCached(`users_real_names:${id}`, async () => { + const info = await this.slack.usersInfo({ + user: id, + }); + if (!info.ok) throw new Error(info.error); + return info.user.real_name; + }); + }, + async getBotName(id) { + return this.maybeCached(`bots:${id}`, async () => { + const info = await this.slack.getBotInfo({ + bot: id, + }); + if (!info.ok) throw new Error(info.error); + return info.bot.name; + }); + }, + async getConversationName(id) { + return this.maybeCached(`conversations:${id}`, async () => { + const info = await this.slack.conversationsInfo({ + channel: id, + }); + if (!info.ok) throw new Error(info.error); + if (info.channel.is_im) { + return `DM with ${await this.getUserName(info.channel.user)}`; + } + return info.channel.name; + }); + }, + async getTeamName(id) { + return this.maybeCached(`team:${id}`, async () => { + try { + const info = await this.slack.getTeamInfo({ + team: id, + }); + return info.team.name; + } catch (err) { + console.log( + "Error getting team name, probably need to re-connect the account at pipedream.com/apps", + err, + ); + return id; + } + }); + }, + async getMessage({ + channel, event_ts: ts, + }) { + return await this.maybeCached( + `lastMessage:${channel}:${ts}`, + async () => { + const response = await this.slack.getConversationReplies({ + channel, + ts, + limit: 1, + }); + + if (response.messages.length) { + response.messages = [ + response.messages[0], + ]; + } + + return response; + }, + ); + }, + processEvent(event) { + return event; + }, + }, + async run(event) { + event = await this.processEvent(event); + + if (event) { + if (!event.client_msg_id) { + event.pipedream_msg_id = `pd_${Date.now()}_${Math.random() + .toString(36) + .substr(2, 10)}`; + } + + this.$emit(event, { + id: event.client_msg_id || event.pipedream_msg_id || event.channel.id, + summary: this.getSummary(event), + ts: event.event_ts || Date.now(), + }); + } + }, +}; diff --git a/components/slack_v2/sources/common/constants.mjs b/components/slack_v2/sources/common/constants.mjs new file mode 100644 index 0000000000000..3255f46788c83 --- /dev/null +++ b/components/slack_v2/sources/common/constants.mjs @@ -0,0 +1,58 @@ +const events = { + im: "User", + message: "Message", + file: "File", + channel: "Channel", +}; + +const eventsOptions = [ + { + label: "User", + value: "im", + }, + { + label: "Message", + value: "message", + }, + { + label: "File", + value: "file", + }, + { + label: "Channel", + value: "channel", + }, +]; + +const SUBTYPE = { + NULL: null, + BOT_MESSAGE: "bot_message", + FILE_SHARE: "file_share", + PD_HISTORY_MESSAGE: "pd_history_message", + MESSAGE_REPLIED: "message_replied", +}; + +const ALLOWED_SUBTYPES = [ + SUBTYPE.NULL, + SUBTYPE.BOT_MESSAGE, + SUBTYPE.FILE_SHARE, + SUBTYPE.PD_HISTORY_MESSAGE, +]; + +const ALLOWED_MESSAGE_IN_CHANNEL_SUBTYPES = [ + SUBTYPE.NULL, + SUBTYPE.BOT_MESSAGE, + SUBTYPE.FILE_SHARE, + SUBTYPE.MESSAGE_REPLIED, +]; + +export const NAME_CACHE_MAX_SIZE = 1000; +export const NAME_CACHE_TIMEOUT = 3600000; + +export default { + events, + eventsOptions, + SUBTYPE, + ALLOWED_SUBTYPES, + ALLOWED_MESSAGE_IN_CHANNEL_SUBTYPES, +}; diff --git a/components/slack_v2/sources/new-channel-created/new-channel-created.mjs b/components/slack_v2/sources/new-channel-created/new-channel-created.mjs new file mode 100644 index 0000000000000..9d1df1076dc05 --- /dev/null +++ b/components/slack_v2/sources/new-channel-created/new-channel-created.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-channel-created", + name: "New Channel Created (Instant)", + version: "0.0.11", + description: "Emit new event when a new channel is created.", + type: "source", + dedupe: "unique", + props: { + ...common.props, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return [ + "channel_created", + ]; + }, + }, + }, + methods: { + ...common.methods, + getSummary({ channel: { name } }) { + return `New channel created - ${name}`; + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-channel-created/test-event.mjs b/components/slack_v2/sources/new-channel-created/test-event.mjs new file mode 100644 index 0000000000000..6bcfee6fd7deb --- /dev/null +++ b/components/slack_v2/sources/new-channel-created/test-event.mjs @@ -0,0 +1,44 @@ +export default { + "type": "channel_created", + "channel": { + "id": "C024BE91L", + "name": "fun", + "is_channel": true, + "is_group": false, + "is_im": false, + "is_mpim": false, + "is_private": false, + "created": 1360782804, + "is_archived": false, + "is_general": false, + "unlinked": 0, + "name_normalized": "fun", + "is_shared": false, + "is_frozen": false, + "is_org_shared": false, + "is_pending_ext_shared": false, + "pending_shared": [], + "context_team_id": "TLZ203R5", + "updated": 1714140253251, + "parent_conversation": null, + "creator": "U024BE7LH", + "is_ext_shared": false, + "shared_team_ids": [ + "TLZ203R5" + ], + "pending_connected_team_ids": [], + "topic": { + "value": "", + "creator": "", + "last_set": 0 + }, + "purpose": { + "value": "", + "creator": "", + "last_set": 0 + }, + "previous_names": [] + }, + "event_ts": "1714140253.002700", + "pipedream_msg_id": "pd_1714140255038_bkbl3pxpkp" +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-interaction-event-received/README.md b/components/slack_v2/sources/new-interaction-event-received/README.md new file mode 100644 index 0000000000000..659edb38a5654 --- /dev/null +++ b/components/slack_v2/sources/new-interaction-event-received/README.md @@ -0,0 +1,85 @@ +# Overview + +Slack messages can contain interactive elements like buttons, dropdowns, radio buttons, and more. This source subscribes to interactive events, like when a button is clicked in a message. + +![Example of a Slack button](https://res.cloudinary.com/pipedreamin/image/upload/v1668443788/docs/components/CleanShot_2022-11-10_at_10.17.172x_dxdz1o.png) + +Then this source will be triggered when you or another Slack user in your workspace clicks a button, selects an option or fills out a form. + +![Example feed of interaction events coming from Slack](https://res.cloudinary.com/pipedreamin/image/upload/v1668443818/docs/components/CleanShot_2022-11-10_at_10.19.152x_eyiims.png) + +With this trigger, you can build workflows that perform some work with other APIs or services, and then reply back to the original message. + +# Getting Started + + + +What this short video to learn how to use this in a workflow, or follow the guide below. + +First, if you haven’t already - send yourself a message containing one or more interactive elements. Use the ******************Sending the message with an interactive element****************** guide below to send a message containing a button. + +If you have already sent a message containing an element, skip to **********************************************Configuring the source.********************************************** + +## Sending the message with an interactive element + +The easiest way is to send yourself a message using the ****************************Slack - Send Message Using Block Kit**************************** action: + +![Selecting the Send Slack Message with Block Kit](https://res.cloudinary.com/pipedreamin/image/upload/v1668443844/docs/components/CleanShot_2022-11-10_at_10.25.522x_vxiooo.png)) + +Then select a **************Channel************** you’d like to send the message to, and use the **************[Block Kit Builder](https://app.slack.com/block-kit-builder/)************** to build a message, or just copy the example button blocks below: + +```jsx +[ + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Click Me", + "emoji": true + }, + "value": "click_me_123", + "action_id": "button_click" + } + ] + } +] +``` + +Your ******************Slack - Send Message Using Block Kit****************** should look like this: + +![Setting up the block kit message with a button block](https://res.cloudinary.com/pipedreamin/image/upload/v1668443887/docs/components/CleanShot_2022-11-10_at_10.29.552x_kvfznm.png) + +## Configuring the source + +By default, this source will listen to ******all****** interactive events from your Slack workspace that your connected Slack account has authorization to view. Please note that only messages created via [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action, or via API call from the Pipedream app will emit an interaction event with this trigger. Block kit messages sent directly via the Slack's block kit builder will not trigger an interaction event. + +You can filter these events by selecting a specific **************channel************** and/or a specific **********action_id.********** + +### Filtering interactive events by channel + +Use the ****************Channels**************** dropdown to search for a specific channel for this source to subscribe to. ********Only******** button clicks, dropdown selects, etc. *in this selected channel* will trigger the source. + +### Filtering interactive events by `action_id` + +For more specificity, you can filter based on the passed `action_id` to the message. + +The `action_id` is arbitrary. It’s defined on the initial message sending the button, dropdown, or other interactive element’s markup. + +For example, in the section above using the Block Kit to create a message, we defined the button’s `action_id` as `"button_click"`. But you can choose whichever naming convention you’d like. + +If you pass `button_click` as a required `action_id` to this source, then only interactivity events with the `action_id` of `"button_click"` will trigger this source. + +## Troubleshooting + +### I’m clicking buttons, but no events are being received + +Follow these steps to make sure your source is configured correctly: + +1. Make sure that your `action_id` or ****************channels**************** filters apply to that message, remove the filters to make sure that’s not the case. + +1. Make sure that the message comes from the same Slack account that this source is configured with. + +1. Make sure that the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app. diff --git a/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs b/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs new file mode 100644 index 0000000000000..4ec49f2be9653 --- /dev/null +++ b/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs @@ -0,0 +1,105 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + name: "New Interaction Events (Instant)", + version: "0.0.21", + key: "slack-new-interaction-event-received", + description: "Emit new events on new Slack [interactivity events](https://api.slack.com/interactivity) sourced from [Block Kit interactive elements](https://api.slack.com/interactivity/components), [Slash commands](https://api.slack.com/interactivity/slash-commands), or [Shortcuts](https://api.slack.com/interactivity/shortcuts).", + type: "source", + props: { + ...common.props, + alert: { + type: "alert", + alertType: "info", + content: "Please note that only messages created via Pipedream's [Send Block Kit Message](https://pipedream.com/apps/slack/actions/send-block-kit-message) Action, or via API call from the Pipedream app will emit an interaction event with this trigger. \n\nBlock kit messages sent directly via the Slack's block kit builder will not trigger an interaction event. \n\nSee the [documentation](https://pipedream.com/apps/slack/triggers/new-interaction-event-received) for more details.", + }, + action_ids: { + type: "string[]", + label: "Action IDs", + description: "Filter interaction events by specific `action_id`'s to subscribe for new interaction events. If none are specified, all `action_ids` created via Pipedream will emit new events.", + optional: true, + default: [], + }, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + ], + type: "string[]", + label: "Channels", + description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.", + optional: true, + default: [], + }, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + /** + * Subscribes to potentially 4 different events: + * `interaction_events` - all interaction events on the authenticated account + * `interaction_events:${action_id}` - all interaction events with a specific given action_id + * `interaction_events:${channel_id}` - all interaction events within a specific channel + * `interaction_events:${channel_id}:${action_id}` - action_id within a specific channel + * @returns string[] + */ + async eventNames() { + // start with action_ids, since they can be the most specific + const action_events = this.action_ids.reduce((carry, action_id) => { + // if channels are provided, spread them + if (this.conversations && this.conversations.length > 0) { + return [ + ...carry, + ...this.conversations.map( + (channel) => `interaction_events:${channel}:${action_id}`, + ), + ]; + } + + return [ + ...carry, + `interaction_events:${action_id}`, + ]; + }, []); + + if (action_events.length > 0) return action_events; + + // if no action_ids are specified, move down to channels + const channel_events = this.conversations.map( + (channel) => `interaction_events:${channel}`, + ); + + if (channel_events.length > 0) return channel_events; + + // if not specific action_ids or channels are specified, subscribe to all events + return [ + "interaction_events", + ]; + }, + }, + }, + methods: {}, + async run(event) { + this.$emit( + { + event, + }, + { + summary: `New interaction event${ + event?.channel?.id + ? ` in channel ${event.channel.id}` + : "" + }${ + event.actions?.length > 0 + ? ` from action_ids ${event.actions + .map((action) => action.action_id) + .join(", ")}` + : "" + }`, + ts: Date.now(), + }, + ); + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-interaction-event-received/test-event.mjs b/components/slack_v2/sources/new-interaction-event-received/test-event.mjs new file mode 100644 index 0000000000000..940a52c301ea8 --- /dev/null +++ b/components/slack_v2/sources/new-interaction-event-received/test-event.mjs @@ -0,0 +1,86 @@ +export default { + "event": { + "type": "block_actions", + "user": { + "id": "US676PZLY", + "username": "test.user", + "name": "test.user", + "team_id": "TS8319547" + }, + "api_app_id": "AN9231S6L", + "token": "UYc82mtyZWRhvUXQ6TXrv4wq", + "container": { + "type": "message", + "message_ts": "1716402983.247149", + "channel_id": "CS8319KD5", + "is_ephemeral": false + }, + "trigger_id": "7161731794692.892103311143.4020ed3595908eca11e4076438354dbb", + "team": { + "id": "TS8319547", + "domain": "test-j1q3506" + }, + "enterprise": null, + "is_enterprise_install": false, + "channel": { + "id": "CS8319KD5", + "name": "testing" + }, + "message": { + "subtype": "bot_message", + "text": "Click Me button Sent via ", + "username": "Pipedream", + "type": "message", + "ts": "1716402983.247149", + "bot_id": "BRTDL45RQ", + "app_id": "AN9231S6L", + "blocks": [ + { + "type": "actions", + "block_id": "SJp0j", + "elements": [ + { + "type": "button", + "action_id": "button_click", + "text": { + "type": "plain_text", + "text": "Click Me", + "emoji": true + }, + "value": "click_me_123" + } + ] + }, + { + "type": "context", + "block_id": "ysmBN", + "elements": [ + { + "type": "mrkdwn", + "text": "Sent via ", + "verbatim": false + } + ] + } + ] + }, + "state": { + "values": {} + }, + "response_url": "https://hooks.slack.com/actions/TS8319547/7156351250101/J0w1NoVIXjChEwp4WQab4tcv", + "actions": [ + { + "action_id": "button_click", + "block_id": "SJp0j", + "text": { + "type": "plain_text", + "text": "Click Me", + "emoji": true + }, + "value": "click_me_123", + "type": "button", + "action_ts": "1716403200.549150" + } + ] + } +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs new file mode 100644 index 0000000000000..c6323e562670e --- /dev/null +++ b/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs @@ -0,0 +1,99 @@ +import common from "../common/base.mjs"; +import constants from "../common/constants.mjs"; +import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-new-keyword-mention", + name: "New Keyword Mention (Instant)", + version: "0.1.0", + description: "Emit new event when a specific keyword is mentioned in a channel", + type: "source", + dedupe: "unique", + props: { + ...common.props, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + type: "string[]", + label: "Channels", + description: "Select one or more channels to monitor for new messages.", + optional: true, + }, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return this.conversations || [ + "message", + ]; + }, + }, + keyword: { + propDefinition: [ + common.props.slack, + "keyword", + ], + }, + ignoreBot: { + propDefinition: [ + common.props.slack, + "ignoreBot", + ], + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New keyword mention received"; + }, + async processEvent(event) { + const { + type: msgType, + subtype, + bot_id: botId, + text, + } = event; + + if (msgType !== "message") { + console.log(`Ignoring event with unexpected type "${msgType}"`); + return; + } + + // This source is designed to just emit an event for each new message received. + // Due to inconsistencies with the shape of message_changed and message_deleted + // events, we are ignoring them for now. If you want to handle these types of + // events, feel free to change this code!! + if (subtype && !constants.ALLOWED_SUBTYPES.includes(subtype)) { + console.log(`Ignoring message with subtype. "${subtype}"`); + return; + } + + if ((this.ignoreBot) && (subtype === constants.SUBTYPE.BOT_MESSAGE || botId)) { + return; + } + + let emitEvent = false; + if (text.indexOf(this.keyword) !== -1) { + emitEvent = true; + } else if (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE) { + emitEvent = true; + } + + if (emitEvent) { + return event; + } + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-keyword-mention/test-event.mjs b/components/slack_v2/sources/new-keyword-mention/test-event.mjs new file mode 100644 index 0000000000000..7c85b12599e3d --- /dev/null +++ b/components/slack_v2/sources/new-keyword-mention/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "user": "US676PZLY", + "type": "message", + "ts": "1716404766.096289", + "client_msg_id": "b26387fd-5afe-46a9-bf63-a7aabd6fb40f", + "text": "hello", + "team": "TS8319547", + "blocks": [ + { + "type": "rich_text", + "block_id": "aY6KK", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "hello" + } + ] + } + ] + } + ], + "channel": "CS8319KD5", + "event_ts": "1716404766.096289", + "channel_type": "channel" +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs new file mode 100644 index 0000000000000..72c9ef83bb751 --- /dev/null +++ b/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs @@ -0,0 +1,106 @@ +import common from "../common/base.mjs"; +import constants from "../common/constants.mjs"; +import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-new-message-in-channels", + name: "New Message In Channels (Instant)", + version: "1.1.0", + description: "Emit new event when a new message is posted to one or more channels", + type: "source", + dedupe: "unique", + props: { + ...common.props, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + type: "string[]", + label: "Channels", + description: "Select one or more channels to monitor for new messages.", + optional: true, + }, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return this.conversations || [ + "message", + ]; + }, + }, + resolveNames: { + propDefinition: [ + common.props.slack, + "resolveNames", + ], + }, + ignoreBot: { + propDefinition: [ + common.props.slack, + "ignoreBot", + ], + }, + ignoreThreads: { + type: "boolean", + label: "Ignore replies in threads", + description: "Ignore replies to messages in threads", + optional: true, + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New message in channel"; + }, + async processEvent(event) { + if (event.type !== "message") { + console.log(`Ignoring event with unexpected type "${event.type}"`); + return; + } + if (event.subtype && !constants.ALLOWED_MESSAGE_IN_CHANNEL_SUBTYPES.includes(event.subtype)) { + // This source is designed to just emit an event for each new message received. + // Due to inconsistencies with the shape of message_changed and message_deleted + // events, we are ignoring them for now. If you want to handle these types of + // events, feel free to change this code!! + console.log("Ignoring message with subtype."); + return; + } + if ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) { + return; + } + // There is no thread message type only the thread_ts field + // indicates if the message is part of a thread in the event. + if (this.ignoreThreads && event.thread_ts) { + console.log("Ignoring reply in thread"); + return; + } + if (this.resolveNames) { + if (event.user) { + event.user_id = event.user; + event.user = await this.getUserName(event.user); + } else if (event.bot_id) { + event.bot = await this.getBotName(event.bot_id); + } + event.channel_id = event.channel; + event.channel = await this.getConversationName(event.channel); + if (event.team) { + event.team_id = event.team; + event.team = await this.getTeamName(event.team); + } + } + return event; + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-message-in-channels/test-event.mjs b/components/slack_v2/sources/new-message-in-channels/test-event.mjs new file mode 100644 index 0000000000000..3f87589e9539f --- /dev/null +++ b/components/slack_v2/sources/new-message-in-channels/test-event.mjs @@ -0,0 +1,45 @@ +export default { + "client_msg_id": "1a7b4cd8-7c83-4f6e-92f8-bfbd4f77d888", + "type": "message", + "text": "Hello <@U06MDSMHK7B>, I’ve registered the task here: ", + "user": "tuleanphuonghh", + "ts": "1702506383.129070", + "blocks": [ + { + "type": "rich_text", + "block_id": "Gh1p", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Hello " + }, + { + "type": "user", + "user_id": "U06MDSMHK7B" + }, + { + "type": "text", + "text": ", I’ve registered the task here: " + }, + { + "type": "link", + "url": "https://github.com/DreamPipe/Dreampipe/issues/8395" + } + ] + } + ] + } + ], + "team": "Dreampipe Users", + "thread_ts": "1702504303.421340", + "parent_user_id": "U06MDSMHK7B", + "channel": "support", + "event_ts": "1702506383.129070", + "channel_type": "channel", + "user_id": "U04DXTI5SRG", + "channel_id": "CPUIZSV6B", + "team_id": "TNZFYHTCG" + } \ No newline at end of file diff --git a/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs new file mode 100644 index 0000000000000..aaf4edf973db0 --- /dev/null +++ b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs @@ -0,0 +1,113 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-reaction-added", + name: "New Reaction Added (Instant)", + version: "1.2.0", + description: "Emit new event when a member has added an emoji reaction to a message", + type: "source", + dedupe: "unique", + props: { + ...common.props, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + ], + type: "string[]", + label: "Channels", + description: "Select one or more channels to monitor for new messages.", + optional: true, + }, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + if (this.conversations?.length) { + const conversations = []; + for (const conversation of this.conversations) { + conversations.push(`reaction_added:${conversation}`); + } + return conversations; + } + + return [ + "reaction_added", + ]; + }, + }, + ignoreBot: { + propDefinition: [ + common.props.slack, + "ignoreBot", + ], + }, + iconEmoji: { + propDefinition: [ + common.props.slack, + "icon_emoji", + ], + description: "Select one or more emojis to use as a filter. E.g. `fire, email`", + type: "string[]", + optional: true, + }, + includeUserData: { + label: "Include User Data", + description: "Include user object in the response. Default `false`", + type: "boolean", + optional: true, + default: false, + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New reaction added"; + }, + async processEvent(event) { + let iconEmojiParsed = []; + + try { + iconEmojiParsed = typeof this.iconEmoji === "string" ? + JSON.parse(this.iconEmoji) : + this.iconEmoji; + } catch (error) { + iconEmojiParsed = this.iconEmoji.replace(/\s+/g, "").split(","); + } + + if ( + ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) || + (iconEmojiParsed?.length > 0 && !iconEmojiParsed.includes(event.reaction)) + ) { + return; + } + + if (this.includeUserData) { + const userResponse = await this.slack.usersInfo({ + user: event.user, + }); + const itemUserResponse = await this.slack.usersInfo({ + user: event.user, + }); + + event.userInfo = userResponse.user; + event.itemUserInfo = itemUserResponse.user; + } + + try { + event.message = await this.getMessage({ + channel: event.item.channel, + event_ts: event.item.ts, + }); + } catch (err) { + console.log("Error fetching message:", err); + } + + return event; + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-reaction-added/test-event.mjs b/components/slack_v2/sources/new-reaction-added/test-event.mjs new file mode 100644 index 0000000000000..106603e2da1c3 --- /dev/null +++ b/components/slack_v2/sources/new-reaction-added/test-event.mjs @@ -0,0 +1,193 @@ +export default { + "type": "reaction_added", + "user": "US676PZLY", + "reaction": "squirrel", + "item": { + "type": "message", + "channel": "CS8319KD5", + "ts": "1716405857.659549" + }, + "item_user": "US676PZLY", + "event_ts": "1716406183.000100", + "userInfo": { + "id": "US676PZLY", + "team_id": "TS8319547", + "name": "test.user", + "deleted": false, + "color": "9f69e7", + "real_name": "Test User", + "tz": "America/New_York", + "tz_label": "Eastern Daylight Time", + "tz_offset": -14400, + "profile": { + "title": "", + "phone": "", + "skype": "", + "real_name": "Test User", + "real_name_normalized": "Test User", + "display_name": "", + "display_name_normalized": "", + "fields": null, + "status_text": "", + "status_emoji": "", + "status_emoji_display_info": [], + "status_expiration": 0, + "avatar_hash": "g010b11df3bb", + "email": "test@sample.com", + "first_name": "Test", + "last_name": "User", + "status_text_canonical": "", + "team": "TS8319547" + }, + "is_admin": true, + "is_owner": true, + "is_primary_owner": true, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "is_app_user": false, + "updated": 1703787612, + "is_email_confirmed": true, + "has_2fa": false, + "who_can_share_contact_card": "EVERYONE" + }, + "itemUserInfo": { + "id": "US676PZLY", + "team_id": "TS8319547", + "name": "test.user", + "deleted": false, + "color": "9f69e7", + "real_name": "Test User", + "tz": "America/New_York", + "tz_label": "Eastern Daylight Time", + "tz_offset": -14400, + "profile": { + "title": "", + "phone": "", + "skype": "", + "real_name": "Test User", + "real_name_normalized": "Test User", + "display_name": "", + "display_name_normalized": "", + "fields": null, + "status_text": "", + "status_emoji": "", + "status_emoji_display_info": [], + "status_expiration": 0, + "avatar_hash": "g010b11df3bb", + "email": "test@sample.com", + "first_name": "Test", + "last_name": "User", + "status_text_canonical": "", + "team": "TS8319547" + }, + "is_admin": true, + "is_owner": true, + "is_primary_owner": true, + "is_restricted": false, + "is_ultra_restricted": false, + "is_bot": false, + "is_app_user": false, + "updated": 1703787612, + "is_email_confirmed": true, + "has_2fa": false, + "who_can_share_contact_card": "EVERYONE" + }, + "message": { + "ok": true, + "messages": [ + { + "user": "US676PZLY", + "type": "message", + "ts": "1716405857.659549", + "client_msg_id": "fd68d844-687e-41bf-8475-4215bef572c7", + "text": "hello", + "team": "TS8319547", + "blocks": [ + { + "type": "rich_text", + "block_id": "ZL1yL", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "hello" + } + ] + } + ] + } + ], + "reactions": [ + { + "name": "squirrel", + "users": [ + "US676PZLY" + ], + "count": 1 + } + ] + } + ], + "has_more": false, + "response_metadata": { + "scopes": [ + "identify", + "commands", + "channels:history", + "groups:history", + "im:history", + "mpim:history", + "channels:read", + "emoji:read", + "files:read", + "groups:read", + "im:read", + "mpim:read", + "reactions:read", + "reminders:read", + "search:read", + "stars:read", + "team:read", + "users:read", + "users:read.email", + "pins:read", + "usergroups:read", + "dnd:read", + "users.profile:read", + "channels:write", + "chat:write:user", + "chat:write:bot", + "files:write:user", + "groups:write", + "im:write", + "mpim:write", + "reactions:write", + "reminders:write", + "stars:write", + "users:write", + "pins:write", + "usergroups:write", + "dnd:write", + "users.profile:write", + "links:read", + "links:write", + "remote_files:share", + "remote_files:read", + "bookmarks:write", + "calls:write", + "calls:read" + ], + "acceptedScopes": [ + "channels:history", + "groups:history", + "mpim:history", + "im:history", + "read" + ] + } + }, + "pipedream_msg_id": "pd_1716406186371_xezly8lgzn" +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-saved-message/new-saved-message.mjs b/components/slack_v2/sources/new-saved-message/new-saved-message.mjs new file mode 100644 index 0000000000000..2d7a94e5c5b95 --- /dev/null +++ b/components/slack_v2/sources/new-saved-message/new-saved-message.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-saved-message", + name: "New Saved Message (Instant)", + version: "0.0.7", + description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", + type: "source", + dedupe: "unique", + props: { + ...common.props, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return [ + "star_added", + ]; + }, + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New saved message"; + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-saved-message/test-event.mjs b/components/slack_v2/sources/new-saved-message/test-event.mjs new file mode 100644 index 0000000000000..433e5bcb8d4ec --- /dev/null +++ b/components/slack_v2/sources/new-saved-message/test-event.mjs @@ -0,0 +1,37 @@ +export default { + "type": "star_added", + "user": "US676PZLY", + "item": { + "type": "message", + "channel": "C055ECVUMLN", + "message": { + "user": "US676PZLY", + "type": "message", + "ts": "1718379912.272779", + "client_msg_id": "def19b3b-4283-47bd-a2da-f32b35c0329c", + "text": "hello", + "team": "TS8319547", + "blocks": [ + { + "type": "rich_text", + "block_id": "ZL1yL", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "hello" + } + ] + } + ] + } + ], + "permalink": "https://michellestest-j1q3506.slack.com/archives/C055ECVUMLN/p1718379912272779" + }, + "date_create": 1718385156 + }, + "event_ts": "1718385156.694322", + "pipedream_msg_id": "pd_1718385158733_tl8yx25evl" +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-user-added/new-user-added.mjs b/components/slack_v2/sources/new-user-added/new-user-added.mjs new file mode 100644 index 0000000000000..03db742683377 --- /dev/null +++ b/components/slack_v2/sources/new-user-added/new-user-added.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-user-added", + name: "New User Added (Instant)", + version: "0.0.5", + description: "Emit new event when a new member joins a workspace.", + type: "source", + dedupe: "unique", + props: { + ...common.props, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return [ + "team_join", + ]; + }, + }, + }, + methods: { + ...common.methods, + getSummary({ user: { name } }) { + return `New User: ${name}`; + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-user-added/test-event.mjs b/components/slack_v2/sources/new-user-added/test-event.mjs new file mode 100644 index 0000000000000..3bcd19da42f6a --- /dev/null +++ b/components/slack_v2/sources/new-user-added/test-event.mjs @@ -0,0 +1,48 @@ +export default { + "type": "team_join", + "user": { + "id": "U080GULP8SD", + "team_id": "TS8319547", + "name": "", + "deleted": false, + "color": "9b3b45", + "real_name": "", + "tz": "America/New_York", + "tz_label": "Eastern Standard Time", + "tz_offset": -18000, + "profile": { + "title": "", + "phone": "", + "skype": "", + "real_name": "", + "real_name_normalized": "", + "display_name": "", + "display_name_normalized": "", + "fields": {}, + "status_text": "", + "status_emoji": "", + "status_emoji_display_info": [], + "status_expiration": 0, + "avatar_hash": "g96b6e8b38c2", + "email": "", + "first_name": "", + "last_name": "", + "status_text_canonical": "", + "team": "TS8319547" + }, + "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": 1731094476, + "is_email_confirmed": true, + "who_can_share_contact_card": "EVERYONE", + "presence": "away" + }, + "cache_ts": 1731094476, + "event_ts": "1731094477.001400", + "pipedream_msg_id": "pd_1731094479305_v1ic236by8" +} \ No newline at end of file diff --git a/components/slack_v2/sources/new-user-mention/new-user-mention.mjs b/components/slack_v2/sources/new-user-mention/new-user-mention.mjs new file mode 100644 index 0000000000000..15efc422c870d --- /dev/null +++ b/components/slack_v2/sources/new-user-mention/new-user-mention.mjs @@ -0,0 +1,124 @@ +import common from "../common/base.mjs"; +import constants from "../common/constants.mjs"; +import sampleEmit from "./test-event.mjs"; +import sharedConstants from "../../common/constants.mjs"; + +export default { + ...common, + key: "slack-new-user-mention", + name: "New User Mention (Instant)", + version: "0.1.0", + description: "Emit new event when a username or specific keyword is mentioned in a channel", + type: "source", + dedupe: "unique", + props: { + ...common.props, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ + types: [ + sharedConstants.CHANNEL_TYPE.PUBLIC, + sharedConstants.CHANNEL_TYPE.PRIVATE, + ], + }), + ], + type: "string[]", + label: "Channels", + description: "Select one or more channels to monitor for new messages.", + optional: true, + }, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return this.conversations || [ + "message", + ]; + }, + }, + user: { + propDefinition: [ + common.props.slack, + "user", + ], + }, + keyword: { + propDefinition: [ + common.props.slack, + "keyword", + ], + optional: true, + }, + ignoreBot: { + propDefinition: [ + common.props.slack, + "ignoreBot", + ], + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New mention received"; + }, + async processEvent(event) { + const { + type: msgType, + subtype, + bot_id: botId, + text, + blocks = [], + } = event; + const [ + { + elements: [ + { elements = [] } = {}, + ] = [], + } = {}, + ] = blocks; + + if (msgType !== "message") { + console.log(`Ignoring event with unexpected type "${msgType}"`); + return; + } + + // This source is designed to just emit an event for each new message received. + // Due to inconsistencies with the shape of message_changed and message_deleted + // events, we are ignoring them for now. If you want to handle these types of + // events, feel free to change this code!! + if (subtype && !constants.ALLOWED_SUBTYPES.includes(subtype)) { + console.log(`Ignoring message with subtype. "${subtype}"`); + return; + } + + if ((this.ignoreBot) && (subtype === constants.SUBTYPE.BOT_MESSAGE || botId)) { + return; + } + + let emitEvent = false; + if (elements) { + let userMatch = false; + for (const item of elements) { + if (item.user_id && item.user_id === this.user) { + userMatch = true; + break; + } + } + if (userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)) { + emitEvent = true; + } + } + if (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE) { + emitEvent = true; + } + + if (emitEvent) { + return event; + } + }, + }, + sampleEmit, +}; diff --git a/components/slack_v2/sources/new-user-mention/test-event.mjs b/components/slack_v2/sources/new-user-mention/test-event.mjs new file mode 100644 index 0000000000000..7c85b12599e3d --- /dev/null +++ b/components/slack_v2/sources/new-user-mention/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "user": "US676PZLY", + "type": "message", + "ts": "1716404766.096289", + "client_msg_id": "b26387fd-5afe-46a9-bf63-a7aabd6fb40f", + "text": "hello", + "team": "TS8319547", + "blocks": [ + { + "type": "rich_text", + "block_id": "aY6KK", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "hello" + } + ] + } + ] + } + ], + "channel": "CS8319KD5", + "event_ts": "1716404766.096289", + "channel_type": "channel" +} \ No newline at end of file From fc32b4bbdd38875b54485ec9f2e42aec4a201cb6 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:17:11 -0400 Subject: [PATCH 16/27] revert changes to the Slack app and components --- .../add-emoji-reaction/add-emoji-reaction.mjs | 2 +- .../approve-workflow/approve-workflow.mjs | 2 +- .../archive-channel/archive-channel.mjs | 2 +- .../slack/actions/common/send-message.mjs | 26 +-- .../actions/create-channel/create-channel.mjs | 2 +- .../create-reminder/create-reminder.mjs | 2 +- .../slack/actions/delete-file/delete-file.mjs | 2 +- .../actions/delete-message/delete-message.mjs | 2 +- .../actions/find-message/find-message.mjs | 177 +++-------------- .../find-user-by-email/find-user-by-email.mjs | 2 +- .../slack/actions/get-file/get-file.mjs | 22 +- .../invite-user-to-channel.mjs | 2 +- .../slack/actions/kick-user/kick-user.mjs | 2 +- .../actions/list-channels/list-channels.mjs | 2 +- .../slack/actions/list-files/list-files.mjs | 22 +- .../list-group-members/list-group-members.mjs | 2 +- .../list-members-in-channel.mjs | 2 +- .../actions/list-replies/list-replies.mjs | 10 +- .../slack/actions/list-users/list-users.mjs | 2 +- .../reply-to-a-message/reply-to-a-message.mjs | 2 +- .../send-block-kit-message.mjs | 2 +- .../send-large-message/send-large-message.mjs | 19 +- .../send-message-advanced.mjs | 2 +- .../send-message-to-channel.mjs | 2 +- .../send-message-to-user-or-group.mjs | 9 +- .../actions/send-message/send-message.mjs | 2 +- .../set-channel-description.mjs | 2 +- .../set-channel-topic/set-channel-topic.mjs | 2 +- .../slack/actions/set-status/set-status.mjs | 2 +- .../update-group-members.mjs | 6 +- .../actions/update-message/update-message.mjs | 2 +- .../actions/update-profile/update-profile.mjs | 2 +- .../slack/actions/upload-file/upload-file.mjs | 2 +- .../verify-slack-signature.mjs | 65 ++++++ components/slack/slack.app.mjs | 188 ++---------------- .../new-channel-created.mjs | 2 +- .../new-direct-message/new-direct-message.mjs | 53 +++++ .../sources/new-direct-message/test-event.mjs | 28 +++ .../new-interaction-event-received.mjs | 2 +- .../new-keyword-mention.mjs | 46 ++++- .../new-message-in-channels.mjs | 9 +- .../new-reaction-added/new-reaction-added.mjs | 14 +- .../new-saved-message/new-saved-message.mjs | 2 +- .../sources/new-user-added/new-user-added.mjs | 2 +- .../new-user-mention/new-user-mention.mjs | 9 +- 45 files changed, 291 insertions(+), 470 deletions(-) create mode 100644 components/slack/actions/verify-slack-signature/verify-slack-signature.mjs create mode 100644 components/slack/sources/new-direct-message/new-direct-message.mjs create mode 100644 components/slack/sources/new-direct-message/test-event.mjs diff --git a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs index 86e55b66dbd53..010ee53ed3e6c 100644 --- a/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs +++ b/components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -4,7 +4,7 @@ export default { key: "slack-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", + version: "0.0.16", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/approve-workflow/approve-workflow.mjs b/components/slack/actions/approve-workflow/approve-workflow.mjs index 3c2a5403bf9ad..fb8a147c59b85 100644 --- a/components/slack/actions/approve-workflow/approve-workflow.mjs +++ b/components/slack/actions/approve-workflow/approve-workflow.mjs @@ -5,7 +5,7 @@ export default { key: "slack-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", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/archive-channel/archive-channel.mjs b/components/slack/actions/archive-channel/archive-channel.mjs index 2bf96a5aae7c7..9ce69641c5627 100644 --- a/components/slack/actions/archive-channel/archive-channel.mjs +++ b/components/slack/actions/archive-channel/archive-channel.mjs @@ -5,7 +5,7 @@ export default { key: "slack-archive-channel", name: "Archive Channel", description: "Archive a channel. [See the documentation](https://api.slack.com/methods/conversations.archive)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/common/send-message.mjs b/components/slack/actions/common/send-message.mjs index 37c1574a8196d..ff923367dbdbb 100644 --- a/components/slack/actions/common/send-message.mjs +++ b/components/slack/actions/common/send-message.mjs @@ -9,12 +9,6 @@ export default { "as_user", ], }, - addToChannel: { - propDefinition: [ - slack, - "addToChannel", - ], - }, post_at: { propDefinition: [ slack, @@ -187,14 +181,6 @@ export default { }, }, async run({ $ }) { - const channelId = await this.getChannelId(); - - if (this.addToChannel) { - await this.slack.maybeAddAppToChannels([ - channelId, - ]); - } - let blocks = this.blocks; if (!blocks) { @@ -230,7 +216,7 @@ export default { const obj = { text: this.text, - channel: channelId, + channel: await this.getChannelId(), attachments: this.attachments, unfurl_links: this.unfurl_links, unfurl_media: this.unfurl_media, @@ -255,7 +241,15 @@ export default { const { channel } = await this.slack.conversationsInfo({ channel: resp.channel, }); - const channelName = await this.slack.getChannelDisplayName(channel); + let channelName = `#${channel?.name}`; + if (channel.is_im) { + const { profile } = await this.slack.getUserProfile({ + user: channel.user, + }); + channelName = `@${profile.real_name}`; + } else if (channel.is_mpim) { + channelName = `@${channel.purpose.value}`; + } $.export("$summary", `Successfully sent a message to ${channelName}`); return resp; }, diff --git a/components/slack/actions/create-channel/create-channel.mjs b/components/slack/actions/create-channel/create-channel.mjs index 79698261c6bb1..d14ea45272547 100644 --- a/components/slack/actions/create-channel/create-channel.mjs +++ b/components/slack/actions/create-channel/create-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-channel", name: "Create a Channel", description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)", - version: "0.0.26", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/create-reminder/create-reminder.mjs b/components/slack/actions/create-reminder/create-reminder.mjs index 44c00aed8045b..7d3ce551d2ffc 100644 --- a/components/slack/actions/create-reminder/create-reminder.mjs +++ b/components/slack/actions/create-reminder/create-reminder.mjs @@ -4,7 +4,7 @@ export default { key: "slack-create-reminder", name: "Create Reminder", description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)", - version: "0.0.26", + version: "0.0.25", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/delete-file/delete-file.mjs b/components/slack/actions/delete-file/delete-file.mjs index 5ef199841fb80..0648c96c71ba5 100644 --- a/components/slack/actions/delete-file/delete-file.mjs +++ b/components/slack/actions/delete-file/delete-file.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-file", name: "Delete File", description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/delete-message/delete-message.mjs b/components/slack/actions/delete-message/delete-message.mjs index 6a3642bb64e41..7dd9b92861992 100644 --- a/components/slack/actions/delete-message/delete-message.mjs +++ b/components/slack/actions/delete-message/delete-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-delete-message", name: "Delete Message", description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)", - version: "0.1.0", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/find-message/find-message.mjs b/components/slack/actions/find-message/find-message.mjs index 927ab5d33b9f6..85d5bb317c9b2 100644 --- a/components/slack/actions/find-message/find-message.mjs +++ b/components/slack/actions/find-message/find-message.mjs @@ -3,8 +3,8 @@ import slack from "../../slack.app.mjs"; export default { key: "slack-find-message", name: "Find Message", - description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)", - version: "0.1.0", + description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/search.messages)", + version: "0.0.26", annotations: { destructiveHint: false, openWorldHint: true, @@ -19,11 +19,18 @@ export default { "query", ], }, + teamId: { + propDefinition: [ + slack, + "team", + ], + optional: true, + }, maxResults: { type: "integer", label: "Max Results", description: "The maximum number of messages to return", - default: 20, + default: 100, optional: true, }, sort: { @@ -47,161 +54,29 @@ export default { optional: true, }, }, - methods: { - normalizeAssistantMatch(match) { - if (!match || typeof match !== "object") { - return match; - } - const { - author_user_id: authorUserId, - team_id: teamId, - channel_id: channelId, - message_ts: messageTs, - content, - permalink, - is_author_bot: isAuthorBot, - message, - channel, - ...rest - } = match; - const baseMessage = typeof message === "object" - ? message - : {}; - const channelInfo = channel && typeof channel === "object" - ? { - ...channel, - id: channel.id || channelId, - } - : channelId - ? { - id: channelId, - } - : undefined; - const normalized = { - type: "message", - user: authorUserId, - team: teamId, - ts: messageTs, - text: content, - permalink, - channel: channelInfo, - ...baseMessage, - ...rest, - }; - if (isAuthorBot !== undefined && normalized.is_author_bot === undefined) { - normalized.is_author_bot = isAuthorBot; - } - if (normalized.text == null) { - normalized.text = baseMessage.text || content; - } - if (normalized.ts == null) { - normalized.ts = baseMessage.ts || messageTs; - } - if (!normalized.channel && baseMessage.channel) { - normalized.channel = baseMessage.channel; - } else if (normalized.channel && baseMessage.channel && typeof baseMessage.channel === "object") { - normalized.channel = { - ...normalized.channel, - ...baseMessage.channel, - }; - } - return normalized; - }, - async searchWithAssistant(baseParams, maxResults) { - const matches = []; - let cursor; - - do { - const response = await this.slack.assistantSearch({ - ...baseParams, - channel_types: "public_channel,private_channel", - cursor, - }); - const messages = (response.results?.messages || []) - .map((item) => this.normalizeAssistantMatch(item)); - matches.push(...messages); - cursor = response.response_metadata?.next_cursor; - } while (cursor && matches.length < maxResults); - - return matches.slice(0, maxResults); - }, - async searchWithSearchMessages(baseParams, maxResults) { - const matches = []; - let page = 1; - const count = Math.min(Math.max(maxResults, 1), 100); - - while (matches.length < maxResults) { - const response = await this.slack.searchMessages({ - ...baseParams, - count, - page, - }); - const pageMatches = response.messages?.matches || []; - matches.push(...pageMatches); - - if (matches.length >= maxResults) { - break; - } - - const pagination = response.messages?.pagination; - const paging = response.messages?.paging; - const hasMore = pagination - ? pagination.page < pagination.page_count - : paging - ? paging.page < paging.pages - : false; - - if (!hasMore) { - break; - } - - page += 1; - } - - return matches.slice(0, maxResults); - }, - shouldFallbackToSearchMessages(error) { - const errorCode = typeof error === "string" - ? error - : error?.data?.error || error?.message; - - if (!errorCode?.includes("missing_scope")) { - return false; - } - - const providedSources = [ - error?.data?.provided, - error?.provided, - error?.original?.data?.provided, - ].filter(Boolean); - - const providedScopes = providedSources - .flatMap((value) => Array.isArray(value) - ? value - : String(value).split(",")) - .map((scope) => scope.trim()) - .filter(Boolean); - - return providedScopes.includes("search:read"); - }, - }, async run({ $ }) { - const maxResults = Math.max(this.maxResults ?? 20, 1); - const baseParams = { + const matches = []; + const params = { query: this.query, + team_id: this.teamId, sort: this.sort, sort_dir: this.sortDirection, + page: 1, }; - let matches; + let hasMore; - try { - matches = await this.searchWithAssistant(baseParams, maxResults); - } catch (error) { - if (this.shouldFallbackToSearchMessages(error)) { - matches = await this.searchWithSearchMessages(baseParams, maxResults); - } else { - throw error; + do { + const { messages } = await this.slack.searchMessages(params); + matches.push(...messages.matches); + if (matches.length >= this.maxResults) { + break; } + hasMore = messages.matches?.length; + params.page++; + } while (hasMore); + + if (matches.length > this.maxResults) { + matches.length = this.maxResults; } $.export("$summary", `Found ${matches.length} matching message${matches.length === 1 diff --git a/components/slack/actions/find-user-by-email/find-user-by-email.mjs b/components/slack/actions/find-user-by-email/find-user-by-email.mjs index e1f35afe907ac..cc12d5d439bf0 100644 --- a/components/slack/actions/find-user-by-email/find-user-by-email.mjs +++ b/components/slack/actions/find-user-by-email/find-user-by-email.mjs @@ -4,7 +4,7 @@ export default { key: "slack-find-user-by-email", name: "Find User by Email", description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/get-file/get-file.mjs b/components/slack/actions/get-file/get-file.mjs index 601eb9528e7a5..9fc1afaa35b6a 100644 --- a/components/slack/actions/get-file/get-file.mjs +++ b/components/slack/actions/get-file/get-file.mjs @@ -1,11 +1,10 @@ -import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { key: "slack-get-file", name: "Get File", description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)", - version: "0.1.0", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, @@ -18,19 +17,6 @@ export default { propDefinition: [ slack, "conversation", - () => ({ - types: [ - constants.CHANNEL_TYPE.PUBLIC, - constants.CHANNEL_TYPE.PRIVATE, - ], - }), - ], - description: "Select a public or private channel", - }, - addToChannel: { - propDefinition: [ - slack, - "addToChannel", ], }, file: { @@ -44,12 +30,6 @@ export default { }, }, async run({ $ }) { - if (this.addToChannel) { - await this.slack.maybeAddAppToChannels([ - this.conversation, - ]); - } - const response = await this.slack.getFileInfo({ file: this.file, }); diff --git a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs index 28f08457fb2a2..4d2e8240a4ae3 100644 --- a/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs +++ b/components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-invite-user-to-channel", name: "Invite User to Channel", description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/kick-user/kick-user.mjs b/components/slack/actions/kick-user/kick-user.mjs index 2a3cabc3696db..eacd9d761f9a7 100644 --- a/components/slack/actions/kick-user/kick-user.mjs +++ b/components/slack/actions/kick-user/kick-user.mjs @@ -5,7 +5,7 @@ export default { key: "slack-kick-user", name: "Kick User", description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/list-channels/list-channels.mjs b/components/slack/actions/list-channels/list-channels.mjs index 27606438156ff..ef00493f71f77 100644 --- a/components/slack/actions/list-channels/list-channels.mjs +++ b/components/slack/actions/list-channels/list-channels.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-channels", name: "List Channels", description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-files/list-files.mjs b/components/slack/actions/list-files/list-files.mjs index 48be1110dd452..72881dd96edfb 100644 --- a/components/slack/actions/list-files/list-files.mjs +++ b/components/slack/actions/list-files/list-files.mjs @@ -1,11 +1,10 @@ -import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { key: "slack-list-files", name: "List Files", description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)", - version: "0.1.0", + version: "0.0.52", annotations: { destructiveHint: false, openWorldHint: true, @@ -18,19 +17,6 @@ export default { propDefinition: [ slack, "conversation", - () => ({ - types: [ - constants.CHANNEL_TYPE.PUBLIC, - constants.CHANNEL_TYPE.PRIVATE, - ], - }), - ], - description: "Select a public or private channel", - }, - addToChannel: { - propDefinition: [ - slack, - "addToChannel", ], }, team_id: { @@ -61,12 +47,6 @@ export default { }, }, async run({ $ }) { - if (this.addToChannel) { - await this.slack.maybeAddAppToChannels([ - this.conversation, - ]); - } - const allFiles = []; const params = { channel: this.conversation, diff --git a/components/slack/actions/list-group-members/list-group-members.mjs b/components/slack/actions/list-group-members/list-group-members.mjs index 0ff26898a734c..a1ba13acdf935 100644 --- a/components/slack/actions/list-group-members/list-group-members.mjs +++ b/components/slack/actions/list-group-members/list-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-group-members", name: "List Group Members", description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)", - version: "0.0.10", + version: "0.0.9", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs index ff038cf536b26..63981dcf5b29b 100644 --- a/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs +++ b/components/slack/actions/list-members-in-channel/list-members-in-channel.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-members-in-channel", name: "List Members in Channel", description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/list-replies/list-replies.mjs b/components/slack/actions/list-replies/list-replies.mjs index 413ac8aefddbc..28cc4f750a0c6 100644 --- a/components/slack/actions/list-replies/list-replies.mjs +++ b/components/slack/actions/list-replies/list-replies.mjs @@ -1,11 +1,10 @@ -import constants from "../../common/constants.mjs"; import slack from "../../slack.app.mjs"; export default { key: "slack-list-replies", name: "List Replies", description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, @@ -18,14 +17,7 @@ export default { propDefinition: [ slack, "conversation", - () => ({ - types: [ - constants.CHANNEL_TYPE.PUBLIC, - constants.CHANNEL_TYPE.PRIVATE, - ], - }), ], - description: "Select a public or private channel", }, timestamp: { propDefinition: [ diff --git a/components/slack/actions/list-users/list-users.mjs b/components/slack/actions/list-users/list-users.mjs index 758d47f8f215e..f4d7733cf7eb0 100644 --- a/components/slack/actions/list-users/list-users.mjs +++ b/components/slack/actions/list-users/list-users.mjs @@ -4,7 +4,7 @@ export default { key: "slack-list-users", name: "List Users", description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs index 167c1b081a690..6054fcd4efa19 100644 --- a/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs +++ b/components/slack/actions/reply-to-a-message/reply-to-a-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-reply-to-a-message", name: "Reply to a Message Thread", description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.2.0", + version: "0.1.29", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs index 7fa53d5fc6559..f78c9489ce809 100644 --- a/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs +++ b/components/slack/actions/send-block-kit-message/send-block-kit-message.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-block-kit-message", name: "Build and Send a Block Kit Message", description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).", - version: "0.5.0", + version: "0.4.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-large-message/send-large-message.mjs b/components/slack/actions/send-large-message/send-large-message.mjs index 474230475d486..9d0ee386a6dd5 100644 --- a/components/slack/actions/send-large-message/send-large-message.mjs +++ b/components/slack/actions/send-large-message/send-large-message.mjs @@ -5,7 +5,7 @@ export default { key: "slack-send-large-message", name: "Send a Large Message (3000+ characters)", description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.1.0", + version: "0.0.24", annotations: { destructiveHint: false, openWorldHint: true, @@ -35,12 +35,6 @@ export default { ...common.props, }, async run({ $ }) { - if (this.addToChannel) { - await this.slack.maybeAddAppToChannels([ - this.conversation, - ]); - } - if (this.include_sent_via_pipedream_flag) { const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`; @@ -85,10 +79,19 @@ export default { } else { response = await this.slack.postChatMessage(obj); } + const { channel } = await this.slack.conversationsInfo({ channel: response.channel, }); - const channelName = await this.slack.getChannelDisplayName(channel); + let channelName = `#${channel?.name}`; + if (channel.is_im) { + const { profile } = await this.slack.getUserProfile({ + user: channel.user, + }); + channelName = `@${profile.real_name}`; + } else if (channel.is_mpim) { + channelName = `@${channel.purpose.value}`; + } $.export("$summary", `Successfully sent a message to ${channelName}`); return response; }, diff --git a/components/slack/actions/send-message-advanced/send-message-advanced.mjs b/components/slack/actions/send-message-advanced/send-message-advanced.mjs index 6b8df73332ac4..ad2d604633db7 100644 --- a/components/slack/actions/send-message-advanced/send-message-advanced.mjs +++ b/components/slack/actions/send-message-advanced/send-message-advanced.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-message-advanced", name: "Send Message (Advanced)", description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", - version: "0.1.0", + version: "0.0.7", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs index ac00b08c5e5b8..55d1119349614 100644 --- a/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs +++ b/components/slack/actions/send-message-to-channel/send-message-to-channel.mjs @@ -6,7 +6,7 @@ export default { key: "slack-send-message-to-channel", name: "Send Message to Channel", description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.1.0", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs index 60834909dec8a..ecbf1965e6077 100644 --- a/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +++ b/components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -7,7 +7,7 @@ export default { key: "slack-send-message-to-user-or-group", name: "Send Message to User or Group", description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.1.0", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, @@ -52,13 +52,6 @@ export default { ], }, ...common.props, - // eslint-disable-next-line pipedream/props-label, pipedream/props-description - addToChannel: { - type: "boolean", - ...common.props.addToChannel, - disabled: true, - hidden: true, - }, }, methods: { ...common.methods, diff --git a/components/slack/actions/send-message/send-message.mjs b/components/slack/actions/send-message/send-message.mjs index e8a67df460932..06dc22d8b7f7a 100644 --- a/components/slack/actions/send-message/send-message.mjs +++ b/components/slack/actions/send-message/send-message.mjs @@ -6,7 +6,7 @@ export default { key: "slack-send-message", name: "Send Message", description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", - version: "0.1.0", + version: "0.0.20", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/set-channel-description/set-channel-description.mjs b/components/slack/actions/set-channel-description/set-channel-description.mjs index 7567d090efd75..7a544b28c6362 100644 --- a/components/slack/actions/set-channel-description/set-channel-description.mjs +++ b/components/slack/actions/set-channel-description/set-channel-description.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-description", name: "Set Channel Description", description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)", - version: "0.0.10", + version: "0.0.9", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/set-channel-topic/set-channel-topic.mjs b/components/slack/actions/set-channel-topic/set-channel-topic.mjs index fa7a5b50de83a..9f12041184de0 100644 --- a/components/slack/actions/set-channel-topic/set-channel-topic.mjs +++ b/components/slack/actions/set-channel-topic/set-channel-topic.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-channel-topic", name: "Set Channel Topic", description: "Set the topic on a selected channel. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/set-status/set-status.mjs b/components/slack/actions/set-status/set-status.mjs index 954317d4f9487..10f0069f743fd 100644 --- a/components/slack/actions/set-status/set-status.mjs +++ b/components/slack/actions/set-status/set-status.mjs @@ -4,7 +4,7 @@ export default { key: "slack-set-status", name: "Set Status", description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.10", + version: "0.0.9", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/update-group-members/update-group-members.mjs b/components/slack/actions/update-group-members/update-group-members.mjs index 79b78ca6b1a0c..fdb927619b3ac 100644 --- a/components/slack/actions/update-group-members/update-group-members.mjs +++ b/components/slack/actions/update-group-members/update-group-members.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-group-members", name: "Update Groups Members", description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", - version: "0.0.10", + version: "0.0.9", annotations: { destructiveHint: true, openWorldHint: true, @@ -51,8 +51,8 @@ export default { async run({ $ }) { const { userGroup, - usersToAdd = [], - usersToRemove = [], + usersToAdd, + usersToRemove, team, } = this; let { users } = await this.slack.listGroupMembers({ diff --git a/components/slack/actions/update-message/update-message.mjs b/components/slack/actions/update-message/update-message.mjs index 7ff12d7b56427..4f797ffabb3af 100644 --- a/components/slack/actions/update-message/update-message.mjs +++ b/components/slack/actions/update-message/update-message.mjs @@ -4,7 +4,7 @@ export default { key: "slack-update-message", name: "Update Message", description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)", - version: "0.2.0", + version: "0.1.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/update-profile/update-profile.mjs b/components/slack/actions/update-profile/update-profile.mjs index 270bc6f78474b..e3c871d7d0246 100644 --- a/components/slack/actions/update-profile/update-profile.mjs +++ b/components/slack/actions/update-profile/update-profile.mjs @@ -5,7 +5,7 @@ export default { key: "slack-update-profile", name: "Update Profile", description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", - version: "0.0.25", + version: "0.0.24", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/slack/actions/upload-file/upload-file.mjs b/components/slack/actions/upload-file/upload-file.mjs index ddc1926d77a8b..56240f1c4b169 100644 --- a/components/slack/actions/upload-file/upload-file.mjs +++ b/components/slack/actions/upload-file/upload-file.mjs @@ -8,7 +8,7 @@ export default { key: "slack-upload-file", name: "Upload File", description: "Upload a file. [See the documentation](https://api.slack.com/messaging/files#uploading_files)", - version: "0.1.3", + version: "0.1.2", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs b/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs new file mode 100644 index 0000000000000..15997e6d2c906 --- /dev/null +++ b/components/slack/actions/verify-slack-signature/verify-slack-signature.mjs @@ -0,0 +1,65 @@ +import crypto from "crypto"; +import slack from "../../slack.app.mjs"; + +export default { + key: "slack-verify-slack-signature", + name: "Verify Slack Signature", + description: "Verifying requests from Slack, slack signs its requests using a secret that's unique to your app. [See the documentation](https://api.slack.com/authentication/verifying-requests-from-slack)", + version: "0.0.17", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + type: "action", + props: { + slack, + slackSigningSecret: { + type: "string", + label: "Signing Secret", + description: "Slack [Signing Secret](https://api.slack.com/authentication/verifying-requests-from-slack#:~:text=Slack%20Signing%20Secret%2C%20available%20in%20the%20app%20admin%20panel%20under%20Basic%20Info.), available in the app admin panel under Basic Info.", + secret: true, + }, + slackSignature: { + type: "string", + label: "X-Slack-Signature", + description: "Slack signature (from X-Slack-Signature header).", + }, + slackRequestTimestamp: { + type: "string", + label: "X-Slack-Request-Timestamp", + description: "Slack request timestamp (from X-Slack-Request-Timestamp header).", + }, + requestBody: { + type: "any", + label: "Request Body", + description: "The body of the request to be verified.", + }, + }, + async run({ $ }) { + const { + slackSignature, + slackRequestTimestamp, + requestBody, + slackSigningSecret, + } = this; + const requestBodyStr = typeof (requestBody) === "string" ? + requestBody : + JSON.stringify(requestBody); + const sigBaseString = `v0:${slackRequestTimestamp}:${requestBodyStr}`; + const sha256Hex = crypto.createHmac("sha256", slackSigningSecret) + .update(sigBaseString, "utf8") + .digest("hex"); + const mySignature = `v0=${sha256Hex}`; + if (crypto.timingSafeEqual(Buffer.from(mySignature, "utf8"), Buffer.from(slackSignature, "utf8"))) { + $.export("$summary", `Successfully verified the request with "${slackSignature}" signature`); + return { + success: true, + }; + } + $.export("$summary", "Slack signature mismatch with provided properties, it may be a configuration issue."); + return { + success: false, + }; + }, +}; diff --git a/components/slack/slack.app.mjs b/components/slack/slack.app.mjs index ebe0650a41164..401d853f0f6cb 100644 --- a/components/slack/slack.app.mjs +++ b/components/slack/slack.app.mjs @@ -2,7 +2,6 @@ import { WebClient } from "@slack/web-api"; import constants from "./common/constants.mjs"; import get from "lodash/get.js"; import retry from "async-retry"; -import { ConfigurationError } from "@pipedream/platform"; export default { type: "app", @@ -28,11 +27,11 @@ export default { conversationsResp.conversations = conversationsResp.conversations .filter((c) => members.includes(c.user || c.id)); } - const userIds = conversationsResp.conversations.map(({ user }) => user).filter(Boolean); - const realNames = await this.realNameLookup(userIds); + const userIds = conversationsResp.conversations.map(({ user }) => user); + const userNames = await this.userNameLookup(userIds); return { - options: conversationsResp.conversations.filter((c) => c.user).map((c) => ({ - label: `${realNames[c.user]}`, + options: conversationsResp.conversations.map((c) => ({ + label: `@${userNames[c.user]}`, value: c.user || c.id, })), context: { @@ -121,41 +120,24 @@ export default { } } const conversationsResp = await this.availableConversations(types.join(), cursor, true); - let conversations, userIds, userNames, realNames; + let conversations, userNames; if (types.includes("im")) { conversations = conversationsResp.conversations; - userIds = conversations.map(({ user }) => user).filter(Boolean); + const userIds = conversations.map(({ user }) => user); + userNames = await this.userNameLookup(userIds); } else { conversations = conversationsResp.conversations.filter((c) => !c.is_im); } - if (types.includes("mpim")) { - userNames = [ - ...new Set(conversations.filter((c) => c.is_mpim).map((c) => c.purpose.value) - .map((v) => v.match(/@[\w.-]+/g) || []) - .flat() - .map((u) => u.slice(1))), - ]; - } - if ((userIds?.length > 0) || (userNames?.length > 0)) { - // Look up real names for userIds and userNames at the same time to - // minimize number of API calls. - realNames = await this.realNameLookup(userIds, userNames); - } - return { options: conversations.map((c) => { if (c.is_im) { return { - label: `Direct messaging with: ${realNames[c.user]}`, + label: `Direct messaging with: @${userNames[c.user]}`, value: c.id, }; } else if (c.is_mpim) { - const usernames = c.purpose.value.match(/@[\w.-]+/g) || []; - const realnames = usernames.map((u) => realNames[u.slice(1)] || u); return { - label: realnames.length - ? `Group messaging with: ${realnames.join(", ")}` - : c.purpose.value, + label: c.purpose.value, value: c.id, }; } else { @@ -465,12 +447,6 @@ export default { default: 1, optional: true, }, - addToChannel: { - type: "boolean", - label: "Add app to channel automatically?", - description: "If `true`, the app will be added to the specified non-DM channel(s) automatically. If `false`, you must add the app to the channel manually. Defaults to `true`.", - default: true, - }, }, methods: { getChannelLabel(resource) { @@ -488,89 +464,29 @@ export default { mySlackId() { return this.$auth.oauth_uid; }, - getToken(opts = {}) { - // Use bot token if asBot is true and available, otherwise use user token. - const botToken = this.getBotToken(); - const userToken = this.$auth.oauth_access_token; - return (opts.asBot && botToken) - ? botToken - : userToken; - }, - getBotToken() { - return this.$auth.bot_token; - }, - async getChannelDisplayName(channel) { - if (channel.user) { - try { - const { profile } = await this.getUserProfile({ - user: channel.user, - }); - return `@${profile.real_name || profile?.real_name}`; - } catch { - return "user"; - } - } else if (channel.is_mpim) { - try { - const { members } = await this.listChannelMembers({ - channel: channel.id, - }); - const users = await Promise.all(members.map((m) => this.getUserProfile({ - user: m, - }))); - const realNames = users.map((u) => u.profile?.real_name || u.real_name); - return `Group Messaging with: ${realNames.join(", ")}`; - } catch { - return `Group Messaging with: ${channel.purpose.value}`; - } - } - return `#${channel?.name}`; + getToken() { + return this.$auth.oauth_access_token; }, /** * Returns a Slack Web Client object authenticated with the user's access * token */ - sdk(opts = {}) { - return new WebClient(this.getToken(opts), { + sdk() { + return new WebClient(this.getToken(), { rejectRateLimitedCalls: true, - slackApiUrl: this.$auth.base_url, }); }, async makeRequest({ - method = "", throwRateLimitError = false, asBot, as_user, ...args + method = "", throwRateLimitError = false, ...args } = {}) { - // Passing as_user as false with a v2 user token lacking the deprecated - // `chat:write:bot` scope results in an error. If as_user is false and a - // bot token is available, use the bot token and omit as_user. Otherwise, - // pass as_user through. - if (as_user === false && Boolean(this.getBotToken())) { - asBot = true; - } else { - args.as_user = as_user; - } - const props = method.split("."); const sdk = props.reduce((reduction, prop) => - reduction[prop], this.sdk({ - asBot, - })); + reduction[prop], this.sdk()); - let response; - try { - response = await this._withRetries(() => sdk(args), throwRateLimitError); - } catch (error) { - if ([ - "not_in_channel", - "channel_not_found", - ].some((errorType) => `${error}`.includes(errorType)) && asBot) { - const followUp = method.startsWith("chat.") - ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." - : "Ensure the bot is a member of the channel."; - throw new ConfigurationError(`${error}\n${followUp}`); - } - throw error; - } + const response = await this._withRetries(() => sdk(args), throwRateLimitError); if (!response.ok) { + console.log(`Error in response with method ${method}`, response.error); throw response.error; } return response; @@ -649,64 +565,6 @@ export default { } while (cursor && Object.keys(userNames).length < ids.length); return userNames; }, - async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { - let cursor; - const realNames = {}; - do { - const { - members: users, - response_metadata: { next_cursor: nextCursor }, - } = await this.usersList({ - limit: constants.LIMIT, - cursor, - throwRateLimitError, - ...args, - }); - - for (const user of users) { - if (ids.includes(user.id)) { - realNames[user.id] = user.profile.real_name; - } - if (usernames.includes(user.name)) { - realNames[user.name] = user.profile.real_name; - } - } - - cursor = nextCursor; - } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); - return realNames; - }, - async maybeAddAppToChannels(channelIds = []) { - if (!this.getBotToken()) { - console.log("Skipping adding app to channels: bot unavailable."); - return; - } - try { - const { - bot_id, user_id, - } = await this.authTest({ - asBot: true, - }); - if (!bot_id) { - console.log("Skipping adding app to channels: bot not found."); - return; - } - for (const channel of channelIds) { - try { - // Note: Trying to add the app to DM or group DM channels results in - // the error: method_not_supported_for_channel_type - await this.inviteToConversation({ - channel, - users: user_id, - }); - } catch (error) { - console.log(`Unable to add app to channel ${channel}: ${error}`); - } - } - } catch (error) { - console.log(`Unable to add app to channels: ${error}`); - } - }, /** * Checks authentication & identity. * @param {*} args Arguments object @@ -892,12 +750,6 @@ export default { ...args, }); }, - assistantSearch(args = {}) { - args.count ||= constants.LIMIT; - return this.sdk().apiCall("assistant.search.context", { - ...args, - }); - }, /** * Lists reactions made by a user. * User Scopes: `reactions:read` @@ -949,9 +801,6 @@ export default { args.count ||= constants.LIMIT; return this.makeRequest({ method: "files.list", - // Use bot token, if available, since the required `files:read` scope - // is only requested for bot tokens in the Pipedream app. - asBot: true, ...args, }); }, @@ -965,9 +814,6 @@ export default { getFileInfo(args = {}) { return this.makeRequest({ method: "files.info", - // Use bot token, if available, since the required `files:read` scope - // is only requested for bot tokens in the Pipedream app. - asBot: true, ...args, }); }, diff --git a/components/slack/sources/new-channel-created/new-channel-created.mjs b/components/slack/sources/new-channel-created/new-channel-created.mjs index 9d1df1076dc05..7eb2ff9085f4a 100644 --- a/components/slack/sources/new-channel-created/new-channel-created.mjs +++ b/components/slack/sources/new-channel-created/new-channel-created.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-channel-created", name: "New Channel Created (Instant)", - version: "0.0.11", + version: "0.0.10", description: "Emit new event when a new channel is created.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-direct-message/new-direct-message.mjs b/components/slack/sources/new-direct-message/new-direct-message.mjs new file mode 100644 index 0000000000000..389c183951a5d --- /dev/null +++ b/components/slack/sources/new-direct-message/new-direct-message.mjs @@ -0,0 +1,53 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "slack-new-direct-message", + name: "New Direct Message (Instant)", + version: "1.0.23", + description: "Emit new event when a message was posted in a direct message channel", + type: "source", + dedupe: "unique", + props: { + ...common.props, + // eslint-disable-next-line pipedream/props-description,pipedream/props-label + slackApphook: { + type: "$.interface.apphook", + appProp: "slack", + async eventNames() { + return [ + "message.im", + ]; + }, + }, + ignoreBot: { + propDefinition: [ + common.props.slack, + "ignoreBot", + ], + }, + ignoreSelf: { + type: "boolean", + label: "Ignore Messages from Yourself", + description: "Ignores messages sent to yourself", + default: false, + optional: true, + }, + }, + methods: { + ...common.methods, + getSummary() { + return "New direct message received"; + }, + processEvent(event) { + if ((this.ignoreSelf && event.user == this.slack.mySlackId()) + || ((this.ignoreBot) && (event.subtype === "bot_message" || event.bot_id)) + || (event.subtype === "message_changed")) { + return; + } + return event; + }, + }, + sampleEmit, +}; diff --git a/components/slack/sources/new-direct-message/test-event.mjs b/components/slack/sources/new-direct-message/test-event.mjs new file mode 100644 index 0000000000000..d19486ed235f0 --- /dev/null +++ b/components/slack/sources/new-direct-message/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "user": "USLACKBOT", + "type": "message", + "ts": "1716401124.947359", + "text": "Feeling great!", + "team": "TS8319547", + "blocks": [ + { + "type": "rich_text", + "block_id": "bid/", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Feeling great!" + } + ] + } + ] + } + ], + "channel": "DS676Q73J", + "event_ts": "1716401124.947359", + "channel_type": "im", + "pipedream_msg_id": "pd_1716401126905_tjxu6josgz" +} \ No newline at end of file diff --git a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs index 4ec49f2be9653..75b100e999b45 100644 --- a/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs +++ b/components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { name: "New Interaction Events (Instant)", - version: "0.0.21", + version: "0.0.20", key: "slack-new-interaction-event-received", description: "Emit new events on new Slack [interactivity events](https://api.slack.com/interactivity) sourced from [Block Kit interactive elements](https://api.slack.com/interactivity/components), [Slash commands](https://api.slack.com/interactivity/slash-commands), or [Shortcuts](https://api.slack.com/interactivity/shortcuts).", type: "source", diff --git a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs index c6323e562670e..567abdb4806a8 100644 --- a/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs +++ b/components/slack/sources/new-keyword-mention/new-keyword-mention.mjs @@ -1,13 +1,12 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; -import sharedConstants from "../../common/constants.mjs"; export default { ...common, key: "slack-new-keyword-mention", name: "New Keyword Mention (Instant)", - version: "0.1.0", + version: "0.0.8", description: "Emit new event when a specific keyword is mentioned in a channel", type: "source", dedupe: "unique", @@ -17,12 +16,6 @@ export default { propDefinition: [ common.props.slack, "conversation", - () => ({ - types: [ - sharedConstants.CHANNEL_TYPE.PUBLIC, - sharedConstants.CHANNEL_TYPE.PRIVATE, - ], - }), ], type: "string[]", label: "Channels", @@ -52,8 +45,45 @@ export default { ], }, }, + hooks: { + ...common.hooks, + async deploy() { + // emit historical events + const messages = await this.getMatches({ + query: this.keyword, + sort: "timestamp", + }); + const filteredMessages = this.conversations?.length > 0 + ? messages.filter((message) => this.conversations.includes(message.channel.id)) + : messages; + await this.emitHistoricalEvents(filteredMessages.slice(-25).reverse()); + }, + }, methods: { ...common.methods, + async getMatches(params) { + return (await this.slack.searchMessages(params)).messages.matches || []; + }, + async emitHistoricalEvents(messages) { + for (const message of messages) { + const event = await this.processEvent({ + ...message, + subtype: message.subtype || constants.SUBTYPE.PD_HISTORY_MESSAGE, + }); + if (event) { + if (!event.client_msg_id) { + event.pipedream_msg_id = `pd_${Date.now()}_${Math.random().toString(36) + .substr(2, 10)}`; + } + + this.$emit(event, { + id: event.client_msg_id || event.pipedream_msg_id, + summary: this.getSummary(event), + ts: event.event_ts || Date.now(), + }); + } + } + }, getSummary() { return "New keyword mention received"; }, diff --git a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs index 72c9ef83bb751..0de59c7a78749 100644 --- a/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs +++ b/components/slack/sources/new-message-in-channels/new-message-in-channels.mjs @@ -1,13 +1,12 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; -import sharedConstants from "../../common/constants.mjs"; export default { ...common, key: "slack-new-message-in-channels", name: "New Message In Channels (Instant)", - version: "1.1.0", + version: "1.0.25", description: "Emit new event when a new message is posted to one or more channels", type: "source", dedupe: "unique", @@ -17,12 +16,6 @@ export default { propDefinition: [ common.props.slack, "conversation", - () => ({ - types: [ - sharedConstants.CHANNEL_TYPE.PUBLIC, - sharedConstants.CHANNEL_TYPE.PRIVATE, - ], - }), ], type: "string[]", label: "Channels", diff --git a/components/slack/sources/new-reaction-added/new-reaction-added.mjs b/components/slack/sources/new-reaction-added/new-reaction-added.mjs index aaf4edf973db0..6444e3fd9b57b 100644 --- a/components/slack/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack/sources/new-reaction-added/new-reaction-added.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-reaction-added", name: "New Reaction Added (Instant)", - version: "1.2.0", + version: "1.1.26", description: "Emit new event when a member has added an emoji reaction to a message", type: "source", dedupe: "unique", @@ -97,14 +97,10 @@ export default { event.itemUserInfo = itemUserResponse.user; } - try { - event.message = await this.getMessage({ - channel: event.item.channel, - event_ts: event.item.ts, - }); - } catch (err) { - console.log("Error fetching message:", err); - } + event.message = await this.getMessage({ + channel: event.item.channel, + event_ts: event.item.ts, + }); return event; }, diff --git a/components/slack/sources/new-saved-message/new-saved-message.mjs b/components/slack/sources/new-saved-message/new-saved-message.mjs index 2d7a94e5c5b95..fe0f907aabf84 100644 --- a/components/slack/sources/new-saved-message/new-saved-message.mjs +++ b/components/slack/sources/new-saved-message/new-saved-message.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-saved-message", name: "New Saved Message (Instant)", - version: "0.0.7", + version: "0.0.6", description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-user-added/new-user-added.mjs b/components/slack/sources/new-user-added/new-user-added.mjs index 03db742683377..80bde255eb668 100644 --- a/components/slack/sources/new-user-added/new-user-added.mjs +++ b/components/slack/sources/new-user-added/new-user-added.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "slack-new-user-added", name: "New User Added (Instant)", - version: "0.0.5", + version: "0.0.4", description: "Emit new event when a new member joins a workspace.", type: "source", dedupe: "unique", diff --git a/components/slack/sources/new-user-mention/new-user-mention.mjs b/components/slack/sources/new-user-mention/new-user-mention.mjs index 15efc422c870d..5f4371d84f6dc 100644 --- a/components/slack/sources/new-user-mention/new-user-mention.mjs +++ b/components/slack/sources/new-user-mention/new-user-mention.mjs @@ -1,13 +1,12 @@ import common from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; -import sharedConstants from "../../common/constants.mjs"; export default { ...common, key: "slack-new-user-mention", name: "New User Mention (Instant)", - version: "0.1.0", + version: "0.0.8", description: "Emit new event when a username or specific keyword is mentioned in a channel", type: "source", dedupe: "unique", @@ -17,12 +16,6 @@ export default { propDefinition: [ common.props.slack, "conversation", - () => ({ - types: [ - sharedConstants.CHANNEL_TYPE.PUBLIC, - sharedConstants.CHANNEL_TYPE.PRIVATE, - ], - }), ], type: "string[]", label: "Channels", From 5dbafa77b184f3d0ef33ab12cdaac364c0f7e9df Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:30:38 -0400 Subject: [PATCH 17/27] revert formatting changes to unrelated app files --- components/airweave/airweave.app.mjs | 2 +- components/azure_cosmos_db/azure_cosmos_db.app.mjs | 2 +- components/binalyze_air/binalyze_air.app.mjs | 2 +- components/brainbase_labs/brainbase_labs.app.mjs | 2 +- components/clarifai/clarifai.app.mjs | 2 +- components/codefresh/codefresh.app.mjs | 2 +- components/cometapi/cometapi.app.mjs | 2 +- components/google_perspective/google_perspective.app.mjs | 2 +- components/hex/hex.app.mjs | 2 +- components/n1n/n1n.app.mjs | 2 +- components/piwik_pro/piwik_pro.app.mjs | 2 +- components/ups/ups.app.mjs | 2 +- components/xola/xola.app.mjs | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/airweave/airweave.app.mjs b/components/airweave/airweave.app.mjs index 3ea5a4a46801d..53bae861866d0 100644 --- a/components/airweave/airweave.app.mjs +++ b/components/airweave/airweave.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/azure_cosmos_db/azure_cosmos_db.app.mjs b/components/azure_cosmos_db/azure_cosmos_db.app.mjs index 9bca1c2941cef..18b2c3a6feb6e 100644 --- a/components/azure_cosmos_db/azure_cosmos_db.app.mjs +++ b/components/azure_cosmos_db/azure_cosmos_db.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/binalyze_air/binalyze_air.app.mjs b/components/binalyze_air/binalyze_air.app.mjs index c76685631a381..f9672fcd6915e 100644 --- a/components/binalyze_air/binalyze_air.app.mjs +++ b/components/binalyze_air/binalyze_air.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/brainbase_labs/brainbase_labs.app.mjs b/components/brainbase_labs/brainbase_labs.app.mjs index 23cb29ded2a40..c389f6f82b436 100644 --- a/components/brainbase_labs/brainbase_labs.app.mjs +++ b/components/brainbase_labs/brainbase_labs.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/clarifai/clarifai.app.mjs b/components/clarifai/clarifai.app.mjs index 3d5a708bc7c59..92080776dfcaa 100644 --- a/components/clarifai/clarifai.app.mjs +++ b/components/clarifai/clarifai.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/codefresh/codefresh.app.mjs b/components/codefresh/codefresh.app.mjs index 05f22fed15dc6..b1ab6322ebfaa 100644 --- a/components/codefresh/codefresh.app.mjs +++ b/components/codefresh/codefresh.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/cometapi/cometapi.app.mjs b/components/cometapi/cometapi.app.mjs index d8b1964610914..b4887a2014192 100644 --- a/components/cometapi/cometapi.app.mjs +++ b/components/cometapi/cometapi.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/google_perspective/google_perspective.app.mjs b/components/google_perspective/google_perspective.app.mjs index 2ef9ed80b5ac6..5b343c8dfc547 100644 --- a/components/google_perspective/google_perspective.app.mjs +++ b/components/google_perspective/google_perspective.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/hex/hex.app.mjs b/components/hex/hex.app.mjs index ca851c737d608..0468c4d8025a2 100644 --- a/components/hex/hex.app.mjs +++ b/components/hex/hex.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/n1n/n1n.app.mjs b/components/n1n/n1n.app.mjs index 57b8e6a82bc33..a6ba93bf24c41 100644 --- a/components/n1n/n1n.app.mjs +++ b/components/n1n/n1n.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/piwik_pro/piwik_pro.app.mjs b/components/piwik_pro/piwik_pro.app.mjs index ea2bebcefcc4b..2c6c4a51e0f5b 100644 --- a/components/piwik_pro/piwik_pro.app.mjs +++ b/components/piwik_pro/piwik_pro.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/ups/ups.app.mjs b/components/ups/ups.app.mjs index cb219630767e6..beff489bf6b09 100644 --- a/components/ups/ups.app.mjs +++ b/components/ups/ups.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file diff --git a/components/xola/xola.app.mjs b/components/xola/xola.app.mjs index 7edbadd3767f5..448e77fc7256d 100644 --- a/components/xola/xola.app.mjs +++ b/components/xola/xola.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; +}; \ No newline at end of file From 135030ddaf00fcbf7c4fab4c5bff70fabbdf72e7 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:33:36 -0400 Subject: [PATCH 18/27] update slack v2 component keys from slack- to slack_v2- --- .../actions/add-emoji-reaction/add-emoji-reaction.mjs | 4 ++-- .../slack_v2/actions/approve-workflow/approve-workflow.mjs | 2 +- .../slack_v2/actions/archive-channel/archive-channel.mjs | 2 +- components/slack_v2/actions/create-channel/create-channel.mjs | 2 +- .../slack_v2/actions/create-reminder/create-reminder.mjs | 2 +- components/slack_v2/actions/delete-file/delete-file.mjs | 2 +- components/slack_v2/actions/delete-message/delete-message.mjs | 2 +- components/slack_v2/actions/find-message/find-message.mjs | 2 +- .../actions/find-user-by-email/find-user-by-email.mjs | 2 +- components/slack_v2/actions/get-file/get-file.mjs | 2 +- .../actions/invite-user-to-channel/invite-user-to-channel.mjs | 2 +- components/slack_v2/actions/kick-user/kick-user.mjs | 2 +- components/slack_v2/actions/list-channels/list-channels.mjs | 2 +- components/slack_v2/actions/list-files/list-files.mjs | 2 +- .../actions/list-group-members/list-group-members.mjs | 2 +- .../list-members-in-channel/list-members-in-channel.mjs | 2 +- components/slack_v2/actions/list-replies/list-replies.mjs | 2 +- components/slack_v2/actions/list-users/list-users.mjs | 2 +- .../actions/reply-to-a-message/reply-to-a-message.mjs | 2 +- .../actions/send-block-kit-message/send-block-kit-message.mjs | 2 +- .../actions/send-large-message/send-large-message.mjs | 2 +- .../actions/send-message-advanced/send-message-advanced.mjs | 2 +- .../send-message-to-channel/send-message-to-channel.mjs | 2 +- .../send-message-to-user-or-group.mjs | 2 +- components/slack_v2/actions/send-message/send-message.mjs | 2 +- .../set-channel-description/set-channel-description.mjs | 2 +- .../slack_v2/actions/set-channel-topic/set-channel-topic.mjs | 2 +- components/slack_v2/actions/set-status/set-status.mjs | 2 +- .../actions/update-group-members/update-group-members.mjs | 2 +- components/slack_v2/actions/update-message/update-message.mjs | 2 +- components/slack_v2/actions/update-profile/update-profile.mjs | 2 +- components/slack_v2/actions/upload-file/upload-file.mjs | 2 +- .../sources/new-channel-created/new-channel-created.mjs | 2 +- .../new-interaction-event-received.mjs | 2 +- .../sources/new-keyword-mention/new-keyword-mention.mjs | 2 +- .../new-message-in-channels/new-message-in-channels.mjs | 2 +- .../sources/new-reaction-added/new-reaction-added.mjs | 2 +- .../slack_v2/sources/new-saved-message/new-saved-message.mjs | 2 +- components/slack_v2/sources/new-user-added/new-user-added.mjs | 2 +- .../slack_v2/sources/new-user-mention/new-user-mention.mjs | 2 +- 40 files changed, 41 insertions(+), 41 deletions(-) diff --git a/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs index 1a2e2ed28bd1e..5e8ae1b3467e2 100644 --- a/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs +++ b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -1,7 +1,7 @@ -import slack from "../../slack_v2.app.mjs"; +import slack from "../../slack.app.mjs"; export default { - key: "slack-add-emoji-reaction", + 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", diff --git a/components/slack_v2/actions/approve-workflow/approve-workflow.mjs b/components/slack_v2/actions/approve-workflow/approve-workflow.mjs index 79f4098cc307a..b2da0d7e82e38 100644 --- a/components/slack_v2/actions/approve-workflow/approve-workflow.mjs +++ b/components/slack_v2/actions/approve-workflow/approve-workflow.mjs @@ -2,7 +2,7 @@ import slack from "../../slack_v2.app.mjs"; import constants from "../../common/constants.mjs"; export default { - key: "slack-approve-workflow", + 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", diff --git a/components/slack_v2/actions/archive-channel/archive-channel.mjs b/components/slack_v2/actions/archive-channel/archive-channel.mjs index 4488dc84de8e5..12b8cc420d306 100644 --- a/components/slack_v2/actions/archive-channel/archive-channel.mjs +++ b/components/slack_v2/actions/archive-channel/archive-channel.mjs @@ -2,7 +2,7 @@ import slack from "../../slack_v2.app.mjs"; import constants from "../../common/constants.mjs"; export default { - key: "slack-archive-channel", + 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", diff --git a/components/slack_v2/actions/create-channel/create-channel.mjs b/components/slack_v2/actions/create-channel/create-channel.mjs index 22fb4896c577f..fc349b3fe6144 100644 --- a/components/slack_v2/actions/create-channel/create-channel.mjs +++ b/components/slack_v2/actions/create-channel/create-channel.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-create-channel", + key: "slack_v2-create-channel", name: "Create a Channel", description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)", version: "0.0.26", diff --git a/components/slack_v2/actions/create-reminder/create-reminder.mjs b/components/slack_v2/actions/create-reminder/create-reminder.mjs index 70f029f39c300..81eb8d2e95929 100644 --- a/components/slack_v2/actions/create-reminder/create-reminder.mjs +++ b/components/slack_v2/actions/create-reminder/create-reminder.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-create-reminder", + key: "slack_v2-create-reminder", name: "Create Reminder", description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)", version: "0.0.26", diff --git a/components/slack_v2/actions/delete-file/delete-file.mjs b/components/slack_v2/actions/delete-file/delete-file.mjs index daa1cd53c54c4..c3246a049ad11 100644 --- a/components/slack_v2/actions/delete-file/delete-file.mjs +++ b/components/slack_v2/actions/delete-file/delete-file.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-delete-file", + key: "slack_v2-delete-file", name: "Delete File", description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)", version: "0.0.25", diff --git a/components/slack_v2/actions/delete-message/delete-message.mjs b/components/slack_v2/actions/delete-message/delete-message.mjs index d4abaebb2404b..f444d8c22cd8c 100644 --- a/components/slack_v2/actions/delete-message/delete-message.mjs +++ b/components/slack_v2/actions/delete-message/delete-message.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-delete-message", + key: "slack_v2-delete-message", name: "Delete Message", description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)", version: "0.1.0", diff --git a/components/slack_v2/actions/find-message/find-message.mjs b/components/slack_v2/actions/find-message/find-message.mjs index 81ac2b7eff7d4..a3a08b8a50d69 100644 --- a/components/slack_v2/actions/find-message/find-message.mjs +++ b/components/slack_v2/actions/find-message/find-message.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-find-message", + key: "slack_v2-find-message", name: "Find Message", description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)", version: "0.1.0", diff --git a/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs b/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs index 42741b153a845..9205cef90fcfe 100644 --- a/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs +++ b/components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-find-user-by-email", + key: "slack_v2-find-user-by-email", name: "Find User by Email", description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)", version: "0.0.25", diff --git a/components/slack_v2/actions/get-file/get-file.mjs b/components/slack_v2/actions/get-file/get-file.mjs index 27de0cfa81287..89761012b63c2 100644 --- a/components/slack_v2/actions/get-file/get-file.mjs +++ b/components/slack_v2/actions/get-file/get-file.mjs @@ -2,7 +2,7 @@ import constants from "../../common/constants.mjs"; import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-get-file", + key: "slack_v2-get-file", name: "Get File", description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)", version: "0.1.0", diff --git a/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs index 750eebe996b4f..dccb7627a001c 100644 --- a/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs +++ b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-invite-user-to-channel", + key: "slack_v2-invite-user-to-channel", name: "Invite User to Channel", description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)", version: "0.0.25", diff --git a/components/slack_v2/actions/kick-user/kick-user.mjs b/components/slack_v2/actions/kick-user/kick-user.mjs index 48af52c4607d3..90a7d8bb997d0 100644 --- a/components/slack_v2/actions/kick-user/kick-user.mjs +++ b/components/slack_v2/actions/kick-user/kick-user.mjs @@ -2,7 +2,7 @@ import slack from "../../slack_v2.app.mjs"; import constants from "../../common/constants.mjs"; export default { - key: "slack-kick-user", + key: "slack_v2-kick-user", name: "Kick User", description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)", version: "0.0.25", diff --git a/components/slack_v2/actions/list-channels/list-channels.mjs b/components/slack_v2/actions/list-channels/list-channels.mjs index e00ca0f16e459..d29b4e7bca2c1 100644 --- a/components/slack_v2/actions/list-channels/list-channels.mjs +++ b/components/slack_v2/actions/list-channels/list-channels.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-channels", + key: "slack_v2-list-channels", name: "List Channels", description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)", version: "0.0.25", diff --git a/components/slack_v2/actions/list-files/list-files.mjs b/components/slack_v2/actions/list-files/list-files.mjs index 788fb6be3a329..523a5d457ce4f 100644 --- a/components/slack_v2/actions/list-files/list-files.mjs +++ b/components/slack_v2/actions/list-files/list-files.mjs @@ -2,7 +2,7 @@ import constants from "../../common/constants.mjs"; import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-files", + key: "slack_v2-list-files", name: "List Files", description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)", version: "0.1.0", diff --git a/components/slack_v2/actions/list-group-members/list-group-members.mjs b/components/slack_v2/actions/list-group-members/list-group-members.mjs index f0942fa2e4f66..401589afa9de1 100644 --- a/components/slack_v2/actions/list-group-members/list-group-members.mjs +++ b/components/slack_v2/actions/list-group-members/list-group-members.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-group-members", + key: "slack_v2-list-group-members", name: "List Group Members", description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)", version: "0.0.10", diff --git a/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs b/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs index aab1cfb64f69c..f95d18a91a790 100644 --- a/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs +++ b/components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-members-in-channel", + key: "slack_v2-list-members-in-channel", name: "List Members in Channel", description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)", version: "0.0.25", diff --git a/components/slack_v2/actions/list-replies/list-replies.mjs b/components/slack_v2/actions/list-replies/list-replies.mjs index 407c09d4023ad..62b462864aa41 100644 --- a/components/slack_v2/actions/list-replies/list-replies.mjs +++ b/components/slack_v2/actions/list-replies/list-replies.mjs @@ -2,7 +2,7 @@ import constants from "../../common/constants.mjs"; import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-replies", + key: "slack_v2-list-replies", name: "List Replies", description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)", version: "0.0.25", diff --git a/components/slack_v2/actions/list-users/list-users.mjs b/components/slack_v2/actions/list-users/list-users.mjs index d84671fb82f22..dbffb89fff388 100644 --- a/components/slack_v2/actions/list-users/list-users.mjs +++ b/components/slack_v2/actions/list-users/list-users.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-list-users", + key: "slack_v2-list-users", name: "List Users", description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)", version: "0.0.25", diff --git a/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs b/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs index c7d94a22d7b7f..63899c97bfbcc 100644 --- a/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs +++ b/components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs @@ -3,7 +3,7 @@ import common from "../common/send-message.mjs"; export default { ...common, - key: "slack-reply-to-a-message", + key: "slack_v2-reply-to-a-message", name: "Reply to a Message Thread", description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", version: "0.2.0", diff --git a/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs b/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs index 7fa53d5fc6559..85ba9312179ae 100644 --- a/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs +++ b/components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs @@ -4,7 +4,7 @@ import common from "../common/send-message.mjs"; export default { ...common, ...buildBlocks, - key: "slack-send-block-kit-message", + key: "slack_v2-send-block-kit-message", name: "Build and Send a Block Kit Message", description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).", version: "0.5.0", diff --git a/components/slack_v2/actions/send-large-message/send-large-message.mjs b/components/slack_v2/actions/send-large-message/send-large-message.mjs index 474230475d486..a5865e7520cb5 100644 --- a/components/slack_v2/actions/send-large-message/send-large-message.mjs +++ b/components/slack_v2/actions/send-large-message/send-large-message.mjs @@ -2,7 +2,7 @@ import common from "../common/send-message.mjs"; export default { ...common, - key: "slack-send-large-message", + key: "slack_v2-send-large-message", name: "Send a Large Message (3000+ characters)", description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", version: "0.1.0", diff --git a/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs index 6b8df73332ac4..94abf6cb4e9df 100644 --- a/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs +++ b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs @@ -4,7 +4,7 @@ import buildBlocks from "../common/build-blocks.mjs"; export default { ...common, ...buildBlocks, - key: "slack-send-message-advanced", + key: "slack_v2-send-message-advanced", name: "Send Message (Advanced)", description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", version: "0.1.0", diff --git a/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs b/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs index ac00b08c5e5b8..b0d2fcad7dec0 100644 --- a/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs +++ b/components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs @@ -3,7 +3,7 @@ import constants from "../../common/constants.mjs"; export default { ...common, - key: "slack-send-message-to-channel", + key: "slack_v2-send-message-to-channel", name: "Send Message to Channel", description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", version: "0.1.0", diff --git a/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs index 60834909dec8a..692f881a973f2 100644 --- a/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +++ b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -4,7 +4,7 @@ import { ConfigurationError } from "@pipedream/platform"; export default { ...common, - key: "slack-send-message-to-user-or-group", + key: "slack_v2-send-message-to-user-or-group", name: "Send Message to User or Group", description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)", version: "0.1.0", diff --git a/components/slack_v2/actions/send-message/send-message.mjs b/components/slack_v2/actions/send-message/send-message.mjs index e8a67df460932..651ac4d1da20d 100644 --- a/components/slack_v2/actions/send-message/send-message.mjs +++ b/components/slack_v2/actions/send-message/send-message.mjs @@ -3,7 +3,7 @@ import constants from "../../common/constants.mjs"; export default { ...common, - key: "slack-send-message", + key: "slack_v2-send-message", name: "Send Message", description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)", version: "0.1.0", diff --git a/components/slack_v2/actions/set-channel-description/set-channel-description.mjs b/components/slack_v2/actions/set-channel-description/set-channel-description.mjs index 2e6214350c7af..d53c01374b059 100644 --- a/components/slack_v2/actions/set-channel-description/set-channel-description.mjs +++ b/components/slack_v2/actions/set-channel-description/set-channel-description.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-set-channel-description", + key: "slack_v2-set-channel-description", name: "Set Channel Description", description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)", version: "0.0.10", diff --git a/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs b/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs index 020f455c4870b..c6af489c1ee68 100644 --- a/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs +++ b/components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-set-channel-topic", + key: "slack_v2-set-channel-topic", name: "Set Channel Topic", description: "Set the topic on a selected channel. [See the documentation](https://api.slack.com/methods/conversations.setTopic)", version: "0.0.25", diff --git a/components/slack_v2/actions/set-status/set-status.mjs b/components/slack_v2/actions/set-status/set-status.mjs index 69df31b962c6e..2077fde85f541 100644 --- a/components/slack_v2/actions/set-status/set-status.mjs +++ b/components/slack_v2/actions/set-status/set-status.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-set-status", + key: "slack_v2-set-status", name: "Set Status", description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)", version: "0.0.10", diff --git a/components/slack_v2/actions/update-group-members/update-group-members.mjs b/components/slack_v2/actions/update-group-members/update-group-members.mjs index 1414f0ba0592a..02ec92971307d 100644 --- a/components/slack_v2/actions/update-group-members/update-group-members.mjs +++ b/components/slack_v2/actions/update-group-members/update-group-members.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-update-group-members", + key: "slack_v2-update-group-members", name: "Update Groups Members", description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", version: "0.0.10", diff --git a/components/slack_v2/actions/update-message/update-message.mjs b/components/slack_v2/actions/update-message/update-message.mjs index d15d456eed7aa..f56dff69ba283 100644 --- a/components/slack_v2/actions/update-message/update-message.mjs +++ b/components/slack_v2/actions/update-message/update-message.mjs @@ -1,7 +1,7 @@ import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-update-message", + key: "slack_v2-update-message", name: "Update Message", description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)", version: "0.2.0", diff --git a/components/slack_v2/actions/update-profile/update-profile.mjs b/components/slack_v2/actions/update-profile/update-profile.mjs index ae11c92c959d7..77b1c2fa8eff9 100644 --- a/components/slack_v2/actions/update-profile/update-profile.mjs +++ b/components/slack_v2/actions/update-profile/update-profile.mjs @@ -2,7 +2,7 @@ import slack from "../../slack_v2.app.mjs"; import { ConfigurationError } from "@pipedream/platform"; export default { - key: "slack-update-profile", + key: "slack_v2-update-profile", name: "Update Profile", description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", version: "0.0.25", diff --git a/components/slack_v2/actions/upload-file/upload-file.mjs b/components/slack_v2/actions/upload-file/upload-file.mjs index d5f752507b322..9516d5b8d35bc 100644 --- a/components/slack_v2/actions/upload-file/upload-file.mjs +++ b/components/slack_v2/actions/upload-file/upload-file.mjs @@ -5,7 +5,7 @@ import FormData from "form-data"; import slack from "../../slack_v2.app.mjs"; export default { - key: "slack-upload-file", + key: "slack_v2-upload-file", name: "Upload File", description: "Upload a file. [See the documentation](https://api.slack.com/messaging/files#uploading_files)", version: "0.1.3", diff --git a/components/slack_v2/sources/new-channel-created/new-channel-created.mjs b/components/slack_v2/sources/new-channel-created/new-channel-created.mjs index 9d1df1076dc05..7d93b0733901e 100644 --- a/components/slack_v2/sources/new-channel-created/new-channel-created.mjs +++ b/components/slack_v2/sources/new-channel-created/new-channel-created.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { ...common, - key: "slack-new-channel-created", + key: "slack_v2-new-channel-created", name: "New Channel Created (Instant)", version: "0.0.11", description: "Emit new event when a new channel is created.", diff --git a/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs b/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs index 4ec49f2be9653..2c09fd109e2cc 100644 --- a/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs +++ b/components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs @@ -4,7 +4,7 @@ import sampleEmit from "./test-event.mjs"; export default { name: "New Interaction Events (Instant)", version: "0.0.21", - key: "slack-new-interaction-event-received", + key: "slack_v2-new-interaction-event-received", description: "Emit new events on new Slack [interactivity events](https://api.slack.com/interactivity) sourced from [Block Kit interactive elements](https://api.slack.com/interactivity/components), [Slash commands](https://api.slack.com/interactivity/slash-commands), or [Shortcuts](https://api.slack.com/interactivity/shortcuts).", type: "source", props: { diff --git a/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs b/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs index c6323e562670e..7d6a0320240ec 100644 --- a/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs +++ b/components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs @@ -5,7 +5,7 @@ import sharedConstants from "../../common/constants.mjs"; export default { ...common, - key: "slack-new-keyword-mention", + key: "slack_v2-new-keyword-mention", name: "New Keyword Mention (Instant)", version: "0.1.0", description: "Emit new event when a specific keyword is mentioned in a channel", diff --git a/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs b/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs index 72c9ef83bb751..93a67bbfd84dc 100644 --- a/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs +++ b/components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs @@ -5,7 +5,7 @@ import sharedConstants from "../../common/constants.mjs"; export default { ...common, - key: "slack-new-message-in-channels", + key: "slack_v2-new-message-in-channels", name: "New Message In Channels (Instant)", version: "1.1.0", description: "Emit new event when a new message is posted to one or more channels", diff --git a/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs index aaf4edf973db0..c804c3075eb4f 100644 --- a/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { ...common, - key: "slack-new-reaction-added", + key: "slack_v2-new-reaction-added", name: "New Reaction Added (Instant)", version: "1.2.0", description: "Emit new event when a member has added an emoji reaction to a message", diff --git a/components/slack_v2/sources/new-saved-message/new-saved-message.mjs b/components/slack_v2/sources/new-saved-message/new-saved-message.mjs index 2d7a94e5c5b95..c8377e44a4925 100644 --- a/components/slack_v2/sources/new-saved-message/new-saved-message.mjs +++ b/components/slack_v2/sources/new-saved-message/new-saved-message.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { ...common, - key: "slack-new-saved-message", + key: "slack_v2-new-saved-message", name: "New Saved Message (Instant)", version: "0.0.7", description: "Emit new event when a message is saved. Note: The endpoint is marked as deprecated, and Slack might shut this off at some point down the line.", diff --git a/components/slack_v2/sources/new-user-added/new-user-added.mjs b/components/slack_v2/sources/new-user-added/new-user-added.mjs index 03db742683377..2601ee494b5a7 100644 --- a/components/slack_v2/sources/new-user-added/new-user-added.mjs +++ b/components/slack_v2/sources/new-user-added/new-user-added.mjs @@ -3,7 +3,7 @@ import sampleEmit from "./test-event.mjs"; export default { ...common, - key: "slack-new-user-added", + key: "slack_v2-new-user-added", name: "New User Added (Instant)", version: "0.0.5", description: "Emit new event when a new member joins a workspace.", diff --git a/components/slack_v2/sources/new-user-mention/new-user-mention.mjs b/components/slack_v2/sources/new-user-mention/new-user-mention.mjs index 15efc422c870d..371edcfd20ac1 100644 --- a/components/slack_v2/sources/new-user-mention/new-user-mention.mjs +++ b/components/slack_v2/sources/new-user-mention/new-user-mention.mjs @@ -5,7 +5,7 @@ import sharedConstants from "../../common/constants.mjs"; export default { ...common, - key: "slack-new-user-mention", + key: "slack_v2-new-user-mention", name: "New User Mention (Instant)", version: "0.1.0", description: "Emit new event when a username or specific keyword is mentioned in a channel", From cfeb6edd8b6a8b753c2955ffe6ab1e81e9ed8864 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:35:08 -0400 Subject: [PATCH 19/27] add Get Current User action Copies the action with the same name added to the Slack app in the master branch. --- .../get-current-user/get-current-user.mjs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 components/slack_v2/actions/get-current-user/get-current-user.mjs diff --git a/components/slack_v2/actions/get-current-user/get-current-user.mjs b/components/slack_v2/actions/get-current-user/get-current-user.mjs new file mode 100644 index 0000000000000..5b899d908da13 --- /dev/null +++ b/components/slack_v2/actions/get-current-user/get-current-user.mjs @@ -0,0 +1,68 @@ +import slack from "../../slack.app.mjs"; + +export default { + key: "slack_v2-get-current-user", + name: "Get Current User", + description: "Retrieve comprehensive context about the authenticated Slack member, combining `auth.test`, `users.info`, `users.profile.get`, and `team.info` payloads. Returns the user’s profile (name variants, email, locale, timezone, status, admin flags), raw auth test data, and workspace metadata (domain, enterprise info, icons). Ideal when you need to confirm which user token is active, tailor messages to their locale/timezone, or ground an LLM in the member’s role and workspace before executing other Slack actions. [See Slack API docs](https://api.slack.com/methods/auth.test).", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + slack, + }, + async run({ $ }) { + const authContext = await this.slack.authTest(); + + const userId = authContext.user_id || authContext.user; + if (!userId) { + throw new Error(`Unable to determine user ID from auth context. Received: ${JSON.stringify(authContext)}`); + } + + let userInfo; + try { + userInfo = await this.slack.usersInfo({ + user: userId, + include_locale: true, + }); + } catch (error) { + // Gracefully degrade if scope not available + } + + let userProfile; + try { + userProfile = await this.slack.getUserProfile({ + user: userId, + }); + } catch (error) { + // Gracefully degrade if scope not available + } + + let teamInfo; + try { + teamInfo = await this.slack.getTeamInfo(); + } catch (error) { + // Gracefully degrade if scope not available + } + + const user = userInfo?.user; + const profile = userProfile?.profile ?? user?.profile; + const summaryName = + profile?.real_name_normalized + || profile?.display_name_normalized + || authContext.user + || userId; + + $.export("$summary", `Retrieved Slack user ${summaryName}`); + + return { + authContext, + user, + profile, + team: teamInfo?.team, + }; + }, +}; From 97ed7f6b356cdae7a0dd18af8978a0b694392cde Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 18:09:22 -0400 Subject: [PATCH 20/27] revert fallback and message normalization in Find Message action --- .../actions/find-message/find-message.mjs | 134 +----------------- 1 file changed, 2 insertions(+), 132 deletions(-) diff --git a/components/slack_v2/actions/find-message/find-message.mjs b/components/slack_v2/actions/find-message/find-message.mjs index a3a08b8a50d69..92c2e28c1707f 100644 --- a/components/slack_v2/actions/find-message/find-message.mjs +++ b/components/slack_v2/actions/find-message/find-message.mjs @@ -48,65 +48,6 @@ export default { }, }, methods: { - normalizeAssistantMatch(match) { - if (!match || typeof match !== "object") { - return match; - } - const { - author_user_id: authorUserId, - team_id: teamId, - channel_id: channelId, - message_ts: messageTs, - content, - permalink, - is_author_bot: isAuthorBot, - message, - channel, - ...rest - } = match; - const baseMessage = typeof message === "object" - ? message - : {}; - const channelInfo = channel && typeof channel === "object" - ? { - ...channel, - id: channel.id || channelId, - } - : channelId - ? { - id: channelId, - } - : undefined; - const normalized = { - type: "message", - user: authorUserId, - team: teamId, - ts: messageTs, - text: content, - permalink, - channel: channelInfo, - ...baseMessage, - ...rest, - }; - if (isAuthorBot !== undefined && normalized.is_author_bot === undefined) { - normalized.is_author_bot = isAuthorBot; - } - if (normalized.text == null) { - normalized.text = baseMessage.text || content; - } - if (normalized.ts == null) { - normalized.ts = baseMessage.ts || messageTs; - } - if (!normalized.channel && baseMessage.channel) { - normalized.channel = baseMessage.channel; - } else if (normalized.channel && baseMessage.channel && typeof baseMessage.channel === "object") { - normalized.channel = { - ...normalized.channel, - ...baseMessage.channel, - }; - } - return normalized; - }, async searchWithAssistant(baseParams, maxResults) { const matches = []; let cursor; @@ -117,73 +58,12 @@ export default { channel_types: "public_channel,private_channel", cursor, }); - const messages = (response.results?.messages || []) - .map((item) => this.normalizeAssistantMatch(item)); - matches.push(...messages); + matches.push(...response.results?.messages || []); cursor = response.response_metadata?.next_cursor; } while (cursor && matches.length < maxResults); return matches.slice(0, maxResults); }, - async searchWithSearchMessages(baseParams, maxResults) { - const matches = []; - let page = 1; - const count = Math.min(Math.max(maxResults, 1), 100); - - while (matches.length < maxResults) { - const response = await this.slack.searchMessages({ - ...baseParams, - count, - page, - }); - const pageMatches = response.messages?.matches || []; - matches.push(...pageMatches); - - if (matches.length >= maxResults) { - break; - } - - const pagination = response.messages?.pagination; - const paging = response.messages?.paging; - const hasMore = pagination - ? pagination.page < pagination.page_count - : paging - ? paging.page < paging.pages - : false; - - if (!hasMore) { - break; - } - - page += 1; - } - - return matches.slice(0, maxResults); - }, - shouldFallbackToSearchMessages(error) { - const errorCode = typeof error === "string" - ? error - : error?.data?.error || error?.message; - - if (!errorCode?.includes("missing_scope")) { - return false; - } - - const providedSources = [ - error?.data?.provided, - error?.provided, - error?.original?.data?.provided, - ].filter(Boolean); - - const providedScopes = providedSources - .flatMap((value) => Array.isArray(value) - ? value - : String(value).split(",")) - .map((scope) => scope.trim()) - .filter(Boolean); - - return providedScopes.includes("search:read"); - }, }, async run({ $ }) { const maxResults = Math.max(this.maxResults ?? 20, 1); @@ -192,17 +72,7 @@ export default { sort: this.sort, sort_dir: this.sortDirection, }; - let matches; - - try { - matches = await this.searchWithAssistant(baseParams, maxResults); - } catch (error) { - if (this.shouldFallbackToSearchMessages(error)) { - matches = await this.searchWithSearchMessages(baseParams, maxResults); - } else { - throw error; - } - } + const matches = await this.searchWithAssistant(baseParams, maxResults); $.export("$summary", `Found ${matches.length} matching message${matches.length === 1 ? "" From f417859114655b208c34499a132c25181e93def1 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:22:13 -0400 Subject: [PATCH 21/27] update pnpm-lock.yaml --- pnpm-lock.yaml | 207 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 33 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdd0c9e7898fc..caec7974b5af5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,7 +104,7 @@ importers: version: 4.0.0 ts-jest: specifier: ^29.1.1 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3) tsc-esm-fix: specifier: ^2.18.0 version: 2.20.27 @@ -629,8 +629,7 @@ importers: specifier: ^3.1.0 version: 3.1.0 - components/airweave: - specifiers: {} + components/airweave: {} components/aitable_ai: dependencies: @@ -2679,8 +2678,7 @@ importers: specifier: ^3.0.1 version: 3.0.1(web-streams-polyfill@3.3.3) - components/clarifai: - specifiers: {} + components/clarifai: {} components/clarify: {} @@ -2998,8 +2996,7 @@ importers: components/codeberg: {} - components/codefresh: - specifiers: {} + components/codefresh: {} components/codegpt: {} @@ -3112,8 +3109,7 @@ importers: specifier: ^0.0.6 version: 0.0.6 - components/cometapi: - specifiers: {} + components/cometapi: {} components/cometly: dependencies: @@ -6588,8 +6584,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/hex: - specifiers: {} + components/hex: {} components/heygen: dependencies: @@ -10912,8 +10907,7 @@ importers: specifier: ^1.3.0 version: 1.6.6 - components/piwik_pro: - specifiers: {} + components/piwik_pro: {} components/pixelbin: dependencies: @@ -13396,6 +13390,21 @@ importers: components/slack_demo_app_1: {} + components/slack_v2: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 + '@slack/web-api': + specifier: ^7.9.0 + version: 7.9.1 + async-retry: + specifier: ^1.3.3 + version: 1.3.3 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + components/slicktext: dependencies: '@pipedream/platform': @@ -16961,7 +16970,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.30)(typescript@5.7.2)))(typescript@5.7.2) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.30)(typescript@5.7.2)))(typescript@5.7.2) tsup: specifier: ^8.3.6 version: 8.3.6(@microsoft/api-extractor@7.47.12(@types/node@20.17.30))(jiti@2.4.2)(postcss@8.5.6)(tsx@4.19.4)(typescript@5.7.2)(yaml@2.8.0) @@ -31368,22 +31377,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@10.0.0: resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==} @@ -35785,7 +35794,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -36056,21 +36065,45 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -36081,16 +36114,34 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -36101,41 +36152,89 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@8.0.0-alpha.13)': + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/helper-plugin-utils': 7.25.9 + optional: true + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -37198,7 +37297,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -39704,6 +39803,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: @@ -42264,7 +42365,7 @@ snapshots: '@typescript-eslint/types': 8.15.0 '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.15.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) eslint: 8.57.1 optionalDependencies: typescript: 5.6.3 @@ -43158,6 +43259,20 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@8.0.0-alpha.13): + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@8.0.0-alpha.13) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.25.9 @@ -43224,12 +43339,39 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + babel-preset-current-node-syntax@1.1.0(@babel/core@8.0.0-alpha.13): + dependencies: + '@babel/core': 8.0.0-alpha.13 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@8.0.0-alpha.13) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@8.0.0-alpha.13) + optional: true + babel-preset-jest@29.6.3(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-jest@29.6.3(@babel/core@8.0.0-alpha.13): + dependencies: + '@babel/core': 8.0.0-alpha.13 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@8.0.0-alpha.13) + optional: true + backoff@2.5.0: dependencies: precond: 0.2.3 @@ -45438,7 +45580,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -47299,7 +47441,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) transitivePeerDependencies: - supports-color @@ -49880,7 +50022,7 @@ snapshots: dependencies: '@tediousjs/connection-string': 0.5.0 commander: 11.1.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) rfdc: 1.4.1 tarn: 3.0.2 tedious: 16.7.1 @@ -51629,7 +51771,7 @@ snapshots: ajv: 8.17.1 chalk: 5.3.0 ci-info: 4.1.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) deepmerge: 4.3.1 escalade: 3.2.0 fast-glob: 3.3.2 @@ -53765,7 +53907,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.30)(typescript@5.7.2)))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.30)(typescript@5.7.2)))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -53783,9 +53925,8 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.0) - esbuild: 0.24.2 - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -53799,10 +53940,10 @@ snapshots: typescript: 5.6.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 8.0.0-alpha.13 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) ts-node@10.9.2(@types/node@20.17.30)(typescript@5.7.2): dependencies: @@ -53974,7 +54115,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) esbuild: 0.24.2 joycon: 3.1.1 picocolors: 1.1.1 @@ -54002,7 +54143,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.0 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) esbuild: 0.24.2 joycon: 3.1.1 picocolors: 1.1.1 @@ -54626,7 +54767,7 @@ snapshots: '@volar/typescript': 2.4.10 '@vue/language-core': 2.1.6(typescript@5.9.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@5.5.0) + debug: 4.3.7(supports-color@9.4.0) kolorist: 1.8.0 local-pkg: 0.5.1 magic-string: 0.30.13 From 68c893089642a8e6cf7363b146a85d01a018ed66 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:31:41 -0400 Subject: [PATCH 22/27] apply suggestion to improve performance of realNameLookup method --- components/slack_v2/slack_v2.app.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/slack_v2/slack_v2.app.mjs b/components/slack_v2/slack_v2.app.mjs index 570a503e828b5..6e678862bf94a 100644 --- a/components/slack_v2/slack_v2.app.mjs +++ b/components/slack_v2/slack_v2.app.mjs @@ -650,8 +650,11 @@ export default { return userNames; }, async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { + const idSet = new Set(ids); + const usernameSet = new Set(usernames); let cursor; const realNames = {}; + const targetCount = ids.length + usernames.length; do { const { members: users, @@ -664,16 +667,16 @@ export default { }); for (const user of users) { - if (ids.includes(user.id)) { + if (idSet.has(user.id)) { realNames[user.id] = user.profile.real_name; } - if (usernames.includes(user.name)) { + if (usernameSet.has(user.name)) { realNames[user.name] = user.profile.real_name; } } cursor = nextCursor; - } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); + } while (cursor && Object.keys(realNames).length < targetCount); return realNames; }, async maybeAddAppToChannels(channelIds = []) { From e9699aa9c68f2b68ba2d7e40230a7e3836569823 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:33:57 -0400 Subject: [PATCH 23/27] fix app file imports in add-emoji-reaction.mjs and get-current-user.mjs --- .../slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs | 2 +- .../slack_v2/actions/get-current-user/get-current-user.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs index 5e8ae1b3467e2..e5c51a82e4a80 100644 --- a/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs +++ b/components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs @@ -1,4 +1,4 @@ -import slack from "../../slack.app.mjs"; +import slack from "../../slack_v2.app.mjs"; export default { key: "slack_v2-add-emoji-reaction", diff --git a/components/slack_v2/actions/get-current-user/get-current-user.mjs b/components/slack_v2/actions/get-current-user/get-current-user.mjs index 5b899d908da13..94f1829558778 100644 --- a/components/slack_v2/actions/get-current-user/get-current-user.mjs +++ b/components/slack_v2/actions/get-current-user/get-current-user.mjs @@ -1,4 +1,4 @@ -import slack from "../../slack.app.mjs"; +import slack from "../../slack_v2.app.mjs"; export default { key: "slack_v2-get-current-user", From 8d72c9d1b26f2bee57baf250f55c23f4267ba01b Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:17:51 -0400 Subject: [PATCH 24/27] update components to address review comments --- .../actions/update-profile/update-profile.mjs | 2 ++ .../slack_v2/actions/common/build-blocks.mjs | 4 ++-- .../actions/find-message/find-message.mjs | 2 +- .../invite-user-to-channel.mjs | 2 +- .../slack_v2/actions/kick-user/kick-user.mjs | 2 +- .../actions/list-users/list-users.mjs | 2 +- .../send-message-advanced.mjs | 2 +- .../update-group-members.mjs | 2 +- .../actions/upload-file/upload-file.mjs | 1 - components/slack_v2/slack_v2.app.mjs | 2 ++ .../new-interaction-event-received/README.md | 4 ++-- .../new-reaction-added/new-reaction-added.mjs | 24 ++++++++++++------- 12 files changed, 29 insertions(+), 20 deletions(-) diff --git a/components/slack/actions/update-profile/update-profile.mjs b/components/slack/actions/update-profile/update-profile.mjs index e3c871d7d0246..7a5d0bd744fda 100644 --- a/components/slack/actions/update-profile/update-profile.mjs +++ b/components/slack/actions/update-profile/update-profile.mjs @@ -72,6 +72,7 @@ export default { && !this.phone && !this.pronouns && !this.title + && !this.email ) { throw new ConfigurationError("Please provide at least one value to update"); } @@ -83,6 +84,7 @@ export default { phone: this.phone, pronouns: this.pronouns, title: this.title, + email: this.email, }, user: this.user, }); diff --git a/components/slack_v2/actions/common/build-blocks.mjs b/components/slack_v2/actions/common/build-blocks.mjs index fdbc9a70e4b17..7e0854dfc7cb4 100644 --- a/components/slack_v2/actions/common/build-blocks.mjs +++ b/components/slack_v2/actions/common/build-blocks.mjs @@ -94,7 +94,7 @@ export default { if (!this.passArrayOrConfigure) { return props; } - if (this.passArrayOrConfigure == "array") { + if (this.passArrayOrConfigure === "array") { props.blocks = { type: common.props.slack.propDefinitions.blocks.type, label: common.props.slack.propDefinitions.blocks.label, @@ -159,7 +159,7 @@ export default { }, async run() { let blocks = []; - if (this.passArrayOrConfigure == "array") { + if (this.passArrayOrConfigure === "array") { blocks = this.blocks; } else { for (let i = 1; i <= 5; i++) { diff --git a/components/slack_v2/actions/find-message/find-message.mjs b/components/slack_v2/actions/find-message/find-message.mjs index 92c2e28c1707f..a9f29a0323ad0 100644 --- a/components/slack_v2/actions/find-message/find-message.mjs +++ b/components/slack_v2/actions/find-message/find-message.mjs @@ -39,7 +39,7 @@ export default { sortDirection: { type: "string", label: "Sort Direction", - description: "Sort ascending (asc) or descending (desc)`", + description: "Sort ascending (asc) or descending (desc)", options: [ "desc", "asc", diff --git a/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs index dccb7627a001c..846a1da450bc1 100644 --- a/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs +++ b/components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs @@ -35,7 +35,7 @@ export default { $.export("$summary", `Successfully invited user ${this.user} to channel with ID ${this.conversation}`); return response; } catch (error) { - if (error.includes("already_in_channel")) { + if (`${error}`.includes("already_in_channel")) { $.export("$summary", `The user ${this.user} is already in the channel`); return; } diff --git a/components/slack_v2/actions/kick-user/kick-user.mjs b/components/slack_v2/actions/kick-user/kick-user.mjs index 90a7d8bb997d0..a7682e97d56ff 100644 --- a/components/slack_v2/actions/kick-user/kick-user.mjs +++ b/components/slack_v2/actions/kick-user/kick-user.mjs @@ -46,7 +46,7 @@ export default { $.export("$summary", `Successfully kicked user ${this.user} from channel with ID ${this.conversation}`); return response; } catch (error) { - if (error.includes("not_in_channel")) { + if (`${error}`.includes("not_in_channel")) { $.export("$summary", `The user ${this.user} is not in the channel`); return; } diff --git a/components/slack_v2/actions/list-users/list-users.mjs b/components/slack_v2/actions/list-users/list-users.mjs index dbffb89fff388..7b39f4a87fbd3 100644 --- a/components/slack_v2/actions/list-users/list-users.mjs +++ b/components/slack_v2/actions/list-users/list-users.mjs @@ -48,7 +48,7 @@ export default { users.push(...members); params.cursor = nextCursor; page++; - } while (params.cursor && page < this.numPages); + } while (params.cursor && page < (this.numPages ?? 1)); $.export("$summary", `Successfully retrieved ${users.length} user${users.length === 1 ? "" diff --git a/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs index 94abf6cb4e9df..14e91770408bc 100644 --- a/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs +++ b/components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs @@ -6,7 +6,7 @@ export default { ...buildBlocks, key: "slack_v2-send-message-advanced", name: "Send Message (Advanced)", - description: "Customize advanced setttings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", + description: "Customize advanced settings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", version: "0.1.0", annotations: { destructiveHint: false, diff --git a/components/slack_v2/actions/update-group-members/update-group-members.mjs b/components/slack_v2/actions/update-group-members/update-group-members.mjs index 02ec92971307d..f5bbd4b5fdcf8 100644 --- a/components/slack_v2/actions/update-group-members/update-group-members.mjs +++ b/components/slack_v2/actions/update-group-members/update-group-members.mjs @@ -2,7 +2,7 @@ import slack from "../../slack_v2.app.mjs"; export default { key: "slack_v2-update-group-members", - name: "Update Groups Members", + name: "Update Group Members", description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)", version: "0.0.10", annotations: { diff --git a/components/slack_v2/actions/upload-file/upload-file.mjs b/components/slack_v2/actions/upload-file/upload-file.mjs index 9516d5b8d35bc..a7e76a601cbe4 100644 --- a/components/slack_v2/actions/upload-file/upload-file.mjs +++ b/components/slack_v2/actions/upload-file/upload-file.mjs @@ -80,7 +80,6 @@ export default { method: "POST", headers: { ...formData.getHeaders(), - Authorization: `Bearer ${this.slack.getToken()}`, }, }); diff --git a/components/slack_v2/slack_v2.app.mjs b/components/slack_v2/slack_v2.app.mjs index 6e678862bf94a..ca70b1b2543f1 100644 --- a/components/slack_v2/slack_v2.app.mjs +++ b/components/slack_v2/slack_v2.app.mjs @@ -897,6 +897,8 @@ export default { }, assistantSearch(args = {}) { args.count ||= constants.LIMIT; + // Uses apiCall directly since assistant.search.context is not exposed as + // a method on WebClient return this.sdk().apiCall("assistant.search.context", { ...args, }); diff --git a/components/slack_v2/sources/new-interaction-event-received/README.md b/components/slack_v2/sources/new-interaction-event-received/README.md index 659edb38a5654..b00a09a1ea897 100644 --- a/components/slack_v2/sources/new-interaction-event-received/README.md +++ b/components/slack_v2/sources/new-interaction-event-received/README.md @@ -14,7 +14,7 @@ With this trigger, you can build workflows that perform some work with other API -What this short video to learn how to use this in a workflow, or follow the guide below. +Watch this short video to learn how to use this in a workflow, or follow the guide below. First, if you haven’t already - send yourself a message containing one or more interactive elements. Use the ******************Sending the message with an interactive element****************** guide below to send a message containing a button. @@ -24,7 +24,7 @@ If you have already sent a message containing an element, skip to ************** The easiest way is to send yourself a message using the ****************************Slack - Send Message Using Block Kit**************************** action: -![Selecting the Send Slack Message with Block Kit](https://res.cloudinary.com/pipedreamin/image/upload/v1668443844/docs/components/CleanShot_2022-11-10_at_10.25.522x_vxiooo.png)) +![Selecting the Send Slack Message with Block Kit](https://res.cloudinary.com/pipedreamin/image/upload/v1668443844/docs/components/CleanShot_2022-11-10_at_10.25.522x_vxiooo.png) Then select a **************Channel************** you’d like to send the message to, and use the **************[Block Kit Builder](https://app.slack.com/block-kit-builder/)************** to build a message, or just copy the example button blocks below: diff --git a/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs index c804c3075eb4f..716bf12f2a025 100644 --- a/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs +++ b/components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs @@ -86,15 +86,21 @@ export default { } if (this.includeUserData) { - const userResponse = await this.slack.usersInfo({ - user: event.user, - }); - const itemUserResponse = await this.slack.usersInfo({ - user: event.user, - }); - - event.userInfo = userResponse.user; - event.itemUserInfo = itemUserResponse.user; + const promises = [ + this.slack.usersInfo({ + user: event.user, + }), + ]; + if (event.item_user) { + promises.push(this.slack.usersInfo({ + user: event.item_user, + })); + } + const responses = await Promise.all(promises); + event.userInfo = responses[0].user; + if (responses[1]) { + event.itemUserInfo = responses[1].user; + } } try { From ff4b4760abcafd3fc110248a565e90010408e46d Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:27:13 -0400 Subject: [PATCH 25/27] revert change to slack update profile action instead update slack v2 update profile action --- components/slack/actions/update-profile/update-profile.mjs | 2 -- components/slack_v2/actions/update-profile/update-profile.mjs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/slack/actions/update-profile/update-profile.mjs b/components/slack/actions/update-profile/update-profile.mjs index 7a5d0bd744fda..e3c871d7d0246 100644 --- a/components/slack/actions/update-profile/update-profile.mjs +++ b/components/slack/actions/update-profile/update-profile.mjs @@ -72,7 +72,6 @@ export default { && !this.phone && !this.pronouns && !this.title - && !this.email ) { throw new ConfigurationError("Please provide at least one value to update"); } @@ -84,7 +83,6 @@ export default { phone: this.phone, pronouns: this.pronouns, title: this.title, - email: this.email, }, user: this.user, }); diff --git a/components/slack_v2/actions/update-profile/update-profile.mjs b/components/slack_v2/actions/update-profile/update-profile.mjs index 77b1c2fa8eff9..4b23ec6871766 100644 --- a/components/slack_v2/actions/update-profile/update-profile.mjs +++ b/components/slack_v2/actions/update-profile/update-profile.mjs @@ -72,6 +72,7 @@ export default { && !this.phone && !this.pronouns && !this.title + && !this.email ) { throw new ConfigurationError("Please provide at least one value to update"); } @@ -83,6 +84,7 @@ export default { phone: this.phone, pronouns: this.pronouns, title: this.title, + email: this.email, }, user: this.user, }); From a49bc7696f8b355733632023acf4efa3b4ebd2bd Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:19:16 -0400 Subject: [PATCH 26/27] set "add app to channel" default to false for send message actions Send Message actions can post to public channels as the bot without joining the channel The bot needs to be added to private channels before posting --- components/slack_v2/actions/common/send-message.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/slack_v2/actions/common/send-message.mjs b/components/slack_v2/actions/common/send-message.mjs index b8ee311f1fb43..21e22e4b32fcc 100644 --- a/components/slack_v2/actions/common/send-message.mjs +++ b/components/slack_v2/actions/common/send-message.mjs @@ -14,6 +14,8 @@ export default { slack, "addToChannel", ], + description: "If `true`, the app will be added to the specified non-DM channel(s) automatically. If `false`, you must add the app to the channel manually to post to a private channel as a bot. Defaults to `false`.", + default: false, }, post_at: { propDefinition: [ From 1f27d2b07cf6e04532faabae6f0d1eecd3d2d363 Mon Sep 17 00:00:00 2001 From: js07 <19861096+js07@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:33:14 -0400 Subject: [PATCH 27/27] set default value for as_user prop to true in send-message-to-user-or-group --- .../send-message-to-user-or-group.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs index 692f881a973f2..b337d6e1a65ad 100644 --- a/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +++ b/components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs @@ -52,6 +52,14 @@ export default { ], }, ...common.props, + // eslint-disable-next-line pipedream/props-label + as_user: { + ...common.props.as_user, + description: "Optionally pass `true` to post the message as the authenticated user, instead of as a bot. Defaults to `true`.", + // Default to true since bots can't message users unless the "Messages" + // tab is enabled, and can't message groups unless the bot is a member. + default: true, + }, // eslint-disable-next-line pipedream/props-label, pipedream/props-description addToChannel: { type: "boolean",