import type { Event } from 'nostr-tools' import type { Article, KindType, Purchase, Review, ReviewTip, Series, Sponsoring } from '@/types/nostr' import { extractTagsFromEvent } from './nostrTagSystem' import { buildObjectId, parseObjectId } from './urlGenerator' import { generateHashId } from './hashIdGenerator' import { extractPurchaseFromEvent, extractReviewTipFromEvent, extractSponsoringFromEvent } from './metadataExtractor' /** * Parse article metadata from Nostr event * Uses new tag system: #publication, #sciencefiction|research, #id_, #paywall, #payment */ export async function parseArticleFromEvent(event: Event): Promise
{ try { const tags = extractTagsFromEvent(event) // Check if it's a publication type if (tags.type !== 'publication') { return null } const { previewContent } = getPreviewContent(event.content, tags.preview) return await buildArticle(event, tags, previewContent) } catch (e) { console.error('Error parsing article:', e) return null } } export async function parseSeriesFromEvent(event: Event): Promise { try { const tags = extractTagsFromEvent(event) // Check if it's a series type (tag is 'series' in English) if (tags.type !== 'series') { return null } if (!tags.title || !tags.description) { return null } // Map category from new system to old system const category = tags.category === 'sciencefiction' ? 'science-fiction' : tags.category === 'research' ? 'scientific-research' : 'science-fiction' // Extract hash, version, index from id tag or parse it let hash: string let version = tags.version ?? 0 let index = 0 if (tags.id) { const parsed = parseObjectId(tags.id) if (parsed.hash) { hash = parsed.hash version = parsed.version ?? version index = parsed.index ?? index } else { // If id is just a hash, use it directly hash = tags.id } } else { // Generate hash from series data hash = await generateHashId({ type: 'series', pubkey: event.pubkey, title: tags.title, description: tags.description, category: tags.category ?? 'sciencefiction', coverUrl: tags.coverUrl ?? '', }) } const id = buildObjectId(hash, index, version) const series: Series = { id, hash, version, index, pubkey: event.pubkey, title: tags.title, description: tags.description, preview: (tags.preview) ?? event.content.substring(0, 200), thumbnailUrl: tags.coverUrl ?? '', // Use coverUrl as thumbnail if available category, ...(tags.coverUrl ? { coverUrl: tags.coverUrl } : {}), } series.kindType = 'series' return series } catch (e) { console.error('Error parsing series:', e) return null } } export async function parseReviewFromEvent(event: Event): Promise { try { const tags = extractTagsFromEvent(event) // Check if it's a quote type (reviews are quotes, tag is 'quote' in English) if (tags.type !== 'quote') { return null } const {articleId} = tags const reviewer = tags.reviewerPubkey if (!articleId || !reviewer) { return null } const rewardedTag = event.tags.find((tag) => tag[0] === 'rewarded' && tag[1] === 'true') const rewardAmountTag = event.tags.find((tag) => tag[0] === 'reward_amount') // Extract hash, version, index from id tag or parse it let hash: string let version = tags.version ?? 0 let index = 0 if (tags.id) { const parsed = parseObjectId(tags.id) if (parsed.hash) { hash = parsed.hash version = parsed.version ?? version index = parsed.index ?? index } else { // If id is just a hash, use it directly hash = tags.id } } else { // Generate hash from review data hash = await generateHashId({ type: 'quote', pubkey: event.pubkey, articleId, reviewerPubkey: reviewer, content: event.content, title: tags.title ?? '', }) } const id = buildObjectId(hash, index, version) // Extract text from tags if present const textTag = event.tags.find((tag) => tag[0] === 'text')?.[1] const review: Review = { id, hash, version, index, articleId, authorPubkey: event.pubkey, reviewerPubkey: reviewer, content: event.content, description: tags.description ?? '', // Required field with default createdAt: event.created_at, ...(tags.title ? { title: tags.title } : {}), ...(textTag ? { text: textTag } : {}), ...(rewardedTag ? { rewarded: true } : {}), ...(rewardAmountTag ? { rewardAmount: parseInt(rewardAmountTag[1] ?? '0', 10) } : {}), } review.kindType = 'review' return review } catch (e) { console.error('Error parsing review:', e) return null } } function getPreviewContent(content: string, previewTag?: string) { const lines = content.split('\n') const previewContent = previewTag ?? lines[0] ?? content.substring(0, 200) return { previewContent } } async function buildArticle(event: Event, tags: ReturnType, preview: string): Promise
{ // Map category from new system to old system const category = tags.category === 'sciencefiction' ? 'science-fiction' : tags.category === 'research' ? 'scientific-research' : undefined const isPresentation = tags.type === 'author' // Extract hash, version, index from id tag or parse it let hash: string let version = tags.version ?? 0 let index = 0 if (tags.id) { const parsed = parseObjectId(tags.id) if (parsed.hash) { hash = parsed.hash version = parsed.version ?? version index = parsed.index ?? index } else { // If id is just a hash, use it directly hash = tags.id } } else { // Generate hash from article data hash = await generateHashId({ type: isPresentation ? 'author' : 'publication', pubkey: event.pubkey, title: tags.title ?? 'Untitled', preview, category: tags.category ?? 'sciencefiction', seriesId: tags.seriesId ?? '', bannerUrl: tags.bannerUrl ?? '', zapAmount: tags.zapAmount ?? 800, }) } const id = buildObjectId(hash, index, version) // Extract pages from JSON metadata if present let pages: Page[] | undefined try { const jsonTag = event.tags.find((tag) => tag[0] === 'json')?.[1] if (jsonTag) { const metadata = JSON.parse(jsonTag) as { pages?: Page[] } if (metadata.pages && Array.isArray(metadata.pages)) { pages = metadata.pages } } } catch (e) { // Ignore JSON parsing errors } return { id, hash, version, index, pubkey: event.pubkey, title: tags.title ?? 'Untitled', preview, content: '', description: tags.description ?? '', // Required field with default contentDescription: tags.description ?? '', // Required field with default (can be improved later) createdAt: event.created_at, zapAmount: tags.zapAmount ?? 800, paid: false, thumbnailUrl: tags.bannerUrl ?? tags.pictureUrl ?? '', // Required field with default ...(tags.invoice ? { invoice: tags.invoice } : {}), ...(tags.paymentHash ? { paymentHash: tags.paymentHash } : {}), ...(category ? { category } : {}), ...(isPresentation ? { isPresentation: true } : {}), ...(tags.mainnetAddress ? { mainnetAddress: tags.mainnetAddress } : {}), ...(tags.totalSponsoring ? { totalSponsoring: tags.totalSponsoring } : {}), ...(tags.seriesId ? { seriesId: tags.seriesId } : {}), ...(tags.bannerUrl ? { bannerUrl: tags.bannerUrl } : {}), ...(pages && pages.length > 0 ? { pages } : {}), ...(tags.type === 'publication' ? { kindType: 'article' as KindType } : tags.type === 'author' ? { kindType: 'article' as KindType } : {}), } } /** * Parse purchase from zap receipt event (kind 9735) */ export async function parsePurchaseFromEvent(event: Event): Promise { try { const extracted = await extractPurchaseFromEvent(event) if (!extracted) { return null } // Extract hash, version, index from id const parsed = parseObjectId(extracted.id) const hash = parsed.hash ?? extracted.id const version = parsed.version ?? 0 const index = parsed.index ?? 0 const id = buildObjectId(hash, index, version) return { id, hash, version, index, payerPubkey: extracted.payerPubkey, articleId: extracted.articleId, authorPubkey: extracted.authorPubkey, amount: extracted.amount, paymentHash: extracted.paymentHash, createdAt: event.created_at, kindType: 'purchase', } } catch (e) { console.error('Error parsing purchase:', e) return null } } /** * Parse sponsoring from zap receipt event (kind 9735) */ export async function parseSponsoringFromEvent(event: Event): Promise { try { const extracted = await extractSponsoringFromEvent(event) if (!extracted) { return null } // Extract hash, version, index from id const parsed = parseObjectId(extracted.id) const hash = parsed.hash ?? extracted.id const version = parsed.version ?? 0 const index = parsed.index ?? 0 const id = buildObjectId(hash, index, version) // Extract text from tags if present const textTag = event.tags.find((tag) => tag[0] === 'text')?.[1] return { id, hash, version, index, payerPubkey: extracted.payerPubkey, authorPubkey: extracted.authorPubkey, amount: extracted.amount, paymentHash: extracted.paymentHash, createdAt: event.created_at, ...(extracted.seriesId ? { seriesId: extracted.seriesId } : {}), ...(extracted.articleId ? { articleId: extracted.articleId } : {}), ...(textTag ? { text: textTag } : {}), kindType: 'sponsoring', } } catch (e) { console.error('Error parsing sponsoring:', e) return null } } /** * Parse review tip from zap receipt event (kind 9735) */ export async function parseReviewTipFromEvent(event: Event): Promise { try { const extracted = await extractReviewTipFromEvent(event) if (!extracted) { return null } // Extract hash, version, index from id const parsed = parseObjectId(extracted.id) const hash = parsed.hash ?? extracted.id const version = parsed.version ?? 0 const index = parsed.index ?? 0 const id = buildObjectId(hash, index, version) // Extract text from tags if present const textTag = event.tags.find((tag) => tag[0] === 'text')?.[1] return { id, hash, version, index, payerPubkey: extracted.payerPubkey, articleId: extracted.articleId, reviewId: extracted.reviewId, reviewerPubkey: extracted.reviewerPubkey, authorPubkey: extracted.authorPubkey, amount: extracted.amount, paymentHash: extracted.paymentHash, createdAt: event.created_at, ...(textTag ? { text: textTag } : {}), kindType: 'review_tip', } } catch (e) { console.error('Error parsing review tip:', e) return null } }