Files
BYW-VoiceHub/server/utils/user-avatar.ts
T

73 lines
2.2 KiB
TypeScript

// 头像来源解析:优先读取用户选中的身份,未选择时按创建顺序取第一个可用头像
export interface AvatarIdentity {
id?: number
provider: string
providerUserId: string
providerUsername?: string | null
avatar?: string | null
createdAt?: Date | string | number | null
}
export interface AvatarSource {
provider: string
providerUserId: string
url: string
}
export function getIdentityAvatarUrl(
identity: AvatarIdentity | null | undefined
): string | null {
if (!identity) return null
if (identity.provider === 'webauthn' || identity.provider === 'totp') return null
const avatar = typeof identity.avatar === 'string' ? identity.avatar.trim() : ''
if (avatar && /^https?:\/\//i.test(avatar)) return avatar
// 兼容迁移前只存了 GitHub 用户名的身份(需校验格式,防止特殊字符拼进 URL)
const username =
typeof identity.providerUsername === 'string' ? identity.providerUsername.trim() : ''
if (identity.provider === 'github' && username && /^[a-zA-Z0-9-]{1,39}$/.test(username)) {
return `https://github.com/${username}.png`
}
return null
}
export function resolveAvatarSource(
user: { avatarProvider?: string | null; avatarProviderUserId?: string | null },
identities: AvatarIdentity[]
): AvatarSource | null {
const candidates = identities
.filter((identity) => getIdentityAvatarUrl(identity))
.sort((a, b) => {
const timeDiff = Number(new Date(a.createdAt || 0)) - Number(new Date(b.createdAt || 0))
if (timeDiff !== 0) return timeDiff
return (a.id ?? 0) - (b.id ?? 0)
})
if (candidates.length === 0) return null
if (user?.avatarProvider && user?.avatarProviderUserId) {
const selected = candidates.find(
(identity) =>
identity.provider === user.avatarProvider &&
identity.providerUserId === user.avatarProviderUserId
)
if (selected) {
return {
provider: selected.provider,
providerUserId: selected.providerUserId,
url: getIdentityAvatarUrl(selected)!
}
}
}
const first = candidates[0]
return {
provider: first.provider,
providerUserId: first.providerUserId,
url: getIdentityAvatarUrl(first)!
}
}