story-research-zapwall/lib/zapAggregation.ts
Nicolas Cantu 2d4aaac007 Fix remaining issues after removing control disabling comments
- Restore nostrEventParsing.ts that was accidentally deleted
- Fix zapAggregation.ts type error without using any
- Mark _unusedExtractTags parameter as unused with underscore prefix
- All TypeScript checks pass
2025-12-27 22:28:29 +01:00

93 lines
2.5 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
const DEFAULT_RELAY = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io'
interface ZapAggregationFilter {
authorPubkey: string
seriesId?: string
articleId?: string
kindType: 'sponsoring' | 'purchase' | 'review_tip'
reviewerPubkey?: string
timeoutMs?: number
}
function buildFilters(params: ZapAggregationFilter) {
const filters = [
{
kinds: [9735],
'#p': [params.authorPubkey],
'#kind_type': [params.kindType],
...(params.seriesId ? { '#series': [params.seriesId] } : {}),
...(params.articleId ? { '#article': [params.articleId] } : {}),
...(params.reviewerPubkey ? { '#reviewer': [params.reviewerPubkey] } : {}),
},
]
return filters
}
function millisatsToSats(value: string | undefined): number {
if (!value) {
return 0
}
const parsed = parseInt(value, 10)
if (Number.isNaN(parsed)) {
return 0
}
return Math.floor(parsed / 1000)
}
export function aggregateZapSats(params: ZapAggregationFilter): Promise<number> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Nostr pool not initialized')
}
const poolWithSub = pool as SimplePoolWithSub
const filters = buildFilters(params)
const relay = DEFAULT_RELAY
const timeout = params.timeoutMs ?? 5000
const sub = poolWithSub.sub([relay], filters)
return collectZap(sub, timeout)
}
function collectZap(
sub: ReturnType<SimplePoolWithSub['sub']>,
timeout: number
): Promise<number> {
return new Promise<number>((resolve, reject) => {
let total = 0
let finished = false
const done = () => {
if (finished) {
return
}
finished = true
sub.unsub()
resolve(total)
}
const onError = (err: unknown) => {
if (finished) {
return
}
finished = true
sub.unsub()
reject(err instanceof Error ? err : new Error('Unknown zap aggregation error'))
}
sub.on('event', (event: Event) => {
const amountTag = event.tags.find((tag) => tag[0] === 'amount')
total += millisatsToSats(amountTag?.[1])
})
sub.on('eose', () => done())
setTimeout(() => done(), timeout).unref?.()
if (typeof (sub as unknown as { on?: unknown }).on === 'function') {
const subWithError = sub as unknown as { on: (event: string, handler: (error: Error) => void) => void }
subWithError.on('error', onError)
}
})
}