pkgs/rest/src/client.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
import type { ApiResponse, DiscordError, DiscordRestClient } from "./types";
export interface ClientOptions {
token: string;
baseUrl?: string;
}
export function RESTClient(options: ClientOptions): DiscordRestClient {
const baseUrl = options.baseUrl || "https://discord.com/api/v10";
const executeRequest = async (
segments: string[],
method: string,
body?: unknown,
query?: Record<string, unknown>,
): Promise<ApiResponse<unknown>> => {
let url = `${baseUrl}/${segments.join("/")}`;
if (query) {
const searchParams = new URLSearchParams();
for (const [key, val] of Object.entries(query)) {
if (val !== undefined && val !== null) {
searchParams.append(key, String(val));
}
}
const queryString = searchParams.toString();
if (queryString) url += `?${queryString}`;
}
try {
const res = await fetch(url, {
method,
headers: {
Authorization: `Bot ${options.token}`,
"Content-Type": "application/json",
},
body: body ? 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<typeof dummyTarget> = {
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<string, string> = {
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<string, unknown> },
) => {
let body: unknown;
let query: Record<string, unknown> | undefined;
if (httpMethod === "GET" || httpMethod === "DELETE") {
query = (bodyOrOptions as { query?: Record<string, unknown> } | undefined)?.query;
} else {
body = bodyOrOptions;
query = methodOptions?.query;
}
return executeRequest(pathSegments, httpMethod, body, query);
};
}
const nextSegment = prop === "me" ? "@me" : propStr;
return createProxy([...pathSegments, nextSegment]);
},
};
return new Proxy(dummyTarget, handler);
};
return createProxy([]) as unknown as DiscordRestClient;
}
|