Skip to content

Commit

Permalink
fix: Correct base path for GIF stickers (#10330)
Browse files Browse the repository at this point in the history
* fix: correct base path for GIF stickers

* test: add sticker GIF

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
Jiralite and kodiakhq[bot] authored Jun 7, 2024
1 parent 7f60a8f commit 599ad3e
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 34 deletions.
61 changes: 33 additions & 28 deletions packages/rest/__tests__/CDN.test.ts
Original file line number Diff line number Diff line change
@@ -1,114 +1,119 @@
import { test, expect } from 'vitest';
import { CDN } from '../src/index.js';

const base = 'https://discord.com';
const baseCDN = 'https://cdn-discord.com';
const baseMedia = 'https://media-discord.com';
const id = '123456';
const hash = 'abcdef';
const animatedHash = 'a_bcdef';
const defaultAvatar = 1_234 % 5;

const cdn = new CDN(base);
const cdn = new CDN(baseCDN, baseMedia);

test('appAsset default', () => {
expect(cdn.appAsset(id, hash)).toEqual(`${base}/app-assets/${id}/${hash}.webp`);
expect(cdn.appAsset(id, hash)).toEqual(`${baseCDN}/app-assets/${id}/${hash}.webp`);
});

test('appIcon default', () => {
expect(cdn.appIcon(id, hash)).toEqual(`${base}/app-icons/${id}/${hash}.webp`);
expect(cdn.appIcon(id, hash)).toEqual(`${baseCDN}/app-icons/${id}/${hash}.webp`);
});

test('avatar default', () => {
expect(cdn.avatar(id, hash)).toEqual(`${base}/avatars/${id}/${hash}.webp`);
expect(cdn.avatar(id, hash)).toEqual(`${baseCDN}/avatars/${id}/${hash}.webp`);
});

test('avatar dynamic-animated', () => {
expect(cdn.avatar(id, animatedHash)).toEqual(`${base}/avatars/${id}/${animatedHash}.gif`);
expect(cdn.avatar(id, animatedHash)).toEqual(`${baseCDN}/avatars/${id}/${animatedHash}.gif`);
});

test('avatar dynamic-not-animated', () => {
expect(cdn.avatar(id, hash)).toEqual(`${base}/avatars/${id}/${hash}.webp`);
expect(cdn.avatar(id, hash)).toEqual(`${baseCDN}/avatars/${id}/${hash}.webp`);
});

test('avatar decoration default', () => {
expect(cdn.avatarDecoration(id, hash)).toEqual(`${base}/avatar-decorations/${id}/${hash}.webp`);
expect(cdn.avatarDecoration(id, hash)).toEqual(`${baseCDN}/avatar-decorations/${id}/${hash}.webp`);
});

test('avatar decoration preset', () => {
expect(cdn.avatarDecoration(hash)).toEqual(`${base}/avatar-decoration-presets/${hash}.png`);
expect(cdn.avatarDecoration(hash)).toEqual(`${baseCDN}/avatar-decoration-presets/${hash}.png`);
});

test('banner default', () => {
expect(cdn.banner(id, hash)).toEqual(`${base}/banners/${id}/${hash}.webp`);
expect(cdn.banner(id, hash)).toEqual(`${baseCDN}/banners/${id}/${hash}.webp`);
});

test('channelIcon default', () => {
expect(cdn.channelIcon(id, hash)).toEqual(`${base}/channel-icons/${id}/${hash}.webp`);
expect(cdn.channelIcon(id, hash)).toEqual(`${baseCDN}/channel-icons/${id}/${hash}.webp`);
});

test('defaultAvatar default', () => {
expect(cdn.defaultAvatar(defaultAvatar)).toEqual(`${base}/embed/avatars/${defaultAvatar}.png`);
expect(cdn.defaultAvatar(defaultAvatar)).toEqual(`${baseCDN}/embed/avatars/${defaultAvatar}.png`);
});

test('discoverySplash default', () => {
expect(cdn.discoverySplash(id, hash)).toEqual(`${base}/discovery-splashes/${id}/${hash}.webp`);
expect(cdn.discoverySplash(id, hash)).toEqual(`${baseCDN}/discovery-splashes/${id}/${hash}.webp`);
});

test('emoji default', () => {
expect(cdn.emoji(id)).toEqual(`${base}/emojis/${id}.webp`);
expect(cdn.emoji(id)).toEqual(`${baseCDN}/emojis/${id}.webp`);
});

test('emoji gif', () => {
expect(cdn.emoji(id, 'gif')).toEqual(`${base}/emojis/${id}.gif`);
expect(cdn.emoji(id, 'gif')).toEqual(`${baseCDN}/emojis/${id}.gif`);
});

test('guildMemberAvatar default', () => {
expect(cdn.guildMemberAvatar(id, id, hash)).toEqual(`${base}/guilds/${id}/users/${id}/avatars/${hash}.webp`);
expect(cdn.guildMemberAvatar(id, id, hash)).toEqual(`${baseCDN}/guilds/${id}/users/${id}/avatars/${hash}.webp`);
});

test('guildMemberAvatar dynamic-animated', () => {
expect(cdn.guildMemberAvatar(id, id, animatedHash)).toEqual(
`${base}/guilds/${id}/users/${id}/avatars/${animatedHash}.gif`,
`${baseCDN}/guilds/${id}/users/${id}/avatars/${animatedHash}.gif`,
);
});

test('guildMemberAvatar dynamic-not-animated', () => {
expect(cdn.guildMemberAvatar(id, id, hash)).toEqual(`${base}/guilds/${id}/users/${id}/avatars/${hash}.webp`);
expect(cdn.guildMemberAvatar(id, id, hash)).toEqual(`${baseCDN}/guilds/${id}/users/${id}/avatars/${hash}.webp`);
});

test('guildScheduledEventCover default', () => {
expect(cdn.guildScheduledEventCover(id, hash)).toEqual(`${base}/guild-events/${id}/${hash}.webp`);
expect(cdn.guildScheduledEventCover(id, hash)).toEqual(`${baseCDN}/guild-events/${id}/${hash}.webp`);
});

test('icon default', () => {
expect(cdn.icon(id, hash)).toEqual(`${base}/icons/${id}/${hash}.webp`);
expect(cdn.icon(id, hash)).toEqual(`${baseCDN}/icons/${id}/${hash}.webp`);
});

test('icon dynamic-animated', () => {
expect(cdn.icon(id, animatedHash)).toEqual(`${base}/icons/${id}/${animatedHash}.gif`);
expect(cdn.icon(id, animatedHash)).toEqual(`${baseCDN}/icons/${id}/${animatedHash}.gif`);
});

test('icon dynamic-not-animated', () => {
expect(cdn.icon(id, hash)).toEqual(`${base}/icons/${id}/${hash}.webp`);
expect(cdn.icon(id, hash)).toEqual(`${baseCDN}/icons/${id}/${hash}.webp`);
});

test('role icon default', () => {
expect(cdn.roleIcon(id, hash)).toEqual(`${base}/role-icons/${id}/${hash}.webp`);
expect(cdn.roleIcon(id, hash)).toEqual(`${baseCDN}/role-icons/${id}/${hash}.webp`);
});

test('splash default', () => {
expect(cdn.splash(id, hash)).toEqual(`${base}/splashes/${id}/${hash}.webp`);
expect(cdn.splash(id, hash)).toEqual(`${baseCDN}/splashes/${id}/${hash}.webp`);
});

test('sticker default', () => {
expect(cdn.sticker(id)).toEqual(`${base}/stickers/${id}.png`);
expect(cdn.sticker(id)).toEqual(`${baseCDN}/stickers/${id}.png`);
});

test('sticker GIF', () => {
expect(cdn.sticker(id, 'gif')).toEqual(`${baseMedia}/stickers/${id}.gif`);
});

test('stickerPackBanner default', () => {
expect(cdn.stickerPackBanner(id)).toEqual(`${base}/app-assets/710982414301790216/store/${id}.webp`);
expect(cdn.stickerPackBanner(id)).toEqual(`${baseCDN}/app-assets/710982414301790216/store/${id}.webp`);
});

test('teamIcon default', () => {
expect(cdn.teamIcon(id, hash)).toEqual(`${base}/team-icons/${id}/${hash}.webp`);
expect(cdn.teamIcon(id, hash)).toEqual(`${baseCDN}/team-icons/${id}/${hash}.webp`);
});

test('makeURL throws on invalid size', () => {
Expand All @@ -122,5 +127,5 @@ test('makeURL throws on invalid extension', () => {
});

test('makeURL valid size', () => {
expect(cdn.avatar(id, animatedHash, { size: 512 })).toEqual(`${base}/avatars/${id}/${animatedHash}.gif?size=512`);
expect(cdn.avatar(id, animatedHash, { size: 512 })).toEqual(`${baseCDN}/avatars/${id}/${animatedHash}.gif?size=512`);
});
29 changes: 24 additions & 5 deletions packages/rest/src/lib/CDN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface MakeURLOptions {
* The allowed extensions that can be used
*/
allowedExtensions?: readonly string[];
/**
* The base URL.
*
* @defaultValue `DefaultRestOptions.cdn`
*/
base?: string;
/**
* The extension to use for the image URL
*
Expand All @@ -62,7 +68,10 @@ export interface MakeURLOptions {
* The CDN link builder
*/
export class CDN {
public constructor(private readonly base: string = DefaultRestOptions.cdn) {}
public constructor(
private readonly cdn: string = DefaultRestOptions.cdn,
private readonly mediaProxy: string = DefaultRestOptions.mediaProxy,
) {}

/**
* Generates an app asset URL for a client's asset.
Expand Down Expand Up @@ -287,10 +296,15 @@ export class CDN {
* @param stickerId - The sticker id
* @param extension - The extension of the sticker
* @privateRemarks
* Stickers cannot have a `.webp` extension, so we default to a `.png`
* Stickers cannot have a `.webp` extension, so we default to a `.png`.
* Sticker GIFs do not use the CDN base URL.
*/
public sticker(stickerId: string, extension: StickerExtension = 'png'): string {
return this.makeURL(`/stickers/${stickerId}`, { allowedExtensions: ALLOWED_STICKER_EXTENSIONS, extension });
return this.makeURL(`/stickers/${stickerId}`, {
allowedExtensions: ALLOWED_STICKER_EXTENSIONS,
base: extension === 'gif' ? this.mediaProxy : this.cdn,
extension,
});
}

/**
Expand Down Expand Up @@ -352,7 +366,12 @@ export class CDN {
*/
private makeURL(
route: string,
{ allowedExtensions = ALLOWED_EXTENSIONS, extension = 'webp', size }: Readonly<MakeURLOptions> = {},
{
allowedExtensions = ALLOWED_EXTENSIONS,
base = this.cdn,
extension = 'webp',
size,
}: Readonly<MakeURLOptions> = {},
): string {
// eslint-disable-next-line no-param-reassign
extension = String(extension).toLowerCase();
Expand All @@ -365,7 +384,7 @@ export class CDN {
throw new RangeError(`Invalid size provided: ${size}\nMust be one of: ${ALLOWED_SIZES.join(', ')}`);
}

const url = new URL(`${this.base}${route}.${extension}`);
const url = new URL(`${base}${route}.${extension}`);

if (size) {
url.searchParams.set('size', String(size));
Expand Down
2 changes: 1 addition & 1 deletion packages/rest/src/lib/REST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class REST extends AsyncEventEmitter<RestEvents> {

public constructor(options: Partial<RESTOptions> = {}) {
super();
this.cdn = new CDN(options.cdn ?? DefaultRestOptions.cdn);
this.cdn = new CDN(options.cdn ?? DefaultRestOptions.cdn, options.mediaProxy ?? DefaultRestOptions.mediaProxy);
this.options = { ...DefaultRestOptions, ...options };
this.globalRemaining = Math.max(1, this.options.globalRequestsPerSecond);
this.agent = options.agent ?? null;
Expand Down
1 change: 1 addition & 0 deletions packages/rest/src/lib/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const DefaultRestOptions = {
async makeRequest(...args): Promise<ResponseLike> {
return getDefaultStrategy()(...args);
},
mediaProxy: 'https://media.discordapp.net',
} as const satisfies Required<RESTOptions>;

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/rest/src/lib/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export interface RESTOptions {
* For example, to use global fetch, simply provide `makeRequest: fetch`
*/
makeRequest(url: string, init: RequestInit): Promise<ResponseLike>;
/**
* The media proxy path
*
* @defaultValue `'https://media.discordapp.net'`
*/
mediaProxy: string;
/**
* The extra offset to add to rate limits in milliseconds
*
Expand Down

0 comments on commit 599ad3e

Please sign in to comment.