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 { 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 { const client = await getClient(opts); const result = await client.pins.list({ channel: channelId }); return (result.items ?? []) as SlackPin[]; }