Phone-run fixes: hash tokens, embed wakes on tab focus, dry faucet leaves drips due (+admin retry), short drip status, claim box wraps; one drip per wallet per mission
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -116,17 +116,19 @@ const server = http.createServer(async (req, res) => {
|
||||
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));
|
||||
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) })), drips: rewards.mine(me.memberId).slice(0, 20), faucet: { on: faucetOn } });
|
||||
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();
|
||||
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 } });
|
||||
}
|
||||
if (p === '/api/my/start' && req.method === 'POST') {
|
||||
const b = await readBody(req); const m = missions.get(String(b.missionId || '')); if (!m || !m.active) return json(res, 404, { error: 'That mission is not open.' });
|
||||
if (!me.wallet) return json(res, 400, { error: 'Link a wallet on InstantAdPay first so the drip has somewhere to land, then open PolHunter again.' });
|
||||
if (rewards.completed(me.memberId, m.id)) return json(res, 400, { error: 'You already completed this one.' });
|
||||
if (rewards.completed(me.memberId, m.id, me.wallet)) return json(res, 400, { error: 'You already completed this one.' });
|
||||
if (limited('start:' + me.memberId, 20, 3600000)) return json(res, 429, { error: 'Easy. Twenty starts an hour is plenty.' });
|
||||
const t = missions.issue(me.memberId, m.id);
|
||||
// a mission URL may place the token itself with {token} (a Telegram Mini App takes it in
|
||||
// ?startapp=, not as our own query string); otherwise it is appended as ?ph=
|
||||
const url = m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t;
|
||||
// the token rides in the hash: a server never sees it, so no redirect or canonical rewrite can lose it
|
||||
const url = m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + '#ph=' + t.t;
|
||||
return json(res, 200, { ok: true, token: t.t, url, dwell: m.dwell || 30, expires: t.exp });
|
||||
}
|
||||
if (p === '/api/my/submit' && req.method === 'POST') {
|
||||
@@ -134,7 +136,7 @@ const server = http.createServer(async (req, res) => {
|
||||
if (limited('submit:' + me.memberId, 30, 3600000)) return json(res, 429, { error: 'Too many tries. Take a breath.' });
|
||||
const c = missions.check(String(b.token || ''), me.memberId, b.code); if (c.error) return json(res, 400, { error: c.error });
|
||||
const m = missions.get(c.rec.missionId); if (!m) return json(res, 404, { error: 'That mission is gone.' });
|
||||
if (rewards.completed(me.memberId, m.id)) return json(res, 400, { error: 'You already completed this one.' });
|
||||
if (rewards.completed(me.memberId, m.id, me.wallet)) return json(res, 400, { error: 'You already completed this one.' });
|
||||
const g = rewards.grant(me, m); if (g.error) return json(res, 400, g);
|
||||
return json(res, 200, { ok: true, pol: g.rec.pol, queued: g.queued, message: g.queued ? 'Found it. Today’s POL is spoken for, so yours is queued and pays out next.' : 'Found it. ' + fmt(g.rec.pol) + ' POL is on its way to your wallet.' });
|
||||
}
|
||||
@@ -156,10 +158,11 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
if (p === '/api/admin/mission' && req.method === 'DELETE') { const b = await readBody(req); missions.remove(String(b.id || '')); return json(res, 200, { ok: true, missions: missions.list() }); }
|
||||
if (p === '/api/admin/settings' && req.method === 'POST') { const b = await readBody(req); const patch = {}; for (const k of ['minPol', 'maxPol', 'dailyCapPol', 'lowBalancePol']) if (b[k] != null && Number(b[k]) >= 0) patch[k] = Number(b[k]); return json(res, 200, { ok: true, settings: rewards.setSettings(patch) }); }
|
||||
if (p === '/api/admin/drip/retry' && req.method === 'POST') { const b = await readBody(req); rewards.mark(String(b.id || ''), { status: 'due', error: null }); return json(res, 200, { ok: true }); }
|
||||
if (p === '/api/admin/faucet/tick' && req.method === 'POST') { const r = await faucet.tick(notify); return json(res, 200, Object.assign(r, { balance: await faucet.balance() })); }
|
||||
if (p === '/api/admin/embed-test') { // mint a token for any mission so the embed can be tried without a hunter
|
||||
const m = missions.get(String(u.searchParams.get('id') || '')); if (!m) return json(res, 404, { error: 'no such mission' });
|
||||
const t = missions.issue(0, m.id); return json(res, 200, { url: m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + (m.url.includes('?') ? '&' : '?') + 'ph=' + t.t, token: t.t, dwell: m.dwell });
|
||||
const t = missions.issue(0, m.id); return json(res, 200, { url: m.url.includes('{token}') ? m.url.replace('{token}', t.t) : m.url + '#ph=' + t.t, token: t.t, dwell: m.dwell });
|
||||
}
|
||||
return json(res, 404, { error: 'No such admin call.' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user