From f1e1886f48ffdd262ad10c9149651ee0f53ad4cc Mon Sep 17 00:00:00 2001 From: martbost Date: Sun, 20 Sep 2026 07:13:46 -0500 Subject: [PATCH] Missions reset with the Central day: a find blocks a mission only for that day (member id or wallet); tag reads Done today Co-Authored-By: Claude Fable 5.1 --- lib/rewards.js | 16 +++++++++++----- public/app.html | 2 +- public/app.js | 2 +- server.js | 3 +-- test/run.js | 5 +++++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/rewards.js b/lib/rewards.js index e7f183a..9995214 100644 --- a/lib/rewards.js +++ b/lib/rewards.js @@ -38,11 +38,17 @@ function paidToday(day) { return store.read('payouts', []).filter(p => p.day === day && p.status !== 'failed' && !p.prize).reduce((n, p) => n + p.pol, 0); } -// has this member completed this mission already? -// by member id, and also by wallet: two accounts sharing a wallet get one drip per mission between them +// has this member completed this mission TODAY (Central)? Missions reset at midnight Central with the codes and +// the pool (Marty, 2026-09-20: yesterday's finds showed as done today). Checked by member id, and also by wallet: +// two accounts sharing a wallet get one drip per mission per day between them. function completed(memberId, missionId, wallet) { - const w = wallet ? String(wallet).toLowerCase() : null; - return store.read('payouts', []).some(p => p.missionId === missionId && p.status !== 'failed' && (p.memberId === Number(memberId) || (w && p.wallet && String(p.wallet).toLowerCase() === w))); + const w = wallet ? String(wallet).toLowerCase() : null; const day = ctDay(); + return store.read('payouts', []).some(p => p.missionId === missionId && p.day === day && p.status !== 'failed' && !p.prize && (p.memberId === Number(memberId) || (w && p.wallet && String(p.wallet).toLowerCase() === w))); +} +// the mission ids this member (or wallet) has done today +function doneToday(memberId, wallet) { + const w = wallet ? String(wallet).toLowerCase() : null; const day = ctDay(); + return new Set(store.read('payouts', []).filter(p => p.day === day && p.status !== 'failed' && !p.prize && (p.memberId === Number(memberId) || (w && p.wallet && String(p.wallet).toLowerCase() === w))).map(p => p.missionId)); } // record a completion; decide paid-today vs queued @@ -96,4 +102,4 @@ function totals() { return { paid: paid.length, pol: Math.round(paid.reduce((n, p) => n + p.pol, 0) * 10000) / 10000, queued: all.filter(p => p.status === 'queued').length, today: paidToday(ctDay()), hunters: new Set(paid.map(p => p.memberId)).size }; } -module.exports = { settings, setSettings, draw, grant, completed, payable, mark, ledger, mine, totals, paidToday, pool, nextResetAt, daily, findsToday }; +module.exports = { settings, setSettings, draw, grant, completed, doneToday, payable, mark, ledger, mine, totals, paidToday, pool, nextResetAt, daily, findsToday }; diff --git a/public/app.html b/public/app.html index bcc1bf1..e164722 100644 --- a/public/app.html +++ b/public/app.html @@ -46,6 +46,6 @@
Rewards are for completed missions, not income. The daily pool is limited; when it is spent, claims close until midnight Central. Cryptocurrency involves risk of loss.
- + diff --git a/public/app.js b/public/app.js index 7847fa9..b297f24 100644 --- a/public/app.js +++ b/public/app.js @@ -31,7 +31,7 @@ const isOpen = open && open.missionId === m.id; const r = m.reward || {}; return '
' - + '' + (m.done ? 'Completed' : esc(m.site)) + '' + + '' + (m.done ? 'Done today' : esc(m.site)) + '' + '

' + esc(m.name) + '

' + esc(m.brief) + '

' + '

' + r.minPol + ' to ' + r.maxPol + ' POL · ' + m.dwell + 's on the site

