import { createAuthenticatedFetch } from 'kody:runtime'
export const SLACK_API_ORIGIN = 'https://slack.com'
export const SLACK_INTEGRATION = 'slack'
export type SlackResponse = {
ok: boolean
error?: string
needed?: string
provided?: string
response_metadata?: { next_cursor?: string }
[key: string]: unknown
}
export class SlackApiError extends Error {
readonly method: string
readonly status: number
readonly details: SlackResponse
constructor(method: string, status: number, details: SlackResponse) {
super('Slack API ' + method + ' failed: ' + (details.error || String(status)))
this.name = 'SlackApiError'
this.method = method
this.status = status
this.details = details
}
}
let authenticatedFetch: typeof fetch | null = null
async function getAuthenticatedFetch(): Promise<typeof fetch> {
if (!authenticatedFetch) {
authenticatedFetch = await createAuthenticatedFetch(SLACK_INTEGRATION)
}
return authenticatedFetch
}
export async function slackRequest<T extends SlackResponse>(
method: string,
params: Record<string, unknown> = {},
): Promise<T> {
if (!/^[a-z][a-z0-9_.]+$/.test(method)) {
throw new Error('Invalid Slack Web API method name.')
}
const body = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null || value === '') continue
body.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value))
}
const url = new URL('/api/' + method, SLACK_API_ORIGIN)
const authedFetch = await getAuthenticatedFetch()
const response = await authedFetch(url.toString(), {
method: 'POST',
headers: {
accept: 'application/json',
'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
},
body,
})
const result = (await response.json()) as SlackResponse
if (!response.ok || result.ok !== true) {
throw new SlackApiError(method, response.status, result)
}
return result as T
}
export function nextCursor(response: SlackResponse): string | null {
const cursor = response.response_metadata?.next_cursor
return typeof cursor === 'string' && cursor.length > 0 ? cursor : null
}