feat(user)!: add more getters for user transformer (#4661)

* feat: enhance user tag getter

* feat(user): add `0000` condition and better jsdoc for tag

* chore(user): use this keyword instead of destructuring assignment

* fix(user): correct isLegacy logic

* feat(images): add ImageOptions

* refactor(images)!: split avatarUrl into more specific functions and add displayAvatarUrl

* feat(user)!: add more useful getters for user

* fix: update tests for images

* chore: using n suffix for bigint instead of converting to bigint

Co-authored-by: Fleny <Fleny113@outlook.com>

* fix: update dependencies for avatarUrl

Co-authored-by: Fleny <Fleny113@outlook.com>

* fix(user): correct the avatarUrl second parameter to avatar hash

Co-authored-by: Fleny <Fleny113@outlook.com>

* chore: undo unnecessary // from auto comment hotkey

Co-authored-by: Fleny <Fleny113@outlook.com>

---------

Co-authored-by: Fleny <Fleny113@outlook.com>
This commit is contained in:
Louis Johnson
2025-12-31 17:46:11 +01:00
committed by GitHub
co-authored by Fleny
parent 875a075f93
commit 288904fef9
5 changed files with 135 additions and 165 deletions
+56 -147
View File
@@ -1,6 +1,11 @@
import { type BigString, type GetGuildWidgetImageQuery, type ImageFormat, type ImageSize, StickerFormatTypes } from '@discordeno/types'
import { iconBigintToHash } from './hash.js'
export interface ImageOptions {
size?: ImageSize
format?: ImageFormat
}
/** Help format an image url. */
export function formatImageUrl(url: string, size: ImageSize = 128, format?: ImageFormat): string {
return `${url}.${format ?? (url.includes('/a_') ? 'gif' : 'webp')}?size=${size}`
@@ -25,26 +30,43 @@ export function emojiUrl(emojiId: BigString, animated = false, format: ImageForm
* Builds a URL to a user's avatar stored in the Discord CDN.
*
* @param userId - The ID of the user to get the avatar of.
* @param discriminator - The user's discriminator. (4-digit tag after the hashtag.)
* @param avatar - The user's avatar hash.
* @param options - The parameters for the building of the URL.
* @returns The link to the resource.
* @returns The user avatar as a URL.
*/
export function avatarUrl(
userId: BigString,
discriminator: string,
options?: {
avatar: BigString | undefined
size?: ImageSize
format?: ImageFormat
},
): string {
return options?.avatar
? formatImageUrl(
`https://cdn.discordapp.com/avatars/${userId}/${typeof options.avatar === 'string' ? options.avatar : iconBigintToHash(options.avatar)}`,
options?.size ?? 128,
options?.format,
)
: `https://cdn.discordapp.com/embed/avatars/${discriminator === '0' ? (BigInt(userId) >> BigInt(22)) % BigInt(6) : Number(discriminator) % 5}.png`
export function avatarUrl(userId: BigString, avatar: BigString, options?: ImageOptions): string {
return formatImageUrl(
`https://cdn.discordapp.com/avatars/${userId}/${typeof avatar === 'string' ? avatar : iconBigintToHash(avatar)}`,
options?.size ?? 128,
options?.format,
)
}
/**
* Builds a URL to a user's default avatar stored in the Discord CDN.
*
* @param userId - The ID of the user to get the avatar of.
* @param discriminator - The user's discriminator. (4-digit tag after the hashtag.)
* @returns The user default avatar as an URL.
*/
export function defaultAvatarUrl(userId: BigString, discriminator: string) {
const isLegacy = discriminator === '0' || discriminator === '0000'
const index = isLegacy ? (BigInt(userId) >> 22n) % 6n : Number(discriminator) % 5
return `https://cdn.discordapp.com/embed/avatars/${index}.png`
}
/**
* Builds a URL to a user's display avatar stored in the Discord CDN.
*
* @param userId - The ID of the user to get the avatar of.
* @param discriminator - The user's discriminator. (4-digit tag after the hashtag.)
* @param avatar - The user's avatar hash.
* @param options - The parameters for the building of the URL.
* @returns The user display avatar as an URL.
*/
export function displayAvatarUrl(userId: BigString, discriminator: string, avatar: BigString | undefined, options?: ImageOptions): string {
return avatar ? avatarUrl(userId, avatar, options) : defaultAvatarUrl(userId, discriminator)
}
export function avatarDecorationUrl(avatarDecoration: BigString): string {
@@ -60,14 +82,7 @@ export function avatarDecorationUrl(avatarDecoration: BigString): string {
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no banner has not been set.
*/
export function bannerUrl(
userId: BigString,
options?: {
banner?: BigString
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function bannerUrl(userId: BigString, options?: ImageOptions & { banner?: BigString }): string | undefined {
return options?.banner
? formatImageUrl(
`https://cdn.discordapp.com/banners/${userId}/${typeof options.banner === 'string' ? options.banner : iconBigintToHash(options.banner)}`,
@@ -84,14 +99,7 @@ export function bannerUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no banner has been set.
*/
export function guildBannerUrl(
guildId: BigString,
options: {
banner?: BigString
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildBannerUrl(guildId: BigString, options: ImageOptions & { banner?: BigString }): string | undefined {
return options.banner
? formatImageUrl(
`https://cdn.discordapp.com/banners/${guildId}/${typeof options.banner === 'string' ? options.banner : iconBigintToHash(options.banner)}`,
@@ -109,14 +117,7 @@ export function guildBannerUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no banner has been set.
*/
export function guildIconUrl(
guildId: BigString,
imageHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildIconUrl(guildId: BigString, imageHash: BigString | undefined, options?: ImageOptions): string | undefined {
return imageHash
? formatImageUrl(
`https://cdn.discordapp.com/icons/${guildId}/${typeof imageHash === 'string' ? imageHash : iconBigintToHash(imageHash)}`,
@@ -134,14 +135,7 @@ export function guildIconUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if the guild does not have a splash image set.
*/
export function guildSplashUrl(
guildId: BigString,
imageHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildSplashUrl(guildId: BigString, imageHash: BigString | undefined, options?: ImageOptions): string | undefined {
return imageHash
? formatImageUrl(
`https://cdn.discordapp.com/splashes/${guildId}/${typeof imageHash === 'string' ? imageHash : iconBigintToHash(imageHash)}`,
@@ -159,14 +153,7 @@ export function guildSplashUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if the guild does not have a splash image set.
*/
export function guildDiscoverySplashUrl(
guildId: BigString,
imageHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildDiscoverySplashUrl(guildId: BigString, imageHash: BigString | undefined, options?: ImageOptions): string | undefined {
return imageHash
? formatImageUrl(
`https://cdn.discordapp.com/discovery-splashes/${guildId}/${typeof imageHash === 'string' ? imageHash : iconBigintToHash(imageHash)}`,
@@ -183,14 +170,7 @@ export function guildDiscoverySplashUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function guildScheduledEventCoverUrl(
eventId: BigString,
options: {
cover?: BigString
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildScheduledEventCoverUrl(eventId: BigString, options: ImageOptions & { cover?: BigString }): string | undefined {
return options.cover
? formatImageUrl(
`https://cdn.discordapp.com/guild-events/${eventId}/${typeof options.cover === 'string' ? options.cover : iconBigintToHash(options.cover)}`,
@@ -225,15 +205,7 @@ export function getWidgetImageUrl(guildId: BigString, options?: GetGuildWidgetIm
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no banner has been set.
*/
export function memberAvatarUrl(
guildId: BigString,
userId: BigString,
options?: {
avatar?: BigString
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function memberAvatarUrl(guildId: BigString, userId: BigString, options?: ImageOptions & { avatar?: BigString }): string | undefined {
return options?.avatar
? formatImageUrl(
`https://cdn.discordapp.com/guilds/${guildId}/users/${userId}/avatars/${
@@ -253,15 +225,7 @@ export function memberAvatarUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no banner has been set.
*/
export function memberBannerUrl(
guildId: BigString,
userId: BigString,
options?: {
banner?: BigString
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function memberBannerUrl(guildId: BigString, userId: BigString, options?: ImageOptions & { banner?: BigString }): string | undefined {
return options?.banner
? formatImageUrl(
`https://cdn.discordapp.com/guilds/${guildId}/users/${userId}/banners/${
@@ -281,14 +245,7 @@ export function memberBannerUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`
*/
export function applicationIconUrl(
applicationId: BigString,
iconHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function applicationIconUrl(applicationId: BigString, iconHash: BigString | undefined, options?: ImageOptions): string | undefined {
return iconHash
? formatImageUrl(
`https://cdn.discordapp.com/app-icons/${applicationId}/${typeof iconHash === 'string' ? iconHash : iconBigintToHash(iconHash)}`,
@@ -306,14 +263,7 @@ export function applicationIconUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function applicationCoverUrl(
applicationId: BigString,
coverHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function applicationCoverUrl(applicationId: BigString, coverHash: BigString | undefined, options?: ImageOptions): string | undefined {
return coverHash
? formatImageUrl(
`https://cdn.discordapp.com/app-icons/${applicationId}/${typeof coverHash === 'string' ? coverHash : iconBigintToHash(coverHash)}`,
@@ -331,14 +281,7 @@ export function applicationCoverUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function applicationAssetUrl(
applicationId: BigString,
assetId: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function applicationAssetUrl(applicationId: BigString, assetId: BigString | undefined, options?: ImageOptions): string | undefined {
return assetId
? formatImageUrl(
`https://cdn.discordapp.com/app-icons/${applicationId}/${typeof assetId === 'string' ? assetId : iconBigintToHash(assetId)}`,
@@ -355,13 +298,7 @@ export function applicationAssetUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function stickerPackBannerUrl(
bannerAssetId: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function stickerPackBannerUrl(bannerAssetId: BigString | undefined, options?: ImageOptions): string | undefined {
return bannerAssetId
? formatImageUrl(
`https://cdn.discordapp.com/app-assets/710982414301790216/store/${
@@ -380,14 +317,7 @@ export function stickerPackBannerUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function stickerUrl(
stickerId: BigString | number,
options?: {
size?: ImageSize
format?: ImageFormat
type?: StickerFormatTypes
},
): string | undefined {
export function stickerUrl(stickerId: BigString | number, options?: ImageOptions & { type?: StickerFormatTypes }): string | undefined {
if (!stickerId) return
const url =
@@ -406,14 +336,7 @@ export function stickerUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function teamIconUrl(
teamId: BigString,
iconHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function teamIconUrl(teamId: BigString, iconHash: BigString | undefined, options?: ImageOptions): string | undefined {
return iconHash
? formatImageUrl(
`https://cdn.discordapp.com/team-icons/${teamId}/store/${typeof iconHash === 'string' ? iconHash : iconBigintToHash(iconHash)}`,
@@ -431,14 +354,7 @@ export function teamIconUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined`.
*/
export function roleIconUrl(
roleId: BigString,
iconHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function roleIconUrl(roleId: BigString, iconHash: BigString | undefined, options?: ImageOptions): string | undefined {
return iconHash
? formatImageUrl(
`https://cdn.discordapp.com/role-icons/${roleId}/${typeof iconHash === 'string' ? iconHash : iconBigintToHash(iconHash)}`,
@@ -456,14 +372,7 @@ export function roleIconUrl(
* @param options - The parameters for the building of the URL.
* @returns The link to the resource or `undefined` if no badge has been set.
*/
export function guildTagBadgeUrl(
guildId: BigString,
badgeHash: BigString | undefined,
options?: {
size?: ImageSize
format?: ImageFormat
},
): string | undefined {
export function guildTagBadgeUrl(guildId: BigString, badgeHash: BigString | undefined, options?: ImageOptions): string | undefined {
if (badgeHash === undefined) return undefined
return formatImageUrl(`https://cdn.discordapp.com/guild-tag-badges/${guildId}/${badgeHash}`, options?.size ?? 128, options?.format)
+38 -16
View File
@@ -1,6 +1,16 @@
import { expect } from 'chai'
import { describe, it } from 'mocha'
import { avatarUrl, emojiUrl, formatImageUrl, getWidgetImageUrl, guildBannerUrl, guildIconUrl, guildSplashUrl } from '../src/images.js'
import {
avatarUrl,
defaultAvatarUrl,
displayAvatarUrl,
emojiUrl,
formatImageUrl,
getWidgetImageUrl,
guildBannerUrl,
guildIconUrl,
guildSplashUrl,
} from '../src/images.js'
describe('images.ts', () => {
describe('formatImageUrl function', () => {
@@ -55,27 +65,39 @@ describe('images.ts', () => {
describe('avatarUrl function', () => {
it('will return the url for given avatar icon hash', () => {
expect(
avatarUrl('207324334904049664', '9130', {
avatar: 'db26a6fb924c985f66b79364cf5797b7',
}),
).to.equal('https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128')
expect(avatarUrl('207324334904049664', 'db26a6fb924c985f66b79364cf5797b7')).to.equal(
'https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128',
)
})
it('will return the url for given avatar icon bigint', () => {
expect(
avatarUrl('207324334904049664', '9130', {
avatar: 4034407661299384404326332419647968090039n,
}),
).to.equal('https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128')
expect(avatarUrl('207324334904049664', 4034407661299384404326332419647968090039n)).to.equal(
'https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128',
)
})
})
describe('defaultAvatarUrl function', () => {
it('will return the url for default avatar', () => {
expect(defaultAvatarUrl('207324334904049664', '9130')).to.equal('https://cdn.discordapp.com/embed/avatars/0.png')
})
})
describe('displayAvatarUrl function', () => {
it('will return the url for given avatar icon hash', () => {
expect(displayAvatarUrl('207324334904049664', '9130', 'db26a6fb924c985f66b79364cf5797b7')).to.equal(
'https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128',
)
})
it('will return the url for given avatar icon bigint', () => {
expect(displayAvatarUrl('207324334904049664', '9130', 4034407661299384404326332419647968090039n)).to.equal(
'https://cdn.discordapp.com/avatars/207324334904049664/db26a6fb924c985f66b79364cf5797b7.webp?size=128',
)
})
it('will return the url for default avatar', () => {
expect(
avatarUrl('207324334904049664', '9130', {
avatar: undefined,
}),
).to.equal('https://cdn.discordapp.com/embed/avatars/0.png')
expect(displayAvatarUrl('207324334904049664', '9130', undefined)).to.equal('https://cdn.discordapp.com/embed/avatars/0.png')
})
})