Files
bitvid/js/channelZapTotal.js
thePR0M3TH3ANandClaude Opus 4.8 cf82978060 zaps: profile-zap counting + channel "sats zapped" total (#47 — closes the feature)
The store now supports p: (profile/channel) pointers — every zap tagged to a
creator pubkey (direct profile zaps AND zaps to their videos, both carry a `p`
tag), from 9735 receipts + verified tallies:

- pointerKey accepts "p"; new profilePointer(pubkey) helper.
- runBatch batches p: pointers into a #p filter; both folds count #p-tagged
  events (deduped by payment_hash like a:/e:).
- Channel page: a "⚡ N sats zapped" total under the lightning address, wired
  to the store via js/channelZapTotal.js (own module for channelProfile.js's
  size budget), hidden until nonzero.

Completes the D2 video + profile scope; the bitvid-native zap tally feature is
now feature-complete (publish + count + chart + video + profile). Tests:
profilePointer/pointerKey + #p counting in tests/zap-totals.test.mjs. Lint
(size cap ok) + build green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-05 00:36:44 -04:00

69 lines
1.8 KiB
JavaScript

// js/channelZapTotal.js
//
// Channel-page "sats zapped to this creator" total (#47, docs/zap-tally-plan.md
// D2 profile path). Uses a p:<pubkey> pointer in the shared zap-totals store, so
// it counts every zap tagged to the creator — direct profile zaps AND zaps to
// their videos (both carry a `p` tag) — from 9735 receipts + verified bitvid
// tallies. Hidden until there's a nonzero total. Kept out of channelProfile.js
// (file-size budget).
import {
profilePointer,
requestVideoZapTotal,
getVideoZapTotalSnapshot,
onZapTotalsChanged,
} from "./zapTotals.js";
import { formatViewCount } from "./viewCounter.js";
import { FEATURE_ZAP_TALLY } from "./constants.js";
let unsub = null;
function teardown() {
if (typeof unsub === "function") {
try {
unsub();
} catch (error) {
// best effort
}
unsub = null;
}
}
export function teardownChannelZapTotal() {
teardown();
}
export function wireChannelZapTotal(pubkey, { document: doc = globalThis.document } = {}) {
teardown();
const el = doc && typeof doc.getElementById === "function"
? doc.getElementById("channelZapTotal")
: null;
if (!el) {
return;
}
const pointer = FEATURE_ZAP_TALLY ? profilePointer(pubkey) : null;
if (!pointer) {
el.classList.add("hidden");
return;
}
const render = () => {
let sats = 0;
try {
sats = getVideoZapTotalSnapshot(pointer) || 0;
} catch (error) {
sats = 0;
}
if (sats > 0) {
el.textContent = `⚡ ${formatViewCount(sats)} sats zapped`;
el.classList.remove("hidden");
} else {
el.classList.add("hidden");
}
};
requestVideoZapTotal(pointer); // primes the store (also schedules the fetch)
render();
unsub = onZapTotalsChanged(render);
}
export default wireChannelZapTotal;