const API_BASE = "https://slack.com/api";
export interface SlackFile {
filename: string;
contentType: string;
content: ArrayBuffer;
}
export class SlackApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
async function slackApi(botToken: string, method: string, body: Record<string, unknown>): Promise<Record<string, unknown>> {
const response = await fetch(`${API_BASE}/${method}`, {
method: "POST",
headers: {
Authorization: `Bearer ${botToken}`,
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(body),
});
const data = (await response.json().catch(() => ({}))) as { ok?: boolean; error?: string };
if (!response.ok || !data.ok) {
throw new SlackApiError(response.status, `Slack API ${method} failed: ${data.error ?? response.status}`);
}
return data;
}
// A user ID works directly as `channel` on chat.postMessage -- Slack opens
// the DM itself, no separate conversations.open call needed first, same
// simplicity as Telegram's chat_id.
export async function postMessage(botToken: string, userId: string, text: string): Promise<void> {
await slackApi(botToken, "chat.postMessage", { channel: userId, text, unfurl_links: false });
}