feat: user tags (#4238)

* feat: user tags

* Apply suggestions from code review

Co-authored-by: Link <lts20050703@gmail.com>

---------

Co-authored-by: Link <lts20050703@gmail.com>
This commit is contained in:
Fleny
2025-07-12 09:25:31 +02:00
committed by GitHub
parent 8e637731e8
commit cb8176c623
6 changed files with 86 additions and 4 deletions

View File

@@ -51,6 +51,7 @@ import type {
Subscription,
UnfurledMediaItem,
User,
UserPrimaryGuild,
VoiceState,
Webhook,
} from './transformers/index.js'
@@ -110,6 +111,7 @@ export interface TransformersObjects {
subscription: Subscription
unfurledMediaItem: UnfurledMediaItem
user: User
userPrimaryGuild: UserPrimaryGuild
voiceState: VoiceState
webhook: Webhook
}
@@ -682,8 +684,16 @@ export function createDesiredPropertiesObject<T extends RecursivePartial<Transfo
avatarDecorationData: defaultValue,
toggles: defaultValue,
collectibles: defaultValue,
primaryGuild: defaultValue,
...desiredProperties.user,
},
userPrimaryGuild: {
identityGuildId: defaultValue,
identityEnabled: defaultValue,
tag: defaultValue,
badge: defaultValue,
...desiredProperties.userPrimaryGuild,
},
avatarDecorationData: {
asset: defaultValue,
skuId: defaultValue,

View File

@@ -71,6 +71,7 @@ import type {
DiscordThreadMemberGuildCreate,
DiscordUnfurledMediaItem,
DiscordUser,
DiscordUserPrimaryGuild,
DiscordVoiceRegion,
DiscordVoiceState,
DiscordWebhook,
@@ -152,6 +153,7 @@ import {
type ThreadMemberGuildCreate,
type UnfurledMediaItem,
type User,
type UserPrimaryGuild,
type VoiceRegion,
type VoiceState,
type Webhook,
@@ -229,6 +231,7 @@ import {
transformThreadMemberGuildCreate,
transformUnfurledMediaItem,
transformUser,
transformUserPrimaryGuild,
transformUserToDiscordUser,
transformVoiceRegion,
transformVoiceState,
@@ -411,6 +414,11 @@ export type Transformers<TProps extends TransformersDesiredProperties, TBehavior
) => any
unfurledMediaItem: (bot: Bot<TProps, TBehavior>, payload: DiscordUnfurledMediaItem, unfurledMediaItem: UnfurledMediaItem) => any
user: (bot: Bot<TProps, TBehavior>, payload: DiscordUser, user: SetupDesiredProps<User, TProps, TBehavior>) => any
userPrimaryGuild: (
bot: Bot<TProps, TBehavior>,
payload: DiscordUserPrimaryGuild,
userPrimaryGuild: SetupDesiredProps<UserPrimaryGuild, TProps, TBehavior>,
) => any
voiceRegion: (bot: Bot<TProps, TBehavior>, payload: DiscordVoiceRegion, voiceRegion: VoiceRegion) => any
voiceState: (bot: Bot<TProps, TBehavior>, payload: DiscordVoiceState, voiceState: SetupDesiredProps<VoiceState, TProps, TBehavior>) => any
webhook: (bot: Bot<TProps, TBehavior>, payload: DiscordWebhook, webhook: SetupDesiredProps<Webhook, TProps, TBehavior>) => any
@@ -546,6 +554,7 @@ export type Transformers<TProps extends TransformersDesiredProperties, TBehavior
threadMemberGuildCreate: (bot: Bot<TProps, TBehavior>, payload: DiscordThreadMemberGuildCreate) => ThreadMemberGuildCreate
unfurledMediaItem: (bot: Bot<TProps, TBehavior>, payload: DiscordUnfurledMediaItem) => UnfurledMediaItem
user: (bot: Bot<TProps, TBehavior>, payload: DiscordUser) => SetupDesiredProps<User, TProps, TBehavior>
userPrimaryGuild: (bot: Bot<TProps, TBehavior>, payload: DiscordUserPrimaryGuild) => SetupDesiredProps<UserPrimaryGuild, TProps, TBehavior>
voiceRegion: (bot: Bot<TProps, TBehavior>, payload: DiscordVoiceRegion) => VoiceRegion
voiceState: (
bot: Bot<TProps, TBehavior>,
@@ -628,6 +637,7 @@ export function createTransformers<TProps extends TransformersDesiredProperties,
threadMemberGuildCreate: options.customizers?.threadMemberGuildCreate ?? defaultCustomizer,
unfurledMediaItem: options.customizers?.unfurledMediaItem ?? defaultCustomizer,
user: options.customizers?.user ?? defaultCustomizer,
userPrimaryGuild: options.customizers?.userPrimaryGuild ?? defaultCustomizer,
voiceRegion: options.customizers?.voiceRegion ?? defaultCustomizer,
voiceState: options.customizers?.voiceState ?? defaultCustomizer,
webhook: options.customizers?.webhook ?? defaultCustomizer,
@@ -720,6 +730,7 @@ export function createTransformers<TProps extends TransformersDesiredProperties,
threadMemberGuildCreate: options.threadMemberGuildCreate ?? transformThreadMemberGuildCreate,
unfurledMediaItem: options.unfurledMediaItem ?? transformUnfurledMediaItem,
user: options.user ?? transformUser,
userPrimaryGuild: options.userPrimaryGuild ?? transformUserPrimaryGuild,
voiceRegion: options.voiceRegion ?? transformVoiceRegion,
voiceState: options.voiceState ?? transformVoiceState,
webhook: options.webhook ?? transformWebhook,

View File

@@ -1782,6 +1782,8 @@ export interface User {
verified: boolean
/** data for the user's collectibles */
collectibles?: Collectibles
/** The user's primary guild */
primaryGuild?: UserPrimaryGuild
}
export interface Collectibles {
@@ -1800,6 +1802,17 @@ export interface Nameplate {
palette: string
}
export interface UserPrimaryGuild {
/** The id of the primary guild */
identityGuildId?: bigint
/** Whether the user is displaying the primary guild's server tag. This can be undefined if the system clears the identity, e.g. because the server no longer supports tags. */
identityEnabled?: boolean
/** The text of the user's server tag. Limited to 4 characters */
tag?: string
/** The server tag badge hash */
badge?: bigint
}
export interface VoiceRegion {
id: string
name: string

View File

@@ -1,6 +1,6 @@
import type { DiscordCollectibles, DiscordNameplate, DiscordUser } from '@discordeno/types'
import type { DiscordCollectibles, DiscordNameplate, DiscordUser, DiscordUserPrimaryGuild } from '@discordeno/types'
import { iconHashToBigInt } from '@discordeno/utils'
import { type Collectibles, type InternalBot, type Nameplate, ToggleBitfield, type User, UserToggles } from '../index.js'
import { type Collectibles, type InternalBot, type Nameplate, ToggleBitfield, type User, type UserPrimaryGuild, UserToggles } from '../index.js'
export const baseUser: InternalBot['transformers']['$inferredTypes']['user'] = {
// This allows typescript to still check for type errors on functions below
@@ -43,6 +43,7 @@ export function transformUser(bot: InternalBot, payload: DiscordUser): typeof bo
if (props.avatarDecorationData && payload.avatar_decoration_data)
user.avatarDecorationData = bot.transformers.avatarDecorationData(bot, payload.avatar_decoration_data)
if (props.collectibles && payload.collectibles) user.collectibles = bot.transformers.collectibles(bot, payload.collectibles)
if (props.primaryGuild && payload.primary_guild) user.primaryGuild = bot.transformers.userPrimaryGuild(bot, payload.primary_guild)
return bot.transformers.customizers.user(bot, payload, user)
}
@@ -67,3 +68,15 @@ export function transformNameplate(bot: InternalBot, payload: DiscordNameplate):
return bot.transformers.customizers.nameplate(bot, payload, nameplate)
}
export function transformUserPrimaryGuild(bot: InternalBot, payload: DiscordUserPrimaryGuild): UserPrimaryGuild {
const userPrimaryGuild = {} as UserPrimaryGuild
const props = bot.transformers.desiredProperties.userPrimaryGuild
if (props.identityGuildId && payload.identity_guild_id) userPrimaryGuild.identityGuildId = bot.transformers.snowflake(payload.identity_guild_id)
if (props.identityEnabled && payload.identity_enabled) userPrimaryGuild.identityEnabled = payload.identity_enabled
if (props.tag && payload.tag) userPrimaryGuild.tag = payload.tag
if (props.badge && payload.badge) userPrimaryGuild.badge = iconHashToBigInt(payload.badge)
return bot.transformers.customizers.userPrimaryGuild(bot, payload, userPrimaryGuild)
}

View File

@@ -37,9 +37,11 @@ export interface DiscordUser {
/** the user's banner, or null if unset */
banner?: string
/** data for the user's avatar decoration */
avatar_decoration_data?: DiscordAvatarDecorationData
avatar_decoration_data?: DiscordAvatarDecorationData | null
/** data for the user's collectibles */
collectibles?: DiscordCollectibles
collectibles?: DiscordCollectibles | null
/** The user's primary guild */
primary_guild?: DiscordUserPrimaryGuild | null
}
/** https://discord.com/developers/docs/resources/user#user-object-user-flags */
@@ -69,6 +71,18 @@ export enum PremiumTypes {
NitroBasic,
}
/** https://discord.com/developers/docs/resources/user#user-object-user-primary-guild */
export interface DiscordUserPrimaryGuild {
/** The id of the primary guild */
identity_guild_id: string | null
/** Whether the user is displaying the primary guild's server tag. This can be `null` if the system clears the identity, e.g. because the server no longer supports tags. */
identity_enabled: boolean | null
/** The text of the user's server tag. Limited to 4 characters */
tag: string | null
/** The server tag badge hash */
badge: string | null
}
/** https://discord.com/developers/docs/resources/user#avatar-decoration-data-object-avatar-decoration-data-structure */
export interface DiscordAvatarDecorationData {
/** the avatar decoration hash */

View File

@@ -447,3 +447,24 @@ export function roleIconUrl(
)
: undefined
}
/**
* Builds the URL to a guild tag badge stored in the Discord CDN.
*
* @param guildId - The ID of the guild to get the tag badge of
* @param badgeHash - The hash identifying the guild tag badge.
* @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 {
if (badgeHash === undefined) return undefined
return formatImageUrl(`https://cdn.discordapp.com/guild-tag-badges/${guildId}/${badgeHash}`, options?.size ?? 128, options?.format)
}