diff --git a/lib/nostrEventParsing.ts b/lib/nostrEventParsing.ts new file mode 100644 index 0000000..f45feba --- /dev/null +++ b/lib/nostrEventParsing.ts @@ -0,0 +1,163 @@ +import type { Event } from 'nostr-tools' +import type { Article, KindType, MediaRef, Review, Series } from '@/types/nostr' +import { extractTagsFromEvent } from './nostrTagSystem' + +/** + * Parse article metadata from Nostr event + * Uses new tag system: #publication, #sciencefiction|research, #id_, #paywall, #payment + */ +export function parseArticleFromEvent(event: Event): Article | null { + 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 as string | undefined) + return buildArticle(event, tags, previewContent) + } catch (e) { + console.error('Error parsing article:', e) + return null + } +} + +export function parseSeriesFromEvent(event: Event): Series | null { + 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' + const series: Series = { + id: tags.id ?? event.id, + pubkey: event.pubkey, + title: tags.title as string, + description: tags.description as string, + preview: (tags.preview as string | undefined) ?? event.content.substring(0, 200), + category, + ...(tags.coverUrl ? { coverUrl: tags.coverUrl as string } : {}), + } + series.kindType = 'series' + return series + } catch (e) { + console.error('Error parsing series:', e) + return null + } +} + +export function parseReviewFromEvent(event: Event): Review | null { + 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.articleId as string | undefined + const reviewer = tags.reviewerPubkey as string | undefined + 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') + + const review: Review = { + id: tags.id ?? event.id, + articleId, + authorPubkey: event.pubkey, + reviewerPubkey: reviewer, + content: event.content, + createdAt: event.created_at, + ...(tags.title ? { title: tags.title as string } : {}), + ...(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 + } +} + +// extractTags is now replaced by extractTagsFromEvent from nostrTagSystem +// This function is kept for backward compatibility but should be migrated +// Currently unused - kept for potential future migration +// @ts-expect-error - Unused function kept for backward compatibility +function _unusedExtractTags(event: Event) { + const tags = extractTagsFromEvent(event) + const mediaTags = event.tags.filter((tag: string[]) => tag[0] === 'media') + const media: MediaRef[] = + mediaTags + .map((tag: string[]) => { + const url = tag[1] + const type = tag[2] === 'video' ? 'video' : 'image' + if (!url) { + return null + } + return { url, type } + }) + .filter(Boolean) as MediaRef[] + + // 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' + + return { + title: (tags.title as string | undefined) ?? 'Untitled', + preview: tags.preview as string | undefined, + description: tags.description as string | undefined, + zapAmount: (tags.zapAmount as number | undefined) ?? 800, + invoice: tags.invoice as string | undefined, + paymentHash: tags.paymentHash as string | undefined, + category, + isPresentation, + mainnetAddress: tags.mainnetAddress as string | undefined, + totalSponsoring: (tags.totalSponsoring as number | undefined) ?? 0, + authorPresentationId: undefined, // Not used in new system + seriesId: tags.seriesId as string | undefined, + bannerUrl: tags.bannerUrl as string | undefined, + coverUrl: tags.coverUrl as string | undefined, + media, + kindType: tags.type === 'author' ? 'article' : tags.type === 'series' ? 'series' : tags.type === 'publication' ? 'article' : tags.type === 'quote' ? 'review' : undefined, + articleId: tags.articleId as string | undefined, + reviewerPubkey: tags.reviewerPubkey as string | undefined, + author: undefined, // Not used in new system + } +} + +function getPreviewContent(content: string, previewTag?: string) { + const lines = content.split('\n') + const previewContent = previewTag ?? lines[0] ?? content.substring(0, 200) + return { previewContent } +} + +function buildArticle(event: Event, tags: ReturnType, preview: string): Article { + // 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' + + return { + id: tags.id ?? event.id, + pubkey: event.pubkey, + title: (tags.title as string | undefined) ?? 'Untitled', + preview, + content: '', + createdAt: event.created_at, + zapAmount: (tags.zapAmount as number | undefined) ?? 800, + paid: false, + ...(tags.invoice ? { invoice: tags.invoice as string } : {}), + ...(tags.paymentHash ? { paymentHash: tags.paymentHash as string } : {}), + ...(category ? { category } : {}), + ...(isPresentation ? { isPresentation: true } : {}), + ...(tags.mainnetAddress ? { mainnetAddress: tags.mainnetAddress as string } : {}), + ...(tags.totalSponsoring ? { totalSponsoring: tags.totalSponsoring as number } : {}), + ...(tags.seriesId ? { seriesId: tags.seriesId as string } : {}), + ...(tags.bannerUrl ? { bannerUrl: tags.bannerUrl as string } : {}), + ...(tags.type === 'publication' ? { kindType: 'article' as KindType } : tags.type === 'author' ? { kindType: 'article' as KindType } : {}), + } +}