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.
 
 
 
 

100 lines
3.1 KiB

import { tagNameEquals } from '@/lib/tag'
import type { TEmoji } from '@/types'
import { kinds, type Event } from 'nostr-tools'
export function getEmojiSetDTag(event: Event): string | undefined {
return event.tags.find(tagNameEquals('d'))?.[1]
}
export function labelEmojiSetEvent(event: Event): string {
const title = event.tags.find(tagNameEquals('title'))?.[1]?.trim()
if (title) return title
const d = getEmojiSetDTag(event)
return d ?? 'emoji set'
}
export function isEmojiSetPointerTag(tag: string[]): boolean {
if (tag[0] !== 'a' || !tag[1]) return false
const k = parseInt(tag[1].split(':')[0] ?? '', 10)
return k === kinds.Emojisets
}
/** Tags on kind 10030 other than inline `emoji` entries and `a` → 30030 pointers. */
export function preservedTagsFromUserEmojiListEvent(event: Event | null): string[][] {
if (!event) return []
return event.tags.filter((t) => {
if (t[0] === 'emoji') return false
if (isEmojiSetPointerTag(t)) return false
return true
})
}
/** Normalize `30030:<hex64>:<d>` for an `a` tag value (pubkey lowercased). */
export function normalizeEmojiSetATagValue(raw: string): string | null {
const s = raw.trim().replace(/\s+/g, '')
const m = /^(\d+):([0-9a-f]{64}):([\s\S]*)$/i.exec(s)
if (!m) return null
const kind = parseInt(m[1], 10)
if (kind !== kinds.Emojisets) return null
const pk = m[2].toLowerCase()
return `${kinds.Emojisets}:${pk}:${m[3]}`
}
export function buildEmojiSetTags(params: {
d: string
title?: string
description?: string
image?: string
emojis: TEmoji[]
}): string[][] {
const d = params.d.trim()
if (!d) throw new Error('Invalid list id')
const tags: string[][] = [['d', d]]
const title = params.title?.trim()
if (title) tags.push(['title', title])
const description = params.description?.trim()
if (description) tags.push(['description', description])
const image = params.image?.trim()
if (image) tags.push(['image', image])
for (const e of params.emojis) {
const sc = e.shortcode.trim().replace(/^:+|:+$/gu, '')
const url = e.url.trim()
if (!sc || !url) continue
tags.push(['emoji', sc, url])
}
return tags
}
export function extractEmojiSetEditorFields(event: Event): {
d: string
title: string
description: string
image: string
emojis: TEmoji[]
} {
const emojis: TEmoji[] = []
for (const t of event.tags) {
if (t[0] === 'emoji' && t[1] && t[2]) {
emojis.push({ shortcode: t[1], url: t[2] })
}
}
return {
d: getEmojiSetDTag(event) ?? '',
title: event.tags.find(tagNameEquals('title'))?.[1] ?? '',
description: event.tags.find(tagNameEquals('description'))?.[1] ?? '',
image: event.tags.find(tagNameEquals('image'))?.[1] ?? '',
emojis
}
}
export function dedupeEmojiSetEventsByD(events: Event[]): Event[] {
const byD = new Map<string, Event>()
for (const e of [...events].sort((a, b) => b.created_at - a.created_at)) {
const d = getEmojiSetDTag(e)
if (!d) continue
if (!byD.has(d)) byD.set(d, e)
}
return [...byD.values()].sort((a, b) =>
labelEmojiSetEvent(a).localeCompare(labelEmojiSetEvent(b), undefined, { sensitivity: 'base' })
)
}