import type { ApiResponse, DiscordError, DiscordRestClient } from "./types"; export interface ClientOptions { token: string; baseUrl?: string; fetch?: typeof fetch; } export function RESTClient(options: ClientOptions): DiscordRestClient { const baseUrl = options.baseUrl || "https://discord.com/api/v10"; const customFetch = options.fetch ?? globalThis.fetch; const executeRequest = async ( segments: string[], method: string, body?: unknown, query?: Record, headers?: Record, ): Promise> => { let url = `${baseUrl}/${segments.join("/")}`; if (query) { const searchParams = new URLSearchParams(); for (const [key, val] of Object.entries(query)) { if (val !== undefined && val !== null) { if (Array.isArray(val)) { for (const item of val) { searchParams.append(key, String(item)); } } else { searchParams.append(key, String(val)); } } } const queryString = searchParams.toString(); if (queryString) url += `?${queryString}`; } try { const isFormData = typeof FormData !== "undefined" && body instanceof FormData; const reqHeaders: Record = { Authorization: `Bot ${options.token}`, ...headers, }; if (!isFormData && !reqHeaders["Content-Type"] && body !== undefined) reqHeaders["Content-Type"] = "application/json"; const res = await customFetch(url, { method, headers: reqHeaders, body: isFormData ? (body as BodyInit) : body !== undefined ? JSON.stringify(body) : undefined, }); const isOk = res.ok; const status = res.status; let data: unknown = null; if (status !== 204) data = (await res.json().catch(() => null)) as unknown; if (isOk) return { ok: true, data, status }; const errorData: DiscordError = data && typeof data === "object" && "message" in data ? (data as DiscordError) : { message: "Unknown API Error" }; return { ok: false, data: errorData, status }; } catch (error) { const message = error instanceof Error ? error.message : "Network Error"; return { ok: false, data: { message }, status: 0, }; } }; const createProxy = (pathSegments: string[] = []): unknown => { const dummyTarget = () => {}; const handler: ProxyHandler = { apply(_target, _thisArg, argList) { const nextSegments = [...pathSegments, ...argList.map(String)]; return createProxy(nextSegments); }, get(_target, prop) { const propStr = String(prop); if (prop === "then") { const promise = executeRequest(pathSegments, "GET"); return promise.then.bind(promise); } const lowercaseProp = propStr.toLowerCase(); const methodMap: Record = { create: "POST", post: "POST", delete: "DELETE", update: "PATCH", patch: "PATCH", put: "PUT", get: "GET", }; if (lowercaseProp in methodMap) { const httpMethod = methodMap[lowercaseProp]; if (!httpMethod) throw new Error(`unsupported HTTP method: ${lowercaseProp}`); return async ( bodyOrOptions?: unknown, methodOptions?: { query?: Record; headers?: Record }, ) => { let body: unknown; let query: Record | undefined; let headers: Record | undefined; if (httpMethod === "GET" || httpMethod === "DELETE") { const opts = bodyOrOptions as | { query?: Record; headers?: Record } | undefined; query = opts?.query; headers = opts?.headers; } else { body = bodyOrOptions; query = methodOptions?.query; headers = methodOptions?.headers; } return executeRequest(pathSegments, httpMethod, body, query, headers); }; } const nextSegment = prop === "me" ? "@me" : propStr; return createProxy([...pathSegments, nextSegment]); }, }; return new Proxy(dummyTarget, handler); }; return createProxy([]) as unknown as DiscordRestClient; }