You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

82 lines
2.6 KiB

import { ExtendedKind } from '@/constants'
import { extractBadgeDefinitionMedia } from '@/lib/badge-definition-media'
import { tagNameEquals } from '@/lib/tag'
import { Event } from 'nostr-tools'
/** Legacy NIP-58 profile badges addressable `d` tag value. */
export const LEGACY_PROFILE_BADGES_D_TAG = 'profile_badges'
export type ProfileBadgeEntry = {
definitionCoordinate: string
awardEventId: string
}
export type ResolvedProfileBadge = {
definitionCoordinate: string
awardEventId: string
name: string
description?: string
imageUrl?: string
}
/** Parse consecutive `a` / `e` pairs from a NIP-58 profile badges list event. */
export function parseProfileBadgeEntries(event: Event | undefined): ProfileBadgeEntry[] {
if (!event) return []
const out: ProfileBadgeEntry[] = []
const tags = event.tags
for (let i = 0; i < tags.length; i++) {
const t = tags[i]
if (t[0] !== 'a' || !t[1]?.trim()) continue
const next = tags[i + 1]
if (next?.[0] === 'e' && next[1]?.trim()) {
out.push({ definitionCoordinate: t[1].trim(), awardEventId: next[1].trim() })
i++
}
}
return out
}
export function isNip58ProfileBadgesListEvent(event: Event): boolean {
if (event.kind === ExtendedKind.PROFILE_BADGES_LIST) return true
if (event.kind !== ExtendedKind.PROFILE_BADGES) return false
const d = event.tags.find(tagNameEquals('d'))?.[1]?.trim()
return d === LEGACY_PROFILE_BADGES_D_TAG
}
export function parseAddressableCoordinate(
coordinate: string
): { kind: number; pubkey: string; d: string } | null {
const trimmed = coordinate.trim()
const idx1 = trimmed.indexOf(':')
if (idx1 < 0) return null
const idx2 = trimmed.indexOf(':', idx1 + 1)
if (idx2 < 0) return null
const kind = parseInt(trimmed.slice(0, idx1), 10)
if (!Number.isFinite(kind)) return null
return {
kind,
pubkey: trimmed.slice(idx1 + 1, idx2),
d: trimmed.slice(idx2 + 1)
}
}
export function resolveBadgeDisplayFromDefinition(
entry: ProfileBadgeEntry,
defEvent: Event | undefined
): ResolvedProfileBadge {
const parsed = parseAddressableCoordinate(entry.definitionCoordinate)
const fallbackName = parsed?.d || entry.definitionCoordinate
const name =
defEvent?.tags.find(tagNameEquals('name'))?.[1]?.trim() ||
defEvent?.tags.find(tagNameEquals('d'))?.[1]?.trim() ||
fallbackName
const description = defEvent?.tags.find(tagNameEquals('description'))?.[1]?.trim()
const media = extractBadgeDefinitionMedia(defEvent)
return {
definitionCoordinate: entry.definitionCoordinate,
awardEventId: entry.awardEventId,
name,
description: description || undefined,
imageUrl: media.image ?? media.thumb
}
}