184 lines
4.4 KiB
TypeScript
184 lines
4.4 KiB
TypeScript
import { WebClient } from "@slack/web-api";
|
|
|
|
import { loadConfig } from "../config/config.js";
|
|
import { sendMessageSlack } from "./send.js";
|
|
import { resolveSlackBotToken } from "./token.js";
|
|
|
|
export type SlackActionClientOpts = {
|
|
token?: string;
|
|
client?: WebClient;
|
|
};
|
|
|
|
export type SlackMessageSummary = {
|
|
ts?: string;
|
|
text?: string;
|
|
user?: string;
|
|
thread_ts?: string;
|
|
reply_count?: number;
|
|
reactions?: Array<{
|
|
name?: string;
|
|
count?: number;
|
|
users?: string[];
|
|
}>;
|
|
};
|
|
|
|
export type SlackPin = {
|
|
type?: string;
|
|
message?: { ts?: string; text?: string };
|
|
file?: { id?: string; name?: string };
|
|
};
|
|
|
|
function resolveToken(explicit?: string) {
|
|
const cfgToken = loadConfig().slack?.botToken;
|
|
const token = resolveSlackBotToken(
|
|
explicit ?? process.env.SLACK_BOT_TOKEN ?? cfgToken ?? undefined,
|
|
);
|
|
if (!token) {
|
|
throw new Error(
|
|
"SLACK_BOT_TOKEN or slack.botToken is required for Slack actions",
|
|
);
|
|
}
|
|
return token;
|
|
}
|
|
|
|
function normalizeEmoji(raw: string) {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
throw new Error("Emoji is required for Slack reactions");
|
|
}
|
|
return trimmed.replace(/^:+|:+$/g, "");
|
|
}
|
|
|
|
async function getClient(opts: SlackActionClientOpts = {}) {
|
|
const token = resolveToken(opts.token);
|
|
return opts.client ?? new WebClient(token);
|
|
}
|
|
|
|
export async function reactSlackMessage(
|
|
channelId: string,
|
|
messageId: string,
|
|
emoji: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
await client.reactions.add({
|
|
channel: channelId,
|
|
timestamp: messageId,
|
|
name: normalizeEmoji(emoji),
|
|
});
|
|
}
|
|
|
|
export async function listSlackReactions(
|
|
channelId: string,
|
|
messageId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
): Promise<SlackMessageSummary["reactions"]> {
|
|
const client = await getClient(opts);
|
|
const result = await client.reactions.get({
|
|
channel: channelId,
|
|
timestamp: messageId,
|
|
full: true,
|
|
});
|
|
const message = result.message as SlackMessageSummary | undefined;
|
|
return message?.reactions ?? [];
|
|
}
|
|
|
|
export async function sendSlackMessage(
|
|
to: string,
|
|
content: string,
|
|
opts: SlackActionClientOpts & { mediaUrl?: string } = {},
|
|
) {
|
|
return await sendMessageSlack(to, content, {
|
|
token: opts.token,
|
|
mediaUrl: opts.mediaUrl,
|
|
client: opts.client,
|
|
});
|
|
}
|
|
|
|
export async function editSlackMessage(
|
|
channelId: string,
|
|
messageId: string,
|
|
content: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
await client.chat.update({
|
|
channel: channelId,
|
|
ts: messageId,
|
|
text: content,
|
|
});
|
|
}
|
|
|
|
export async function deleteSlackMessage(
|
|
channelId: string,
|
|
messageId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
await client.chat.delete({
|
|
channel: channelId,
|
|
ts: messageId,
|
|
});
|
|
}
|
|
|
|
export async function readSlackMessages(
|
|
channelId: string,
|
|
opts: SlackActionClientOpts & {
|
|
limit?: number;
|
|
before?: string;
|
|
after?: string;
|
|
} = {},
|
|
): Promise<{ messages: SlackMessageSummary[]; hasMore: boolean }> {
|
|
const client = await getClient(opts);
|
|
const result = await client.conversations.history({
|
|
channel: channelId,
|
|
limit: opts.limit,
|
|
latest: opts.before,
|
|
oldest: opts.after,
|
|
});
|
|
return {
|
|
messages: (result.messages ?? []) as SlackMessageSummary[],
|
|
hasMore: Boolean(result.has_more),
|
|
};
|
|
}
|
|
|
|
export async function getSlackMemberInfo(
|
|
userId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
return await client.users.info({ user: userId });
|
|
}
|
|
|
|
export async function listSlackEmojis(opts: SlackActionClientOpts = {}) {
|
|
const client = await getClient(opts);
|
|
return await client.emoji.list();
|
|
}
|
|
|
|
export async function pinSlackMessage(
|
|
channelId: string,
|
|
messageId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
await client.pins.add({ channel: channelId, timestamp: messageId });
|
|
}
|
|
|
|
export async function unpinSlackMessage(
|
|
channelId: string,
|
|
messageId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
) {
|
|
const client = await getClient(opts);
|
|
await client.pins.remove({ channel: channelId, timestamp: messageId });
|
|
}
|
|
|
|
export async function listSlackPins(
|
|
channelId: string,
|
|
opts: SlackActionClientOpts = {},
|
|
): Promise<SlackPin[]> {
|
|
const client = await getClient(opts);
|
|
const result = await client.pins.list({ channel: channelId });
|
|
return (result.items ?? []) as SlackPin[];
|
|
}
|