@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
JavaScript
// 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