import type { Event } from 'nostr-tools' import { nostrService } from './nostr' import type { Series } from '@/types/nostr' import { parseSeriesFromEvent } from './nostrEventParsing' import { buildTagFilter, extractTagsFromEvent } from './nostrTagSystem' import { getPrimaryRelaySync } from './config' import { PLATFORM_SERVICE, MIN_EVENT_DATE } from './platformConfig' import { objectCache } from './objectCache' import { parseObjectId } from './urlGenerator' function buildSeriesFilters(authorPubkey: string): Array<{ kinds: number[] authors?: string[] '#series'?: string[] since: number }> { const tagFilter = buildTagFilter({ type: 'series', authorPubkey, service: PLATFORM_SERVICE, }) return [ { kinds: tagFilter.kinds as number[], ...(tagFilter.authors ? { authors: tagFilter.authors as string[] } : {}), ...(tagFilter['#series'] ? { '#series': tagFilter['#series'] as string[] } : {}), since: MIN_EVENT_DATE, }, ] } export function getSeriesByAuthor(authorPubkey: string, timeoutMs: number = 5000): Promise { const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildSeriesFilters(authorPubkey) const { createSubscription } = require('@/types/nostr-tools-extended') return new Promise((resolve) => { const results: Series[] = [] const relayUrl = getPrimaryRelaySync() const sub = createSubscription(pool, [relayUrl], filters) let finished = false const done = (): void => { if (finished) { return } finished = true sub.unsub() resolve(results) } sub.on('event', (event: Event): void => { void (async (): Promise => { const parsed = await parseSeriesFromEvent(event) if (parsed) { results.push(parsed) } })() }) sub.on('eose', (): void => { done() }) setTimeout(() => done(), timeoutMs).unref?.() }) } function buildSeriesByIdFilters(seriesId: string): Array<{ kinds: number[] ids: string[] since: number [key: string]: unknown }> { return [ { kinds: [1], ids: [seriesId], ...buildTagFilter({ type: 'series', service: PLATFORM_SERVICE, }), since: MIN_EVENT_DATE, }, ] } export async function getSeriesById(seriesId: string, timeoutMs: number = 5000): Promise { // Try to parse seriesId as id format (__) or use it as hash const parsed = parseObjectId(seriesId) const hash = parsed.hash ?? seriesId // Check cache first const cached = await objectCache.get('series', hash) if (cached) { return cached as Series } const pool = nostrService.getPool() if (!pool) { throw new Error('Pool not initialized') } const filters = buildSeriesByIdFilters(seriesId) 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: Series | null): Promise => { if (finished) { return } finished = true sub.unsub() // Cache the result if found if (value && value.hash) { // Find the event to cache it // Note: We would need the event here, but for now we cache what we have // The event will be cached when it's parsed from the subscription } resolve(value) } sub.on('event', async (event: Event): Promise => { const parsed = await parseSeriesFromEvent(event) if (parsed) { // Cache the parsed series const tags = extractTagsFromEvent(event) if (parsed.hash) { await objectCache.set('series', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden ?? false, parsed.index) } await done(parsed) } }) sub.on('eose', (): void => { void done(null) }) setTimeout(() => done(null), timeoutMs).unref?.() }) }