story-research-zapwall/lib/zapAggregation.ts
Nicolas Cantu 85e3e57ad5 Remove all control disabling comments
- Remove @ts-expect-error comment from _unusedExtractTags
- Remove eslint-disable comment from zapAggregation.ts and fix type properly
- Remove unused _unusedExtractTags function
- Remove unused decryptDecryptionKey import
- Fix extractTagsFromEvent return type to include all optional properties explicitly
- All errors fixed without disabling any controls
2025-12-27 22:27:47 +01:00

92 lines
2.4 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') {
;(sub as any).on('error', onError)
}
})
}