Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/discord

src/oauth.ts

63 lines · 1.6 KB · TypeScript
import { createAuthenticatedFetch } from 'kody:runtime'
import {
	DISCORD_API_BASE_URL,
	DISCORD_USER_AGENT,
	missingOauthIntegrationError,
	throwDiscordApiError,
} from './errors.ts'
import { integrationName, type InputRecord } from './validation.ts'

const fetchers = new Map<string, typeof fetch>()

async function oauthFetch(integration: string): Promise<typeof fetch> {
	const cached = fetchers.get(integration)
	if (cached) return cached
	try {
		const authed = await createAuthenticatedFetch(integration)
		fetchers.set(integration, authed)
		return authed
	} catch (error) {
		throw missingOauthIntegrationError(integration, error)
	}
}

export async function discordOauthRequest(
	path: string,
	input: InputRecord,
	init: { method?: string; query?: Record<string, string | number | undefined> } = {},
): Promise<unknown> {
	const integration = integrationName(input)
	const url = new URL(DISCORD_API_BASE_URL + path)
	for (const [key, value] of Object.entries(init.query ?? {})) {
		if (value === undefined) continue
		url.searchParams.set(key, String(value))
	}

	const authedFetch = await oauthFetch(integration)
	const response = await authedFetch(url.toString(), {
		method: init.method ?? 'GET',
		headers: {
			accept: 'application/json',
			'User-Agent': DISCORD_USER_AGENT,
		},
	})
	const text = await response.text()
	let json: unknown = null
	if (text) {
		try {
			json = JSON.parse(text)
		} catch {
			json = text
		}
	}
	if (!response.ok) {
		throwDiscordApiError({
			status: response.status,
			path: url.pathname + url.search,
			lane: 'oauth',
			details: json ?? text,
			integration,
		})
	}
	return json
}