diff --git a/server.js b/server.js index 3584616..ebfea9f 100644 --- a/server.js +++ b/server.js @@ -62,8 +62,13 @@ async function notify(kind, p) { if (kind === 'paid') { const ok = await telegram('\u{1F3AF} PolHunter · ' + (p.username ? '@' + p.username : '#' + p.memberId) + ' found it on ' + p.site + ' and got ' + fmt(p.pol) + ' POL · verify\nHunt yours'); if (ok && p.id) rewards.mark(p.id, { posted: Date.now() }); return ok; } if (kind === 'low') return telegram('⚠️ PolHunter faucet is low: ' + fmt(p.balance) + ' POL left in ' + p.address + ' (alert threshold ' + fmt(p.threshold) + '). Top up from Receiver B.'); if (kind === 'failed') return telegram('❌ PolHunter · drip to #' + p.memberId + ' failed: ' + p.error); - if (kind === 'badge') { const b = social.BADGES.find(x => x.id === p.id); if (!b) return false; const who = social.nameOf(p.me); const file = await badge.render(p.id, p.me.username || '#' + p.me.memberId); const cap = '\u{1F3C6} PolHunter \u00b7 ' + (p.me.username ? '@' + p.me.username : '#' + p.me.memberId) + ' unlocked ' + b.name + ': ' + b.why + '\n' + SITE + '/b/' + who + '/' + p.id; if (!file) return telegram(cap); const ok = await telegramPhoto(file, cap); if (String(process.env.HUNT_TG_BADGE_GENERAL || 'on') === 'on') await telegramPhoto(file, cap, true); return ok; } + if (kind === 'badge') { const ok = await postBadge(p); if (ok) store.update('badges-posted', {}, all => { const k = String(p.me.memberId); all[k] = Array.from(new Set([...(all[k] || []), p.id])); return all; }); return ok; } if (kind === 'prize') { const medal = ['\u{1F947}', '\u{1F948}', '\u{1F949}']; return telegram('\u{1F3C6} PolHunter weekly prizes for the week of ' + p.week + '\n' + p.winners.map(w => (medal[w.rank - 1] || '#' + w.rank) + ' ' + w.who + ' \u00b7 ' + w.finds + ' finds \u00b7 ' + fmt(w.prizePol) + ' POL').join('\n') + '\nLeaderboard'); } + return false; +} +async function postBadge(p) { + { const b = social.BADGES.find(x => x.id === p.id); if (!b) return false; const who = social.nameOf(p.me); const file = await badge.render(p.id, p.me.username || '#' + p.me.memberId); const cap = '\u{1F3C6} PolHunter \u00b7 ' + (p.me.username ? '@' + p.me.username : '#' + p.me.memberId) + ' unlocked ' + b.name + ': ' + b.why + '\n' + SITE + '/b/' + who + '/' + p.id; if (!file) return telegram(cap); const ok = await telegramPhoto(file, cap); if (String(process.env.HUNT_TG_BADGE_GENERAL || 'on') === 'on') await telegramPhoto(file, cap, true); return ok; } + return false; } // Marty's rule (2026-09-19): when the day's pool is spent, say so; claims reopen at midnight Central const dailyMsg = d => 'That is your ' + ['', 'one', 'two', 'three', 'four', 'five'][d.limit] + ' for today. Fresh missions at midnight Central.'; @@ -286,6 +291,20 @@ async function postMissedFinds() { try { for (const p of rewards.unposted(POST_SINCE, 15)) { if (await notify('paid', p)) n++; else break; } } catch (e) { console.error('find sweep', e.message); } finally { sweeping = false; } if (n) console.log('posted', n, 'missed find(s) to Telegram'); + await postMissedBadges().catch(e => console.error('badge sweep', e.message)); + return n; +} +// badges of every hunter active since the cutoff that were never posted (gated, Telegram down, restart) +async function postMissedBadges() { + if (!outbound()) return 0; + const since = POST_SINCE; const recs = store.read('payouts', []).filter(x => (x.at || 0) >= since && x.status !== 'failed'); + const seen = new Map(); for (const r of recs) seen.set(r.memberId, { memberId: r.memberId, username: r.username || null }); + const posted = store.read('badges-posted', {}); let n = 0; + for (const me of seen.values()) { + const have = new Set(posted[String(me.memberId)] || []); + for (const b of social.badgesFor(me.memberId)) { if (have.has(b.id)) continue; if (n >= 10) return n; if (await notify('badge', { me, id: b.id })) n++; else return n; } + } + if (n) console.log('posted', n, 'missed badge(s) to Telegram'); return n; } setInterval(() => postMissedFinds().catch(() => {}), 2 * 60000); setTimeout(() => postMissedFinds().catch(() => {}), 20000);