forked from Dougley/slshx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.ts
127 lines (115 loc) · 3.37 KB
/
helpers.ts
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
124
125
126
127
import type {
RESTPostOAuth2ClientCredentialsResult,
Snowflake,
} from "discord-api-types/v9";
import { Routes } from "discord-api-types/v9";
import { log } from "../helpers";
export { Routes };
export interface APIBasicAuth {
username: string;
password: string;
}
export interface APIBearerAuth {
bearer: string;
expires?: number;
}
export interface APIBotAuth {
bot: string;
}
export type APIAuth = APIBasicAuth | APIBearerAuth | APIBotAuth;
export class APIError extends Error {
constructor(
readonly method: string,
readonly path: string,
readonly code: number,
message: string
) {
super(message);
this.name = `APIError [${code}]`;
}
}
const RELATIVE_FORMAT = /* @__PURE__ */ new Intl.RelativeTimeFormat("en", {
numeric: "always",
style: "long",
});
export async function call<Body, Result>(
method: string,
path: string,
body?: Body,
auth?: APIAuth
): Promise<Result> {
const url = `https://discord.com/api/v9${path}`;
const headers: HeadersInit = {};
const init: RequestInit = { method, headers };
if (body instanceof FormData) {
init.body = body;
} else if (body instanceof URLSearchParams) {
headers["Content-Type"] = "application/x-www-form-urlencoded";
init.body = body.toString();
} else if (body) {
headers["Content-Type"] = "application/json";
init.body = JSON.stringify(body);
}
if (auth) {
if ("bearer" in auth) {
headers["Authorization"] = `Bearer ${auth.bearer}`;
} else if ("bot" in auth) {
headers["Authorization"] = `Bot ${auth.bot}`;
} else {
const credentials = btoa(`${auth.username}:${auth.password}`);
headers["Authorization"] = `Basic ${credentials}`;
}
}
const res = await fetch(url, init);
// @ts-expect-error globalThis isn't updated with custom globals
if (globalThis.MINIFLARE) {
let resetString = "";
const limit = res.headers.get("x-ratelimit-limit");
const remaining = res.headers.get("x-ratelimit-remaining");
const reset = res.headers.get("x-ratelimit-reset");
if (limit && remaining && reset) {
resetString = `(${remaining}/${limit} remaining, reset ${RELATIVE_FORMAT.format(
Math.round(parseFloat(reset) - Date.now() / 1000),
"second"
)})`;
}
// Replace interaction tokens with ***
path = path.replace(/[a-z0-9]{50,}/i, "***") + ":";
log(method, path, res.status.toString(), res.statusText, resetString);
}
if (res.status === 204) {
return undefined as unknown as Result;
} else if (res.ok) {
return await res.json<Result>();
}
let error = await res.text();
// Try prettify the error if it's JSON
try {
error = JSON.stringify(JSON.parse(error), null, 2);
} catch {}
throw new APIError(method, path, res.status, error);
}
/** @see https://discord.com/developers/docs/topics/oauth2#client-credentials-grant */
export async function getBearerAuth(
applicationId: Snowflake,
applicationSecret: string
): Promise<APIBearerAuth> {
const body = new URLSearchParams({
grant_type: "client_credentials",
scope: "applications.commands.update",
});
const auth: APIBasicAuth = {
username: applicationId,
password: applicationSecret,
};
const res: RESTPostOAuth2ClientCredentialsResult = await call(
"POST",
"/oauth2/token",
body,
auth
);
return {
bearer: res.access_token,
expires: Date.now() + res.expires_in * 1000,
};
}