From d90f08d54aa112da5f5c8178fc6aeac29604ea85 Mon Sep 17 00:00:00 2001 From: martbost Date: Sat, 19 Sep 2026 10:52:21 -0500 Subject: [PATCH] Milestone posts: a celebratory Telegram post the moment membership crosses 500 (then 1k, 2.5k, 5k, 10k), once per mark Co-Authored-By: Claude Fable 5.1 --- snapshot.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/snapshot.js b/snapshot.js index 0c95317..b85e770 100644 --- a/snapshot.js +++ b/snapshot.js @@ -81,6 +81,7 @@ async function post() { // every 10 minutes: post once a day at or after snapshotHourUtc (a restart never double-posts) async function tick() { + try { await milestoneTick(); } catch (e) { console.error('milestone', e.message); } const sc = X.siteConfig(); if (String(sc.snapshotEnabled || '1') !== '1' || !sc.telegramBotToken) return; const hour = Number(sc.snapshotHourUtc == null || sc.snapshotHourUtc === '' ? 14 : sc.snapshotHourUtc); @@ -92,4 +93,39 @@ async function tick() { async function preview() { const sc = X.siteConfig(); return compose(await gather(), sc.telegramCtaUrl); } -module.exports = { init, gather, compose, post, tick, preview }; +// ---- membership milestones (Marty, 2026-09-19): one celebratory post per mark, the moment it is crossed +const MILESTONES = [500, 1000, 2500, 5000, 10000]; +function composeMilestone(mark, s, cta) { + const L = []; + L.push('\u{1F389} ' + n(mark) + ' members on InstantAdPay.'); + L.push(n(mark) + ' people have joined, and here is what that looks like right now: ' + n(s.wallets) + ' wallets linked, ' + n(s.payoutsOn) + ' with payouts switched on, ' + n(s.lifeBuys) + ' purchases, and ' + pol(s.lifePaidWei) + ' POL paid to members in the same transactions the purchases came from. Every one of those payments is on the public ledger.'); + L.push('Thank you to every member who brought someone in. That is the whole engine, and it is working.'); + L.push('The next ' + n(mark) + ' start today.'); + L.push('Live ledger' + (cta ? ' \u00b7 Join free' : '')); + return L.join('\n\n'); +} +async function memberCount() { + if (!(X.db && X.db.enabled())) return 0; + const r = await X.db.q('SELECT COUNT(*) n FROM accounts'); return Number((r[0] || {}).n || 0); +} +// runs from tick(): announce the highest newly crossed mark, once, whatever snapshotEnabled says +async function milestoneTick() { + const sc = X.siteConfig(); if (!sc.telegramBotToken) return null; + const st = state(); const done = st.milestones || {}; + const count = await memberCount(); + const due = MILESTONES.filter(m => count >= m && !done[m]); + if (!due.length) return null; + const mark = due[due.length - 1]; + const text = composeMilestone(mark, await gather(), sc.telegramCtaUrl); + const targets = String(sc.snapshotTargets || 'feed,echo').split(',').map(x => x.trim()); + let sent = 0; + if (targets.includes('feed') && sc.telegramChatId) { if (await X.send(sc.telegramChatId, text, sc.telegramTopicId)) sent++; } + if (targets.includes('echo') && sc.telegramEchoChatId) { if (await X.send(sc.telegramEchoChatId, text, sc.telegramEchoTopicId)) sent++; } + for (const m of due) done[m] = Date.now(); // lower marks crossed in the same jump are marked too, so they never post late + st.milestones = done; setState(st); + console.log('milestone posted', mark, 'members', count, 'sent', sent); + return { ok: true, mark, count, sent, text }; +} +async function previewMilestone(mark) { const sc = X.siteConfig(); return composeMilestone(Number(mark) || 500, await gather(), sc.telegramCtaUrl); } + +module.exports = { init, gather, compose, post, tick, preview, milestoneTick, previewMilestone, memberCount, MILESTONES };