UNPKG

@microfox/slack

Version:

This package provides a lightweight, proxy interface to the official Slack Web API, offering a curated set of the most commonly used functions for building Slack integrations. It is designed to be simple, efficient, and easy to integrate into your project

633 lines (631 loc) 21.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { MicrofoxSlackClient: () => MicrofoxSlackClient }); module.exports = __toCommonJS(index_exports); // src/MicrofoxSlackClient.ts var import_web_api = require("@slack/web-api"); var import_dotenv = __toESM(require("dotenv")); import_dotenv.default.config(); var MicrofoxSlackClient = class { constructor(token, options) { this.web = new import_web_api.WebClient(token, options); } /** * Lists channels in a workspace. * @param cursor A cursor to the next page of results. * @param limit The maximum number of channels to return. * @param types Channel types to include. Defaults to all types (public, private, im). */ async getChannels({ cursor, limit = 50, types = ["public", "private"] }) { var _a; const typeMapping = { "public": "public_channel", "private": "private_channel", "im": "im" }; const slackTypes = types.map((type) => typeMapping[type]).join(","); let channels = []; let nextCursor = cursor; let hasMore = true; while (hasMore) { const result = await this.web.conversations.list({ types: slackTypes, limit, cursor: nextCursor }); channels = [...channels, ...result.channels || []]; nextCursor = (_a = result.response_metadata) == null ? void 0 : _a.next_cursor; hasMore = !!nextCursor && channels.length < limit; } return { channels, nextCursor }; } /** * Lists channel IDs and names in a workspace. * @param cursor A cursor to the next page of results. * @param limit The maximum number of channels to return. * @param types Channel types to include. Defaults to all types (public, private, im). */ async getChannelsIds({ cursor, limit, types = ["public", "private", "im"] }) { var _a; const response = await this.getChannels({ cursor, limit, types }); return { channels: ((_a = response.channels) == null ? void 0 : _a.map((channel) => ({ id: channel.id || "", name: channel.name || "" }))) || [], nextCursor: response.nextCursor }; } /** * Fetches information about a conversation. * @param channelId Conversation ID to fetch information for. */ async getChannelConversationInfo({ channelId }) { const result = await this.web.conversations.info({ channel: channelId }); return result.channel; } /** * Lists all users in a workspace. * @param cursor A cursor to the next page of results. * @param limit The maximum number of users to return. * @param includeBots Whether to include bots. */ async getActiveUsers({ cursor, limit, includeBots = false }) { var _a, _b, _c; const result = await this.web.users.list({ limit: limit || 100, ...cursor ? { cursor } : {} }); return { users: ((_b = (_a = result.members) == null ? void 0 : _a.filter((member) => !member.deleted)) == null ? void 0 : _b.filter((member) => includeBots ? true : !member.is_bot)) || [], nextCursor: (_c = result.response_metadata) == null ? void 0 : _c.next_cursor }; } /** * Lists all users in a workspace. * @param cursor A cursor to the next page of results. * @param limit The maximum number of users to return. * @param includeBots Whether to include bots. */ async getActiveUsersIds({ cursor, limit, includeBots = false }) { var _a; const response = await this.getActiveUsers({ includeBots, cursor, limit }); return { users: ((_a = response.users) == null ? void 0 : _a.map((user) => { var _a2, _b, _c, _d; return { id: user.id || "", name: user.name || "", email: ((_a2 = user.profile) == null ? void 0 : _a2.email) || "", real_name: ((_b = user.profile) == null ? void 0 : _b.real_name) || "", display_name: ((_c = user.profile) == null ? void 0 : _c.display_name) || "", title: ((_d = user.profile) == null ? void 0 : _d.title) || "" }; })) || [], nextCursor: response.nextCursor }; } /** * Lists all users in a channel. * @param channelId Channel ID to get members of. */ async getChannelMembers({ channelId, includeBots = false, limit, nextCursor }) { var _a, _b; const result = await this.web.conversations.members({ channel: channelId, limit, cursor: nextCursor }); let members = []; if (result == null ? void 0 : result.members) { members = await Promise.all((_a = result == null ? void 0 : result.members) == null ? void 0 : _a.map(async (member) => { var _a2, _b2, _c, _d; const user = await this.getUserInfo({ userId: member }); return { id: member || "", name: (user == null ? void 0 : user.name) || "", is_bot: (user == null ? void 0 : user.is_bot) || false, real_name: ((_a2 = user == null ? void 0 : user.profile) == null ? void 0 : _a2.real_name) || "", display_name: ((_b2 = user == null ? void 0 : user.profile) == null ? void 0 : _b2.display_name) || "", title: ((_c = user == null ? void 0 : user.profile) == null ? void 0 : _c.title) || "", email: ((_d = user == null ? void 0 : user.profile) == null ? void 0 : _d.email) || "", is_deleted: (user == null ? void 0 : user.deleted) || false }; })); } return { members: (members == null ? void 0 : members.filter((member) => includeBots ? true : !member.is_bot)) || [], nextCursor: (_b = result.response_metadata) == null ? void 0 : _b.next_cursor }; } /** * Finds a user by their email address. * @param email The email address of the user to find. */ async getUserByEmail({ email }) { const result = await this.web.users.lookupByEmail({ email }); return result.user; } /** * Finds users by their email addresses. * @param emails The email addresses of the users to find. */ async getUsersByEmails({ emails }) { const result = await Promise.all(emails.map(async (email) => { const user = await this.getUserByEmail({ email }); return user; })); return result; } /** * Sends a direct message to a user. * @param userId The ID of the user to message. * @param text The text of the message to send. * @param username Optional custom username for the message. * @param icon_url Optional URL to an image to use as the icon for the message. */ async messageUser({ userId, text, username, icon_url }) { var _a; const im = await this.web.conversations.open({ users: userId }); if (im.ok && ((_a = im.channel) == null ? void 0 : _a.id)) { return this.messageChannel({ channelId: im.channel.id, text, username, icon_url }); } throw new Error(`Could not open DM with user ${userId}`); } /** * Sends a message to a channel. * @param channelId The ID of the channel to message. * @param text The text of the message to send. * @param username Optional custom username for the message. * @param icon_url Optional URL to an image to use as the icon for the message. */ async messageChannel({ channelId, text, username, icon_url }) { const payload = { channel: channelId, text }; const finalUsername = username != null ? username : process.env.SLACK_AUTHOR_NAME; if (finalUsername) { payload.username = finalUsername; } const finalIconUrl = icon_url != null ? icon_url : process.env.SLACK_ICON_URL; if (finalIconUrl) { payload.icon_url = finalIconUrl; } return this.web.chat.postMessage(payload); } /** * Sends a direct message to multiple users with optional templating. * @param userIds The IDs of the users to message. * @param text The text of the message to send. Can include template variables for personalization. * @param username Optional custom username for the message. * @param icon_url Optional URL to an image to use as the icon for the message. * * Available template variables: * - {mention} - Mentions the user (@username) * - {user_name} - User's name (fallback: username -> real_name -> display_name) * - {user_email} - User's email address * - {user_title} - User's job title * - {user_phone} - User's phone number * - {user_status} - User's status text * - {user_avatar} - User's profile image URL * - {first_name} - User's first name * - {last_name} - User's last name * * @example * ```typescript * // Simple message * await client.messageUsers({ * userIds: ['U1234567890', 'U0987654321'], * text: "Hello everyone!" * }); * * // Templated message * await client.messageUsers({ * userIds: ['U1234567890', 'U0987654321'], * text: "Hi {mention}! Your title is {user_title} and email is {user_email}." * }); * ``` */ async messageUsers({ userIds, text, username, icon_url }) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o; const results = []; const hasTemplateVars = /{(mention|user_name|user_email|user_title|user_phone|user_status|user_avatar|first_name|last_name)}/.test(text); if (!hasTemplateVars) { const responses = await Promise.all( userIds.map( (userId) => this.messageUser({ userId, text, username, icon_url }) ) ); return responses; } for (const userId of userIds) { try { const user = await this.getUserInfo({ userId }); if (!user) { continue; } let personalizedMessage = text.replace(/{mention}/g, `<@${user.id}>`).replace(/{user_name}/g, user.name || user.real_name || ((_a = user.profile) == null ? void 0 : _a.display_name) || ((_b = user.profile) == null ? void 0 : _b.real_name) || "User").replace(/{user_email}/g, ((_c = user.profile) == null ? void 0 : _c.email) || "").replace(/{user_title}/g, ((_d = user.profile) == null ? void 0 : _d.title) || "").replace(/{user_phone}/g, ((_e = user.profile) == null ? void 0 : _e.phone) || "").replace(/{user_status}/g, ((_f = user.profile) == null ? void 0 : _f.status_text) || "").replace(/{user_avatar}/g, ((_g = user.profile) == null ? void 0 : _g.image_72) || ((_h = user.profile) == null ? void 0 : _h.image_192) || ((_i = user.profile) == null ? void 0 : _i.image_original) || "").replace(/{first_name}/g, ((_j = user.profile) == null ? void 0 : _j.first_name) || ((_l = (_k = user.profile) == null ? void 0 : _k.real_name) == null ? void 0 : _l.split(" ")[0]) || user.name || "User").replace(/{last_name}/g, ((_m = user.profile) == null ? void 0 : _m.last_name) || ((_o = (_n = user.profile) == null ? void 0 : _n.real_name) == null ? void 0 : _o.split(" ").slice(1).join(" ")) || ""); const result = await this.messageUser({ userId: user.id, text: personalizedMessage, username, icon_url }); results.push(result); } catch (error) { console.error(`Failed to send message to user ${userId}:`, error); } } return results; } /** * Sends a message to multiple channels. * @param channelIds The IDs of the channels to message. * @param text The text of the message to send. * @param username Optional custom username for the message. * @param icon_url Optional URL to an image to use as the icon for the message. */ async messageChannels({ channelIds, text, username, icon_url }) { const results = await Promise.all( channelIds.map( (channelId) => this.messageChannel({ channelId, text, username, icon_url }) ) ); return results; } /** * Sets a reminder for a user. * @param userId The ID of the user to set a reminder for. * @param text The text of the reminder. * @param time A string describing when the reminder should fire (e.g., "in 5 minutes" or a Unix timestamp). */ async setReminder({ userId, text, time }) { return this.web.reminders.add({ user: userId, text, time }); } /** * Creates a new channel. * @param name The name of the channel to create. * @param isPrivate Whether the channel should be private. Defaults to false. * @param join Whether to join the channel after creation. Defaults to true. * @param userIds Optional array of user IDs to add to the channel after creation. */ async createChannel({ name, isPrivate = false, userIds }) { var _a; const result = await this.web.conversations.create({ name, is_private: isPrivate }); if ((_a = result == null ? void 0 : result.channel) == null ? void 0 : _a.id) { if (userIds && userIds.length > 0) { await this.addUsersToChannel({ channelId: result.channel.id, userIds }); } } return result.channel; } /** * Adds a reaction to a message. * @param channelId The ID of the channel where the message is. * @param timestamp The timestamp of the message to react to. * @param reaction The name of the emoji to use for the reaction. */ async reactMessage({ channelId, timestamp, reaction }) { return this.web.reactions.add({ channel: channelId, timestamp, name: reaction }); } /** * Gets information about a user. * @param userId The ID of the user to get information for. */ async getUserInfo({ userId }) { const result = await this.web.users.info({ user: userId }); return result.user; } /** * Fetches a list of conversations a user is a member of. * @param userId The ID of the user to fetch conversations for. * @param cursor A cursor to the next page of results. * @param limit The maximum number of conversations to return. * @param types An array of conversation types to include. * @param excludeArchived Whether to exclude archived conversations. */ async getUserChannels({ userId, cursor, limit, types = ["public_channel", "private_channel"], excludeArchived }) { var _a; const result = await this.web.users.conversations({ user: userId, cursor, limit, types: types.join(","), exclude_archived: excludeArchived }); return { channels: result.channels || [], nextCursor: (_a = result.response_metadata) == null ? void 0 : _a.next_cursor }; } /** * Replies to a message in a thread. * @param channelId The ID of the channel where the message is. * @param thread_ts The timestamp of the message to reply to, establishing the thread. * @param text The text of the reply. * @param username Optional custom username for the message. * @param icon_url Optional URL to an image to use as the icon for the message. */ async replyMessage({ channelId, thread_ts, text, username, icon_url }) { const payload = { channel: channelId, thread_ts, text }; const finalUsername = username != null ? username : process.env.SLACK_AUTHOR_NAME; if (finalUsername) { payload.username = finalUsername; } const finalIconUrl = icon_url != null ? icon_url : process.env.SLACK_ICON_URL; if (finalIconUrl) { payload.icon_url = finalIconUrl; } return this.web.chat.postMessage(payload); } /** * Joins a channel. * @param channelId The ID of the channel to join. */ async joinChannel({ channelId }) { return this.web.conversations.join({ channel: channelId }); } /** * Adds multiple users to a channel. * @param channelId The ID of the channel to add users to. * @param userIds Array of user IDs to add to the channel. */ async addUsersToChannel({ channelId, userIds }) { const results = []; try { const result = await this.web.conversations.invite({ channel: channelId, users: userIds.join(",") }); results.push(result); } catch (error) { console.warn("Bulk user invite failed, trying individual invites:", error); for (const userId of userIds) { try { const result = await this.web.conversations.invite({ channel: channelId, users: userId }); results.push(result); } catch (individualError) { console.error(`Failed to add user ${userId} to channel ${channelId}:`, individualError); } } } return results; } /** * Removes a user from a channel. * @param channelId The ID of the channel to remove the user from. * @param userId The ID of the user to remove. */ async removeUserFromChannel({ channelId, userId }) { return this.web.conversations.kick({ channel: channelId, user: userId }); } /** * Gets information about a file. * @param fileId The ID of the file to get information for. */ async getFileInfo({ fileId }) { const result = await this.web.files.info({ file: fileId }); return result.file; } /** * Fetches a conversation's history of messages and events. * @param channelId Conversation ID to fetch history for. * @param limit The maximum number of items to return. * @param latest End of the time range of messages to include in results. * @param oldest Start of the time range of messages to include in results. * @param inclusive Include messages with latest or oldest timestamps in results. * @param cursor Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata. */ async getConversationHistory({ channelId, limit, latest, oldest, inclusive, cursor }) { var _a; const result = await this.web.conversations.history({ channel: channelId, limit, latest, oldest, inclusive, cursor }); return { messages: result.messages || [], nextCursor: (_a = result.response_metadata) == null ? void 0 : _a.next_cursor }; } /** * Uploads a file to a channel using an external URL. * @param filename The name of the file. * @param file A Buffer containing the file content. * @param channelId The ID of the channel to upload the file to. Can be a comma-separated list of strings. * @param alt_text Description of image for screen-reader. * @param snippet_type Syntax type of the snippet being uploaded. * @param initialComment The message text introducing the file in specified channels. * @param title An optional title for the file. */ async uploadFile({ filename, file, channelId, alt_text, snippet_type, initialComment, title }) { const uploadURLResponse = await this.web.files.getUploadURLExternal({ filename, length: file.length, alt_text, snippet_type }); if (!uploadURLResponse.ok || !uploadURLResponse.upload_url || !uploadURLResponse.file_id) { throw new Error(`Failed to get upload URL: ${uploadURLResponse.error}`); } const { upload_url, file_id } = uploadURLResponse; const uploadResponse = await fetch(upload_url, { method: "POST", body: file }); if (!uploadResponse.ok) { throw new Error(`File upload failed with status: ${uploadResponse.statusText}`); } const completeUploadResponse = await this.web.files.completeUploadExternal({ files: [{ id: file_id, title: title || filename }], channel_id: channelId, initial_comment: initialComment }); if (!completeUploadResponse.ok) { throw new Error(`Failed to complete upload: ${completeUploadResponse.error}`); } return completeUploadResponse.files; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MicrofoxSlackClient }); //# sourceMappingURL=index.js.map