@ovencord/rest 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +94 -0
- package/package.json +58 -0
- package/src/environment.ts +11 -0
- package/src/index.ts +8 -0
- package/src/lib/CDN.ts +407 -0
- package/src/lib/REST.ts +472 -0
- package/src/lib/errors/DiscordAPIError.ts +116 -0
- package/src/lib/errors/HTTPError.ts +29 -0
- package/src/lib/errors/RateLimitError.ts +47 -0
- package/src/lib/handlers/BurstHandler.ts +153 -0
- package/src/lib/handlers/SequentialHandler.ts +431 -0
- package/src/lib/handlers/Shared.ts +205 -0
- package/src/lib/interfaces/Handler.ts +27 -0
- package/src/lib/utils/AsyncEventEmitter.ts +2 -0
- package/src/lib/utils/AsyncQueue.ts +64 -0
- package/src/lib/utils/constants.ts +65 -0
- package/src/lib/utils/types.ts +406 -0
- package/src/lib/utils/utils.ts +248 -0
- package/src/shared.ts +16 -0
- package/src/strategies/bunRequest.ts +32 -0
package/src/lib/CDN.ts
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
|
|
2
|
+
import { CDNRoutes } from 'discord-api-types/v10';
|
|
3
|
+
import {
|
|
4
|
+
ALLOWED_EXTENSIONS,
|
|
5
|
+
ALLOWED_SIZES,
|
|
6
|
+
ALLOWED_STICKER_EXTENSIONS,
|
|
7
|
+
DefaultRestOptions,
|
|
8
|
+
type ImageExtension,
|
|
9
|
+
type ImageSize,
|
|
10
|
+
type StickerExtension,
|
|
11
|
+
} from './utils/constants.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The options used for image URLs.
|
|
15
|
+
*/
|
|
16
|
+
export interface BaseImageURLOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The extension to use for the image URL.
|
|
19
|
+
*
|
|
20
|
+
* @defaultValue `'webp'`
|
|
21
|
+
*/
|
|
22
|
+
extension?: ImageExtension;
|
|
23
|
+
/**
|
|
24
|
+
* The size specified in the image URL.
|
|
25
|
+
*/
|
|
26
|
+
size?: ImageSize;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface EmojiURLOptionsWebp extends BaseImageURLOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Whether to use the `animated` query parameter.
|
|
32
|
+
*/
|
|
33
|
+
animated?: boolean;
|
|
34
|
+
extension?: 'webp';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EmojiURLOptionsNotWebp extends BaseImageURLOptions {
|
|
38
|
+
extension: Exclude<ImageExtension, 'webp'>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The options used for emoji URLs.
|
|
43
|
+
*/
|
|
44
|
+
export type EmojiURLOptions = EmojiURLOptionsNotWebp | EmojiURLOptionsWebp;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The options used for image URLs that may be animated.
|
|
48
|
+
*/
|
|
49
|
+
export interface ImageURLOptions extends BaseImageURLOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Whether to prefer the static asset.
|
|
52
|
+
*/
|
|
53
|
+
forceStatic?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The options to use when making a CDN URL
|
|
58
|
+
*/
|
|
59
|
+
interface MakeURLOptions {
|
|
60
|
+
/**
|
|
61
|
+
* The allowed extensions that can be used
|
|
62
|
+
*/
|
|
63
|
+
allowedExtensions?: readonly string[];
|
|
64
|
+
/**
|
|
65
|
+
* Whether to use the `animated` query parameter
|
|
66
|
+
*/
|
|
67
|
+
animated?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The base URL.
|
|
70
|
+
*
|
|
71
|
+
* @defaultValue `DefaultRestOptions.cdn`
|
|
72
|
+
*/
|
|
73
|
+
base?: string;
|
|
74
|
+
/**
|
|
75
|
+
* The extension to use for the image URL
|
|
76
|
+
*
|
|
77
|
+
* @defaultValue `'webp'`
|
|
78
|
+
*/
|
|
79
|
+
extension?: string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The size specified in the image URL
|
|
82
|
+
*/
|
|
83
|
+
size?: ImageSize;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Options for initializing the {@link CDN} class.
|
|
88
|
+
*/
|
|
89
|
+
export interface CDNOptions {
|
|
90
|
+
/**
|
|
91
|
+
* The base URL for the CDN.
|
|
92
|
+
*
|
|
93
|
+
* @defaultValue `DefaultRestOptions.cdn`
|
|
94
|
+
*/
|
|
95
|
+
cdn?: string | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* The base URL for the media proxy.
|
|
98
|
+
*
|
|
99
|
+
* @defaultValue `DefaultRestOptions.mediaProxy`
|
|
100
|
+
*/
|
|
101
|
+
mediaProxy?: string | undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The CDN link builder
|
|
106
|
+
*/
|
|
107
|
+
export class CDN {
|
|
108
|
+
private readonly cdn: string;
|
|
109
|
+
|
|
110
|
+
private readonly mediaProxy: string;
|
|
111
|
+
|
|
112
|
+
public constructor({ cdn, mediaProxy }: CDNOptions = {}) {
|
|
113
|
+
this.cdn = cdn ?? DefaultRestOptions.cdn;
|
|
114
|
+
this.mediaProxy = mediaProxy ?? DefaultRestOptions.mediaProxy;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generates an app asset URL for a client's asset.
|
|
119
|
+
*
|
|
120
|
+
* @param clientId - The client id that has the asset
|
|
121
|
+
* @param assetHash - The hash provided by Discord for this asset
|
|
122
|
+
* @param options - Optional options for the asset
|
|
123
|
+
*/
|
|
124
|
+
public appAsset(clientId: string, assetHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
125
|
+
return this.makeURL(`/app-assets/${clientId}/${assetHash}`, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Generates an app icon URL for a client's icon.
|
|
130
|
+
*
|
|
131
|
+
* @param clientId - The client id that has the icon
|
|
132
|
+
* @param iconHash - The hash provided by Discord for this icon
|
|
133
|
+
* @param options - Optional options for the icon
|
|
134
|
+
*/
|
|
135
|
+
public appIcon(clientId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
136
|
+
return this.makeURL(`/app-icons/${clientId}/${iconHash}`, options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generates an avatar URL, e.g. for a user or a webhook.
|
|
141
|
+
*
|
|
142
|
+
* @param id - The id that has the icon
|
|
143
|
+
* @param avatarHash - The hash provided by Discord for this avatar
|
|
144
|
+
* @param options - Optional options for the avatar
|
|
145
|
+
*/
|
|
146
|
+
public avatar(id: string, avatarHash: string, options?: Readonly<ImageURLOptions>): string {
|
|
147
|
+
return this.dynamicMakeURL(`/avatars/${id}/${avatarHash}`, avatarHash, options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generates a user avatar decoration preset URL.
|
|
152
|
+
*
|
|
153
|
+
* @param asset - The avatar decoration hash
|
|
154
|
+
*/
|
|
155
|
+
public avatarDecoration(asset: string): string {
|
|
156
|
+
return this.makeURL(`/avatar-decoration-presets/${asset}`, { extension: 'png' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generates a banner URL, e.g. for a user or a guild.
|
|
161
|
+
*
|
|
162
|
+
* @param id - The id that has the banner splash
|
|
163
|
+
* @param bannerHash - The hash provided by Discord for this banner
|
|
164
|
+
* @param options - Optional options for the banner
|
|
165
|
+
*/
|
|
166
|
+
public banner(id: string, bannerHash: string, options?: Readonly<ImageURLOptions>): string {
|
|
167
|
+
return this.dynamicMakeURL(`/banners/${id}/${bannerHash}`, bannerHash, options);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generates an icon URL for a channel, e.g. a group DM.
|
|
172
|
+
*
|
|
173
|
+
* @param channelId - The channel id that has the icon
|
|
174
|
+
* @param iconHash - The hash provided by Discord for this channel
|
|
175
|
+
* @param options - Optional options for the icon
|
|
176
|
+
*/
|
|
177
|
+
public channelIcon(channelId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
178
|
+
return this.makeURL(`/channel-icons/${channelId}/${iconHash}`, options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generates a default avatar URL
|
|
183
|
+
*
|
|
184
|
+
* @param index - The default avatar index
|
|
185
|
+
* @remarks
|
|
186
|
+
* To calculate the index for a user do `(userId >> 22) % 6`,
|
|
187
|
+
* or `discriminator % 5` if they're using the legacy username system.
|
|
188
|
+
*/
|
|
189
|
+
public defaultAvatar(index: number): string {
|
|
190
|
+
return this.makeURL(`/embed/avatars/${index}`, { extension: 'png' });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generates a discovery splash URL for a guild's discovery splash.
|
|
195
|
+
*
|
|
196
|
+
* @param guildId - The guild id that has the discovery splash
|
|
197
|
+
* @param splashHash - The hash provided by Discord for this splash
|
|
198
|
+
* @param options - Optional options for the splash
|
|
199
|
+
*/
|
|
200
|
+
public discoverySplash(guildId: string, splashHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
201
|
+
return this.makeURL(`/discovery-splashes/${guildId}/${splashHash}`, options);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Generates an emoji's URL.
|
|
206
|
+
*
|
|
207
|
+
* @param emojiId - The emoji id
|
|
208
|
+
* @param options - Optional options for the emoji
|
|
209
|
+
*/
|
|
210
|
+
public emoji(emojiId: string, options?: Readonly<EmojiURLOptions>): string {
|
|
211
|
+
return this.makeURL(`/emojis/${emojiId}`, options);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generates a guild member avatar URL.
|
|
216
|
+
*
|
|
217
|
+
* @param guildId - The id of the guild
|
|
218
|
+
* @param userId - The id of the user
|
|
219
|
+
* @param avatarHash - The hash provided by Discord for this avatar
|
|
220
|
+
* @param options - Optional options for the avatar
|
|
221
|
+
*/
|
|
222
|
+
public guildMemberAvatar(
|
|
223
|
+
guildId: string,
|
|
224
|
+
userId: string,
|
|
225
|
+
avatarHash: string,
|
|
226
|
+
options?: Readonly<ImageURLOptions>,
|
|
227
|
+
): string {
|
|
228
|
+
return this.dynamicMakeURL(`/guilds/${guildId}/users/${userId}/avatars/${avatarHash}`, avatarHash, options);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generates a guild member banner URL.
|
|
233
|
+
*
|
|
234
|
+
* @param guildId - The id of the guild
|
|
235
|
+
* @param userId - The id of the user
|
|
236
|
+
* @param bannerHash - The hash provided by Discord for this banner
|
|
237
|
+
* @param options - Optional options for the banner
|
|
238
|
+
*/
|
|
239
|
+
public guildMemberBanner(
|
|
240
|
+
guildId: string,
|
|
241
|
+
userId: string,
|
|
242
|
+
bannerHash: string,
|
|
243
|
+
options?: Readonly<ImageURLOptions>,
|
|
244
|
+
): string {
|
|
245
|
+
return this.dynamicMakeURL(`/guilds/${guildId}/users/${userId}/banners/${bannerHash}`, bannerHash, options);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generates an icon URL, e.g. for a guild.
|
|
250
|
+
*
|
|
251
|
+
* @param id - The id that has the icon splash
|
|
252
|
+
* @param iconHash - The hash provided by Discord for this icon
|
|
253
|
+
* @param options - Optional options for the icon
|
|
254
|
+
*/
|
|
255
|
+
public icon(id: string, iconHash: string, options?: Readonly<ImageURLOptions>): string {
|
|
256
|
+
return this.dynamicMakeURL(`/icons/${id}/${iconHash}`, iconHash, options);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generates a URL for the icon of a role
|
|
261
|
+
*
|
|
262
|
+
* @param roleId - The id of the role that has the icon
|
|
263
|
+
* @param roleIconHash - The hash provided by Discord for this role icon
|
|
264
|
+
* @param options - Optional options for the role icon
|
|
265
|
+
*/
|
|
266
|
+
public roleIcon(roleId: string, roleIconHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
267
|
+
return this.makeURL(`/role-icons/${roleId}/${roleIconHash}`, options);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generates a guild invite splash URL for a guild's invite splash.
|
|
272
|
+
*
|
|
273
|
+
* @param guildId - The guild id that has the invite splash
|
|
274
|
+
* @param splashHash - The hash provided by Discord for this splash
|
|
275
|
+
* @param options - Optional options for the splash
|
|
276
|
+
*/
|
|
277
|
+
public splash(guildId: string, splashHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
278
|
+
return this.makeURL(`/splashes/${guildId}/${splashHash}`, options);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generates a sticker URL.
|
|
283
|
+
*
|
|
284
|
+
* @param stickerId - The sticker id
|
|
285
|
+
* @param extension - The extension of the sticker
|
|
286
|
+
* @privateRemarks
|
|
287
|
+
* Stickers cannot have a `.webp` extension, so we default to a `.png`.
|
|
288
|
+
* Sticker GIFs do not use the CDN base URL.
|
|
289
|
+
*/
|
|
290
|
+
public sticker(stickerId: string, extension: StickerExtension = 'png'): string {
|
|
291
|
+
return this.makeURL(`/stickers/${stickerId}`, {
|
|
292
|
+
allowedExtensions: ALLOWED_STICKER_EXTENSIONS,
|
|
293
|
+
base: extension === 'gif' ? this.mediaProxy : this.cdn,
|
|
294
|
+
extension,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Generates a sticker pack banner URL.
|
|
300
|
+
*
|
|
301
|
+
* @param bannerId - The banner id
|
|
302
|
+
* @param options - Optional options for the banner
|
|
303
|
+
*/
|
|
304
|
+
public stickerPackBanner(bannerId: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
305
|
+
return this.makeURL(`/app-assets/710982414301790216/store/${bannerId}`, options);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Generates a team icon URL for a team's icon.
|
|
310
|
+
*
|
|
311
|
+
* @param teamId - The team id that has the icon
|
|
312
|
+
* @param iconHash - The hash provided by Discord for this icon
|
|
313
|
+
* @param options - Optional options for the icon
|
|
314
|
+
*/
|
|
315
|
+
public teamIcon(teamId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
316
|
+
return this.makeURL(`/team-icons/${teamId}/${iconHash}`, options);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generates a cover image for a guild scheduled event.
|
|
321
|
+
*
|
|
322
|
+
* @param scheduledEventId - The scheduled event id
|
|
323
|
+
* @param coverHash - The hash provided by discord for this cover image
|
|
324
|
+
* @param options - Optional options for the cover image
|
|
325
|
+
*/
|
|
326
|
+
public guildScheduledEventCover(
|
|
327
|
+
scheduledEventId: string,
|
|
328
|
+
coverHash: string,
|
|
329
|
+
options?: Readonly<BaseImageURLOptions>,
|
|
330
|
+
): string {
|
|
331
|
+
return this.makeURL(`/guild-events/${scheduledEventId}/${coverHash}`, options);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Generates a URL for a soundboard sound.
|
|
336
|
+
*
|
|
337
|
+
* @param soundId - The soundboard sound id
|
|
338
|
+
*/
|
|
339
|
+
public soundboardSound(soundId: string): string {
|
|
340
|
+
return `${this.cdn}${CDNRoutes.soundboardSound(soundId)}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generates a URL for a guild tag badge.
|
|
345
|
+
*
|
|
346
|
+
* @param guildId - The guild id
|
|
347
|
+
* @param badgeHash - The hash of the badge
|
|
348
|
+
* @param options - Optional options for the badge
|
|
349
|
+
*/
|
|
350
|
+
public guildTagBadge(guildId: string, badgeHash: string, options?: Readonly<BaseImageURLOptions>): string {
|
|
351
|
+
return this.makeURL(`/guild-tag-badges/${guildId}/${badgeHash}`, options);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`.
|
|
356
|
+
*
|
|
357
|
+
* @param route - The base cdn route
|
|
358
|
+
* @param hash - The hash provided by Discord for this icon
|
|
359
|
+
* @param options - Optional options for the link
|
|
360
|
+
*/
|
|
361
|
+
private dynamicMakeURL(
|
|
362
|
+
route: string,
|
|
363
|
+
hash: string,
|
|
364
|
+
{ forceStatic = false, ...options }: Readonly<ImageURLOptions> = {},
|
|
365
|
+
): string {
|
|
366
|
+
return this.makeURL(route, !forceStatic && hash.startsWith('a_') ? { ...options, animated: true } : options);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Constructs the URL for the resource
|
|
371
|
+
*
|
|
372
|
+
* @param route - The base cdn route
|
|
373
|
+
* @param options - The extension/size options for the link
|
|
374
|
+
*/
|
|
375
|
+
private makeURL(
|
|
376
|
+
route: string,
|
|
377
|
+
{
|
|
378
|
+
allowedExtensions = ALLOWED_EXTENSIONS,
|
|
379
|
+
base = this.cdn,
|
|
380
|
+
extension = 'webp',
|
|
381
|
+
size,
|
|
382
|
+
animated,
|
|
383
|
+
}: Readonly<MakeURLOptions> = {},
|
|
384
|
+
): string {
|
|
385
|
+
extension = String(extension).toLowerCase();
|
|
386
|
+
|
|
387
|
+
if (!allowedExtensions.includes(extension)) {
|
|
388
|
+
throw new RangeError(`Invalid extension provided: ${extension}\nMust be one of: ${allowedExtensions.join(', ')}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (size && !ALLOWED_SIZES.includes(size)) {
|
|
392
|
+
throw new RangeError(`Invalid size provided: ${size}\nMust be one of: ${ALLOWED_SIZES.join(', ')}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const url = new URL(`${base}${route}.${extension}`);
|
|
396
|
+
|
|
397
|
+
if (animated !== undefined) {
|
|
398
|
+
url.searchParams.set('animated', String(animated));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (size) {
|
|
402
|
+
url.searchParams.set('size', String(size));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return url.toString();
|
|
406
|
+
}
|
|
407
|
+
}
|