Social layer: leaderboard (week/month/all) + six badges, /leaders and /promo pages, share panel and rank on the board, ?r= referral cookie -> member's IAP join link, OG tags, banner set served through the curtain
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ const store = require('./lib/store');
|
||||
const sso = require('./lib/sso');
|
||||
const missions = require('./lib/missions');
|
||||
const rewards = require('./lib/rewards');
|
||||
const social = require('./lib/social');
|
||||
const faucet = require('./lib/faucet');
|
||||
|
||||
const PORT = Number(process.env.PORT || 3000);
|
||||
@@ -48,6 +49,7 @@ async function notify(kind, p) {
|
||||
}
|
||||
// Marty's rule (2026-09-19): when the day's pool is spent, say so; claims reopen at midnight Central
|
||||
const SPENT_MSG = 'Today\u2019s POL pool is spent. No more claims today. Claims reopen at midnight Central.';
|
||||
function refOf(req) { const m = /(?:^|;\s*)ph\.ref=([^;]+)/.exec(req.headers.cookie || ''); const r = m ? decodeURIComponent(m[1]) : ''; return /^[A-Za-z0-9_.-]{1,40}$/.test(r) ? r : null; }
|
||||
function explorer() { return Number(process.env.HUNT_CHAIN_ID) === 80002 ? 'https://amoy.polygonscan.com' : 'https://polygonscan.com'; }
|
||||
|
||||
// ---- helpers -------------------------------------------------------------------------------
|
||||
@@ -115,9 +117,17 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
if (p === '/embed.js') return sendFile(res, path.join(PUBLIC_DIR, 'embed.js'), { 'Cache-Control': 'public, max-age=300', 'Access-Control-Allow-Origin': '*' });
|
||||
// the coin on the code pill, fetched by mission-site visitors who hold no curtain pass
|
||||
if (p === '/img/coin-sm.png' || p === '/img/coin.png') return sendFile(res, path.join(PUBLIC_DIR, p.slice(1)), { 'Cache-Control': 'public, max-age=86400' });
|
||||
if (p === '/img/coin-sm.png' || p === '/img/coin.png' || p === '/img/og.jpg' || /^\/promo\/polhunter-\d+x\d+\.(jpg|png)$/.test(p)) return sendFile(res, path.join(PUBLIC_DIR, p.slice(1)), { 'Cache-Control': 'public, max-age=86400' });
|
||||
|
||||
if (curtained(req, res, u)) return;
|
||||
// a member's share link: /?r=<their IAP username or id> becomes a 30-day cookie; the landing then
|
||||
// sends sign-ups to instantadpay.com/join/<ref>, so IAP's own last-touch sponsor rule applies
|
||||
if (p === '/' && u.searchParams.get('r')) {
|
||||
const r = String(u.searchParams.get('r')).trim().slice(0, 40); u.searchParams.delete('r');
|
||||
const h = { Location: '/' + (u.searchParams.toString() ? '?' + u.searchParams : ''), 'Cache-Control': 'no-store' };
|
||||
if (/^[A-Za-z0-9_.-]{1,40}$/.test(r)) h['Set-Cookie'] = 'ph.ref=' + encodeURIComponent(r) + '; Path=/; Max-Age=2592000; SameSite=Lax; Secure';
|
||||
res.writeHead(302, h); return res.end();
|
||||
}
|
||||
|
||||
// ---- sign-in by hand-off from InstantAdPay
|
||||
if (p === '/auth') {
|
||||
@@ -132,7 +142,11 @@ const server = http.createServer(async (req, res) => {
|
||||
if (p === '/logout') { const s = sso.fromRequest(req); if (s) sso.endSession(s.sid); res.writeHead(302, { Location: '/', 'Set-Cookie': sso.clearCookie() }); return res.end(); }
|
||||
|
||||
// ---- public
|
||||
if (p === '/api/config') return json(res, 200, { name: 'PolHunter', signupsOpen: signupsOpen(), reward: rewards.settings(), iapUrl: 'https://instantadpay.com/my', explorer: explorer() });
|
||||
if (p === '/api/config') { const ref = refOf(req); return json(res, 200, { name: 'PolHunter', signupsOpen: signupsOpen(), reward: rewards.settings(), iapUrl: 'https://instantadpay.com/my', explorer: explorer(), ref, joinUrl: ref ? 'https://instantadpay.com/join/' + encodeURIComponent(ref) + '?from=polhunter' : 'https://instantadpay.com/?from=polhunter' }); }
|
||||
if (p === '/api/leaders') { const per = ['week', 'month', 'all'].includes(u.searchParams.get('period')) ? u.searchParams.get('period') : 'week'; return json(res, 200, { period: per, rows: social.leaderboard(per, 25) }, { 'Cache-Control': 'public, max-age=60' }); }
|
||||
if (p === '/api/badges') return json(res, 200, { badges: social.BADGES });
|
||||
if (p === '/leaders') return sendFile(res, path.join(PUBLIC_DIR, 'leaders.html'));
|
||||
if (p === '/promo') return sendFile(res, path.join(PUBLIC_DIR, 'promo.html'));
|
||||
if (p === '/api/ledger') { const t = rewards.totals(); return json(res, 200, { totals: t, recent: rewards.ledger(30).map(x => ({ who: x.username ? '@' + x.username : '#' + x.memberId, site: x.site, pol: x.pol, tx: x.tx, at: x.paidAt })) }); }
|
||||
|
||||
// ---- hunter (session required)
|
||||
@@ -141,7 +155,9 @@ const server = http.createServer(async (req, res) => {
|
||||
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();
|
||||
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() });
|
||||
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(),
|
||||
badges: social.badgesFor(me.memberId), 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)) } });
|
||||
}
|
||||
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.' });
|
||||
|
||||
Reference in New Issue
Block a user