92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
import type { Event } from 'nostr-tools'
|
|
import { nostrService } from './nostr'
|
|
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
|
|
import { getPrimaryRelaySync } from './config'
|
|
|
|
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 = getPrimaryRelaySync()
|
|
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)
|
|
}
|
|
})
|
|
}
|