116 lines · 4.0 KB
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 });
}