2026-01-13 14:49:19 +01:00

148 lines
4.5 KiB
TypeScript

import type { Event } from 'nostr-tools'
import type { Article, Page } from '@/types/nostr'
import { extractTagsFromEvent } from '../nostrTagSystem'
import { buildObjectId } from '../urlGenerator'
import { generateHashId } from '../hashIdGenerator'
import { getPreviewContent, mapNostrCategoryToLegacy, resolveObjectIdParts, resolveThumbnailUrl, type ExtractedTags } from './shared'
/**
* Parse article metadata from Nostr event
* Uses new tag system: #publication, #sciencefiction|research, #id_<id>, #paywall, #payment
*/
export async function parseArticleFromEvent(event: Event): Promise<Article | null> {
try {
const tags = extractTagsFromEvent(event)
if (tags.type !== 'publication') {
return null
}
const { previewContent } = getPreviewContent(event.content, tags.preview)
return buildArticle(event, tags, previewContent)
} catch (e) {
console.error('Error parsing article:', e)
return null
}
}
function parsePagesFromEventJsonTag(event: Event): Page[] | undefined {
const jsonTag = event.tags.find((tag) => tag[0] === 'json')?.[1]
if (!jsonTag) {
return undefined
}
try {
const parsed: unknown = JSON.parse(jsonTag)
if (typeof parsed !== 'object' || parsed === null) {
return undefined
}
const metadata = parsed as { pages?: unknown }
if (!Array.isArray(metadata.pages)) {
return undefined
}
return metadata.pages as Page[]
} catch {
return undefined
}
}
function buildArticlePaymentFields(tags: ExtractedTags): Partial<Article> {
const result: Partial<Article> = {}
if (tags.invoice) {
result.invoice = tags.invoice
}
if (tags.paymentHash) {
result.paymentHash = tags.paymentHash
}
return result
}
function buildArticleClassificationFields(params: {
tags: ExtractedTags
category: 'science-fiction' | 'scientific-research' | undefined
isPresentation: boolean
}): Partial<Article> {
const result: Partial<Article> = {}
if (params.category) {
result.category = params.category
}
if (params.isPresentation) {
result.isPresentation = true
}
if (params.tags.type === 'publication' || params.tags.type === 'author') {
result.kindType = 'article'
}
return result
}
function buildArticleOptionalMetaFields(params: { tags: ExtractedTags; pages: Page[] | undefined }): Partial<Article> {
const result: Partial<Article> = {}
if (params.tags.mainnetAddress) {
result.mainnetAddress = params.tags.mainnetAddress
}
if (params.tags.totalSponsoring) {
result.totalSponsoring = params.tags.totalSponsoring
}
if (params.tags.seriesId) {
result.seriesId = params.tags.seriesId
}
if (params.tags.bannerUrl) {
result.bannerUrl = params.tags.bannerUrl
}
if (params.pages && params.pages.length > 0) {
result.pages = params.pages
}
return result
}
async function buildArticle(event: Event, tags: ExtractedTags, preview: string): Promise<Article> {
const category = mapNostrCategoryToLegacy(tags.category)
const isPresentation = tags.type === 'author'
const { hash, version, index } = await resolveObjectIdParts(buildObjectIdResolutionParams({ event, tags, preview, isPresentation }))
const id = buildObjectId(hash, index, version)
const pages = parsePagesFromEventJsonTag(event)
return {
id,
hash,
version,
index,
pubkey: event.pubkey,
title: tags.title ?? 'Untitled',
preview,
content: '',
description: tags.description ?? '',
contentDescription: tags.description ?? '',
createdAt: event.created_at,
zapAmount: tags.zapAmount ?? 800,
paid: false,
thumbnailUrl: resolveThumbnailUrl(tags),
...buildArticlePaymentFields(tags),
...buildArticleClassificationFields({ tags, category, isPresentation }),
...buildArticleOptionalMetaFields({ tags, pages }),
}
}
function buildObjectIdResolutionParams(params: {
event: Event
tags: ExtractedTags
preview: string
isPresentation: boolean
}): Parameters<typeof resolveObjectIdParts>[0] {
return {
...(params.tags.id ? { idTag: params.tags.id } : {}),
defaultVersion: params.tags.version ?? 0,
defaultIndex: 0,
generateHash: async (): Promise<string> =>
generateHashId({
type: params.isPresentation ? 'author' : 'publication',
pubkey: params.event.pubkey,
title: params.tags.title ?? 'Untitled',
preview: params.preview,
category: params.tags.category ?? 'sciencefiction',
seriesId: params.tags.seriesId ?? '',
bannerUrl: params.tags.bannerUrl ?? '',
zapAmount: params.tags.zapAmount ?? 800,
}),
}
}