import type { Event } from 'nostr-tools' import { nostrService } from './nostr' import type { ReviewTip } from '@/types/nostr' import { parseReviewTipFromEvent } from './nostrEventParsing' import { objectCache } from './objectCache' import { getPrimaryRelaySync } from './config' import { MIN_EVENT_DATE } from './platformConfig' import { parseObjectId } from './urlGenerator' function buildReviewTipFilters(articleId?: string, reviewId?: string, authorPubkey?: string, reviewerPubkey?: string): Array<{ kinds: number[] since?: number '#p'?: string[] '#e'?: string[] '#review_id'?: string[] '#reviewer'?: string[] '#kind_type'?: string[] }> { const filters: Array<{ kinds: number[] since?: number '#p'?: string[] '#e'?: string[] '#review_id'?: string[] '#reviewer'?: string[] '#kind_type'?: string[] }> = [] const baseFilter: { kinds: number[] since: number '#kind_type': string[] '#p'?: string[] '#e'?: string[] '#review_id'?: string[] '#reviewer'?: string[] } = { kinds: [9735], // Zap receipt since: MIN_EVENT_DATE, '#kind_type': ['review_tip'], } if (authorPubkey) { baseFilter['#p'] = [authorPubkey] } if (articleId) { baseFilter['#e'] = [articleId] } if (reviewId) { baseFilter['#review_id'] = [reviewId] } if (reviewerPubkey) { baseFilter['#reviewer'] = [reviewerPubkey] } filters.push(baseFilter) return filters } export async function getReviewTipById(reviewTipId: string, timeoutMs: number = 5000): Promise { const parsed = parseObjectId(reviewTipId) const hash = parsed.hash ?? reviewTipId // Check cache first const cached = await objectCache.get('review_tip', hash) if (cached) { return cached as ReviewTip } const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildReviewTipFilters() const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (value: ReviewTip | null): Promise => { if (finished) { return } finished = true sub.unsub() resolve(value) } sub.on('event', async (event: Event): Promise => { const reviewTipParsed = await parseReviewTipFromEvent(event) if (reviewTipParsed?.id === reviewTipId) { // Cache the parsed review tip if (reviewTipParsed.hash) { await objectCache.set('review_tip', reviewTipParsed.hash, event, reviewTipParsed, 0, false, reviewTipParsed.index ?? 0) } done(reviewTipParsed) } }) sub.on('eose', (): void => { void done(null) }) setTimeout(() => done(null), timeoutMs).unref?.() }) } export function getReviewTipsForArticle(articleId: string, timeoutMs: number = 5000): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildReviewTipFilters(articleId) const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const results: ReviewTip[] = [] const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (): Promise => { if (finished) { return } finished = true sub.unsub() resolve(results) } sub.on('event', async (event: Event): Promise => { const reviewTipParsed = await parseReviewTipFromEvent(event) if (reviewTipParsed?.articleId === articleId) { // Cache the parsed review tip if (reviewTipParsed.hash) { await objectCache.set('review_tip', reviewTipParsed.hash, event, reviewTipParsed, 0, false, reviewTipParsed.index ?? 0) } results.push(reviewTipParsed) } }) sub.on('eose', (): void => { void done() }) setTimeout(() => done(), timeoutMs).unref?.() }) } export function getReviewTipsForReview(reviewId: string, timeoutMs: number = 5000): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildReviewTipFilters(undefined, reviewId) const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const results: ReviewTip[] = [] const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = async (): Promise => { if (finished) { return } finished = true sub.unsub() resolve(results) } sub.on('event', async (event: Event): Promise => { const reviewTipParsed = await parseReviewTipFromEvent(event) if (reviewTipParsed?.reviewId === reviewId) { // Cache the parsed review tip if (reviewTipParsed.hash) { await objectCache.set('review_tip', reviewTipParsed.hash, event, reviewTipParsed, 0, false, reviewTipParsed.index ?? 0) } results.push(reviewTipParsed) } }) sub.on('eose', (): void => { void done() }) setTimeout(() => done(), timeoutMs).unref?.() }) }