import { ALLOWED_EXTENSIONS, ALLOWED_SIZES, ALLOWED_STICKER_EXTENSIONS, DefaultRestOptions, ImageExtension, ImageSize, StickerExtension, } from './utils/constants'; /** * The options used for image URLs */ export interface BaseImageURLOptions { /** * The extension to use for the image URL * @default 'webp' */ extension?: ImageExtension; /** * The size specified in the image URL */ size?: ImageSize; } /** * The options used for image URLs with animated content */ export interface ImageURLOptions extends BaseImageURLOptions { /** * Whether or not to prefer the static version of an image asset. */ forceStatic?: boolean; } /** * The options to use when making a CDN URL */ export interface MakeURLOptions { /** * The extension to use for the image URL * @default 'webp' */ extension?: string | undefined; /** * The size specified in the image URL */ size?: ImageSize; /** * The allowed extensions that can be used */ allowedExtensions?: ReadonlyArray; } /** * The CDN link builder */ export class CDN { public constructor(private readonly base: string = DefaultRestOptions.cdn) {} /** * Generates an app asset URL for a client's asset. * @param clientId The client id that has the asset * @param assetHash The hash provided by Discord for this asset * @param options Optional options for the asset */ public appAsset(clientId: string, assetHash: string, options?: Readonly): string { return this.makeURL(`/app-assets/${clientId}/${assetHash}`, options); } /** * Generates an app icon URL for a client's icon. * @param clientId The client id that has the icon * @param iconHash The hash provided by Discord for this icon * @param options Optional options for the icon */ public appIcon(clientId: string, iconHash: string, options?: Readonly): string { return this.makeURL(`/app-icons/${clientId}/${iconHash}`, options); } /** * Generates an avatar URL, e.g. for a user or a webhook. * @param id The id that has the icon * @param avatarHash The hash provided by Discord for this avatar * @param options Optional options for the avatar */ public avatar(id: string, avatarHash: string, options?: Readonly): string { return this.dynamicMakeURL(`/avatars/${id}/${avatarHash}`, avatarHash, options); } /** * Generates a banner URL, e.g. for a user or a guild. * @param id The id that has the banner splash * @param bannerHash The hash provided by Discord for this banner * @param options Optional options for the banner */ public banner(id: string, bannerHash: string, options?: Readonly): string { return this.dynamicMakeURL(`/banners/${id}/${bannerHash}`, bannerHash, options); } /** * Generates an icon URL for a channel, e.g. a group DM. * @param channelId The channel id that has the icon * @param iconHash The hash provided by Discord for this channel * @param options Optional options for the icon */ public channelIcon(channelId: string, iconHash: string, options?: Readonly): string { return this.makeURL(`/channel-icons/${channelId}/${iconHash}`, options); } /** * Generates the default avatar URL for a discriminator. * @param discriminator The discriminator modulo 5 */ public defaultAvatar(discriminator: number): string { return this.makeURL(`/embed/avatars/${discriminator}`, { extension: 'png' }); } /** * Generates a discovery splash URL for a guild's discovery splash. * @param guildId The guild id that has the discovery splash * @param splashHash The hash provided by Discord for this splash * @param options Optional options for the splash */ public discoverySplash(guildId: string, splashHash: string, options?: Readonly): string { return this.makeURL(`/discovery-splashes/${guildId}/${splashHash}`, options); } /** * Generates an emoji's URL for an emoji. * @param emojiId The emoji id * @param extension The extension of the emoji */ public emoji(emojiId: string, extension?: ImageExtension): string { return this.makeURL(`/emojis/${emojiId}`, { extension }); } /** * Generates a guild member avatar URL. * @param guildId The id of the guild * @param userId The id of the user * @param avatarHash The hash provided by Discord for this avatar * @param options Optional options for the avatar */ public guildMemberAvatar( guildId: string, userId: string, avatarHash: string, options?: Readonly, ): string { return this.dynamicMakeURL(`/guilds/${guildId}/users/${userId}/avatars/${avatarHash}`, avatarHash, options); } /** * Generates an icon URL, e.g. for a guild. * @param id The id that has the icon splash * @param iconHash The hash provided by Discord for this icon * @param options Optional options for the icon */ public icon(id: string, iconHash: string, options?: Readonly): string { return this.dynamicMakeURL(`/icons/${id}/${iconHash}`, iconHash, options); } /** * Generates a URL for the icon of a role * @param roleId The id of the role that has the icon * @param roleIconHash The hash provided by Discord for this role icon * @param options Optional options for the role icon */ public roleIcon(roleId: string, roleIconHash: string, options?: Readonly): string { return this.makeURL(`/role-icons/${roleId}/${roleIconHash}`, options); } /** * Generates a guild invite splash URL for a guild's invite splash. * @param guildId The guild id that has the invite splash * @param splashHash The hash provided by Discord for this splash * @param options Optional options for the splash */ public splash(guildId: string, splashHash: string, options?: Readonly): string { return this.makeURL(`/splashes/${guildId}/${splashHash}`, options); } /** * Generates a sticker URL. * @param stickerId The sticker id * @param extension The extension of the sticker */ public sticker(stickerId: string, extension?: StickerExtension): string { return this.makeURL(`/stickers/${stickerId}`, { allowedExtensions: ALLOWED_STICKER_EXTENSIONS, extension: extension ?? 'png', // Stickers cannot have a `.webp` extension, so we default to a `.png` }); } /** * Generates a sticker pack banner URL. * @param bannerId The banner id * @param options Optional options for the banner */ public stickerPackBanner(bannerId: string, options?: Readonly): string { return this.makeURL(`/app-assets/710982414301790216/store/${bannerId}`, options); } /** * Generates a team icon URL for a team's icon. * @param teamId The team id that has the icon * @param iconHash The hash provided by Discord for this icon * @param options Optional options for the icon */ public teamIcon(teamId: string, iconHash: string, options?: Readonly): string { return this.makeURL(`/team-icons/${teamId}/${iconHash}`, options); } /** * Generates a cover image for a guild scheduled event. * @param scheduledEventId The scheduled event id * @param coverHash The hash provided by discord for this cover image * @param options Optional options for the cover image */ public guildScheduledEventCover( scheduledEventId: string, coverHash: string, options?: Readonly, ): string { return this.makeURL(`/guild-events/${scheduledEventId}/${coverHash}`, options); } /** * Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`. * @param route The base cdn route * @param hash The hash provided by Discord for this icon * @param options Optional options for the link */ private dynamicMakeURL( route: string, hash: string, { forceStatic = false, ...options }: Readonly = {}, ): string { return this.makeURL(route, !forceStatic && hash.startsWith('a_') ? { ...options, extension: 'gif' } : options); } /** * Constructs the URL for the resource * @param route The base cdn route * @param options The extension/size options for the link */ private makeURL( route: string, { allowedExtensions = ALLOWED_EXTENSIONS, extension = 'webp', size }: Readonly = {}, ): string { extension = String(extension).toLowerCase(); if (!allowedExtensions.includes(extension)) { throw new RangeError(`Invalid extension provided: ${extension}\nMust be one of: ${allowedExtensions.join(', ')}`); } if (size && !ALLOWED_SIZES.includes(size)) { throw new RangeError(`Invalid size provided: ${size}\nMust be one of: ${ALLOWED_SIZES.join(', ')}`); } const url = new URL(`${this.base}${route}.${extension}`); if (size) { url.searchParams.set('size', String(size)); } return url.toString(); } }