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 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 10:52:21 -05:00
parent a85ee4cacf
commit d90f08d54a
+37 -1
View File
@@ -81,6 +81,7 @@ async function post() {
// every 10 minutes: post once a day at or after snapshotHourUtc (a restart never double-posts) // every 10 minutes: post once a day at or after snapshotHourUtc (a restart never double-posts)
async function tick() { async function tick() {
try { await milestoneTick(); } catch (e) { console.error('milestone', e.message); }
const sc = X.siteConfig(); const sc = X.siteConfig();
if (String(sc.snapshotEnabled || '1') !== '1' || !sc.telegramBotToken) return; if (String(sc.snapshotEnabled || '1') !== '1' || !sc.telegramBotToken) return;
const hour = Number(sc.snapshotHourUtc == null || sc.snapshotHourUtc === '' ? 14 : sc.snapshotHourUtc); 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); } 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} <b>' + n(mark) + ' members on InstantAdPay.</b>');
L.push(n(mark) + ' people have joined, and here is what that looks like right now: <b>' + n(s.wallets) + '</b> wallets linked, <b>' + n(s.payoutsOn) + '</b> with payouts switched on, <b>' + n(s.lifeBuys) + '</b> purchases, and <b>' + pol(s.lifePaidWei) + ' POL</b> 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('<a href="https://instantadpay.com/ledger">Live ledger</a>' + (cta ? ' \u00b7 <a href="' + cta + '">Join free</a>' : ''));
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 };