@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/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
+ }