' + (m.done ? '' : (board.pool && board.pool.spent && !isOpen) ? '
Closed until midnight Central
' : (board.daily && !board.daily.left && !isOpen) ? '
Back tomorrow
' : isOpen diff --git a/server.js b/server.js index fa72a01..38180ad 100644 --- a/server.js +++ b/server.js @@ -195,8 +195,7 @@ const server = http.createServer(async (req, res) => { if (p.startsWith('/api/my/')) { const me = sso.fromRequest(req); if (!me) return json(res, 401, { error: 'Open PolHunter from your InstantAdPay dashboard to sign in.' }); if (p === '/api/my/board') { - const done = new Set(rewards.mine(me.memberId).map(x => x.missionId)); - const wdone = me.wallet ? new Set(store.read('payouts', []).filter(x => x.wallet && x.wallet.toLowerCase() === me.wallet && x.status !== 'failed').map(x => x.missionId)) : new Set(); + const done = rewards.doneToday(me.memberId, me.wallet); const wdone = done; // today's finds, by member id or wallet return json(res, 200, { me: { memberId: me.memberId, username: me.username, wallet: me.wallet }, missions: missions.forMember(me.memberId).map(m => Object.assign(pubMission(m), { done: done.has(m.id) || wdone.has(m.id) })), drips: rewards.mine(me.memberId).slice(0, 20), faucet: { on: faucetOn }, pool: rewards.pool(), daily: rewards.daily(me.memberId), badges: social.badgesFor(me.memberId), badgeCards: (() => { const got = new Set(social.badgesFor(me.memberId).map(b => b.id)); const who = social.nameOf(me); return social.BADGES.map(b => ({ id: b.id, name: b.name, icon: b.icon, why: b.why, art: '/badges/badge-' + b.id + '.jpg', earned: got.has(b.id), page: got.has(b.id) ? SITE + '/b/' + who + '/' + b.id : null, image: got.has(b.id) ? SITE + '/badge-img/' + who + '/' + b.id + '.jpg' : null })); })(), rank: { week: social.rankOf(me.memberId, 'week'), month: social.rankOf(me.memberId, 'month'), all: social.rankOf(me.memberId, 'all') }, share: { link: social.shareLink(SITE, me), joinUrl: 'https://instantadpay.com/join/' + encodeURIComponent(String(me.username || me.memberId)) } }); diff --git a/test/run.js b/test/run.js index 6fac982..35dd4b6 100644 --- a/test/run.js +++ b/test/run.js @@ -62,6 +62,11 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); eq([ok.status, ok.body.pol >= 0.05 && ok.body.pol <= 1, ok.body.queued, ok.body.unlocked], [200, true, false, ['first', 'sweep']], 'the right code pays a drip in range, not queued, and unlocks First Find (and Full Sweep: the test has one site)'); const twice = await call('/api/my/submit', { method: 'POST', body: JSON.stringify({ token: t, code: code.code }) }); eq(twice.status, 400, 'the same mission cannot be claimed twice'); const b2 = await call('/api/my/board'); eq([b2.body.missions[0].done, b2.body.drips.length, b2.body.drips[0].status], [true, 1, 'due'], 'board: done, one drip due (faucet off, so it waits)'); + // missions reset with the Central day: a find dated yesterday no longer blocks the mission today + { const fs3 = require('fs'); const pf = path.join(DIR, 'payouts.json'); const all = JSON.parse(fs3.readFileSync(pf, 'utf8')); const yd = new Date(Date.now() - 86400000).toLocaleDateString('en-CA', { timeZone: 'America/Chicago' }); all[0].day = yd; fs3.writeFileSync(pf, JSON.stringify(all)); + const b2b = await call('/api/my/board'); eq(b2b.body.missions[0].done, false, 'yesterday’s find does not mark the mission done today'); + const again2 = await call('/api/my/start', { method: 'POST', body: JSON.stringify({ missionId: 'test-1' }) }); eq(again2.status, 200, 'the mission can be started again the next day'); + all[0].day = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Chicago' }); fs3.writeFileSync(pf, JSON.stringify(all)); } // the daily pool: once today's committed POL reaches the cap, the board says so and new starts are // refused until the next Central midnight (Marty, 2026-09-19)