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

598 lines 19.8 kB
// src/MicrofoxSlackClient.ts import { WebClient } from "@slack/web-api"; import dotenv from "dotenv"; dotenv.config(); var MicrofoxSlackClient = class { constructor(token, options) { this.web = new 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; } }; export { MicrofoxSlackClient }; //# sourceMappingURL=index.mjs.map