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

134 lines
5.7 KiB
TypeScript

import type { ArticleDraft } from '../articlePublisher'
import type { Review } from '@/types/nostr'
import { buildTags } from '../nostrTagSystem'
import { PLATFORM_SERVICE } from '../platformConfig'
import { buildObjectId } from '../urlGenerator'
import { getPublishRelays } from '../relaySelection'
import { writeOrchestrator } from '../writeOrchestrator'
import { finalizeEvent, type EventTemplate } from 'nostr-tools'
import { hexToBytes } from 'nostr-tools/utils'
import { ensureKeys, getPrivateKeyForSigning, mapDraftCategoryToTag, requireCategory } from './shared'
export async function publishReview(params: {
articleId: string
seriesId: string
category: ArticleDraft['category']
authorPubkey: string
reviewerPubkey: string
content: string
title?: string
text?: string
authorPrivateKey?: string
}): Promise<Review> {
ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
requireCategory(params.category)
const hash = await buildReviewHashId({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}) })
const parsedReview = buildParsedReview({ params, hash, version: 0, index: 0 })
const eventTemplate = await buildReviewEvent(params, params.category)
const privateKey = getPrivateKeyForSigning(params.authorPrivateKey)
const event = finalizeEvent(eventTemplate, hexToBytes(privateKey))
await publishReviewToRelays({ parsedReview, event })
return parsedReview
}
function buildParsedReview(params: {
params: { articleId: string; authorPubkey: string; reviewerPubkey: string; content: string; title?: string; text?: string }
hash: string
version: number
index: number
}): Review {
return {
id: buildObjectId(params.hash, params.index, params.version),
hash: params.hash,
version: params.version,
index: params.index,
articleId: params.params.articleId,
authorPubkey: params.params.authorPubkey,
reviewerPubkey: params.params.reviewerPubkey,
content: params.params.content,
description: params.params.content.substring(0, 200),
createdAt: Math.floor(Date.now() / 1000),
...(params.params.title ? { title: params.params.title } : {}),
...(params.params.text ? { text: params.params.text } : {}),
kindType: 'review',
}
}
async function publishReviewToRelays(params: { parsedReview: Review; event: import('nostr-tools').Event }): Promise<void> {
const relays = await getPublishRelays()
const result = await writeOrchestrator.writeAndPublish(
{ objectType: 'review', hash: params.parsedReview.hash, event: params.event, parsed: params.parsedReview, version: params.parsedReview.version, hidden: false, index: params.parsedReview.index },
relays
)
if (!result.success) {
throw new Error('Failed to publish review')
}
}
async function buildReviewEvent(
params: { articleId: string; seriesId: string; authorPubkey: string; reviewerPubkey: string; content: string; title?: string; text?: string },
category: NonNullable<ArticleDraft['category']>
): Promise<Pick<EventTemplate, 'kind' | 'created_at' | 'content' | 'tags'>> {
const ctx = await buildReviewEventContext(params, category)
return { kind: 1, created_at: Math.floor(Date.now() / 1000), content: params.content, tags: ctx.tags }
}
async function buildReviewEventContext(
params: { articleId: string; reviewerPubkey: string; content: string; title?: string; text?: string },
category: NonNullable<ArticleDraft['category']>
): Promise<{ tags: string[][] }> {
const newCategory = mapDraftCategoryToTag(category)
const hashId = await buildReviewHashId({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}) })
const reviewJson = buildReviewJson({ reviewerPubkey: params.reviewerPubkey, articleId: params.articleId, content: params.content, ...(params.title ? { title: params.title } : {}), newCategory, hashId })
const tags = buildReviewTags({ newCategory, hashId, articleId: params.articleId, reviewerPubkey: params.reviewerPubkey, ...(params.title ? { title: params.title } : {}), ...(params.text ? { text: params.text } : {}), reviewJson })
return { tags }
}
async function buildReviewHashId(params: { reviewerPubkey: string; articleId: string; content: string; title?: string }): Promise<string> {
const { generateReviewHashId } = await import('../hashIdGenerator')
return generateReviewHashId({ pubkey: params.reviewerPubkey, articleId: params.articleId, reviewerPubkey: params.reviewerPubkey, content: params.content, ...(params.title ? { title: params.title } : {}) })
}
function buildReviewJson(params: { reviewerPubkey: string; articleId: string; content: string; title?: string; newCategory: 'sciencefiction' | 'research'; hashId: string }): string {
return JSON.stringify({
type: 'review',
pubkey: params.reviewerPubkey,
articleId: params.articleId,
reviewerPubkey: params.reviewerPubkey,
content: params.content,
title: params.title,
category: params.newCategory,
id: params.hashId,
version: 0,
index: 0,
})
}
function buildReviewTags(params: {
newCategory: 'sciencefiction' | 'research'
hashId: string
articleId: string
reviewerPubkey: string
title?: string
text?: string
reviewJson: string
}): string[][] {
const tags = buildTags({
type: 'quote',
category: params.newCategory,
id: params.hashId,
service: PLATFORM_SERVICE,
version: 0,
hidden: false,
paywall: false,
articleId: params.articleId,
reviewerPubkey: params.reviewerPubkey,
...(params.title ? { title: params.title } : {}),
})
if (params.text) {
tags.push(['text', params.text])
}
tags.push(['json', params.reviewJson])
return tags
}