// PolHunter: gamified visits across the network, paid in POL. // // This is the shell the hunt engine will grow inside. What it does today is refuse to do anything // dangerous by default, because the day before it was built a test area in this same network // emailed 213 real people. Three gates, all default-deny, all read from the environment: // // OUTBOUND=on nothing can send mail (or anything else outward) without it. There is no // mailer in this app yet; when one arrives it must check outbound() first. // SIGNUPS=open the sign-in door stays shut until this is set. Hunters will sign in with // their InstantAdPay account; that bridge does not exist yet either. // CURTAIN= while set, every request gets a contentless "Coming soon" page unless the // browser has visited ?k= once. Lift it by unsetting the variable. // // A missing variable means silence, never delivery. 'use strict'; const http = require('http'); const fs = require('fs'); const path = require('path'); const PORT = Number(process.env.PORT || 3000); const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data'); const PUBLIC_DIR = path.join(__dirname, 'public'); const CURTAIN = String(process.env.CURTAIN || '').trim(); const outbound = () => process.env.OUTBOUND === 'on'; const signupsOpen = () => process.env.SIGNUPS === 'open'; try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch (e) {} const CURTAIN_PAGE = ` Coming soon

Coming soon

This site is still being built.

`; function curtained(req, res, u) { if (!CURTAIN) return false; if (u.searchParams.get('k') === CURTAIN) { u.searchParams.delete('k'); res.writeHead(302, { 'Set-Cookie': 'ph.pass=' + encodeURIComponent(CURTAIN) + '; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax; Secure', Location: u.pathname + (u.searchParams.toString() ? '?' + u.searchParams : ''), 'Cache-Control': 'no-store' }); res.end(); return true; } const m = /(?:^|;\s*)ph\.pass=([^;]*)/.exec(req.headers.cookie || ''); if (m && decodeURIComponent(m[1]) === CURTAIN) return false; res.writeHead(503, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', 'X-Robots-Tag': 'noindex, nofollow' }); res.end(req.method === 'HEAD' ? '' : CURTAIN_PAGE); return true; } const TYPES = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': 'application/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.json': 'application/json' }; function sendFile(res, file) { fs.readFile(file, (err, buf) => { if (err) { res.writeHead(404, { 'Content-Type': 'text/plain' }); return res.end('Not found'); } res.writeHead(200, { 'Content-Type': TYPES[path.extname(file)] || 'application/octet-stream', 'Cache-Control': 'no-store' }); res.end(buf); }); } const json = (res, code, body) => { res.writeHead(code, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(body)); }; const server = http.createServer((req, res) => { const u = new URL(req.url, 'http://x'); const p = u.pathname; if (p === '/health') return json(res, 200, { ok: true, outbound: outbound(), signups: signupsOpen(), curtain: !!CURTAIN }); if (curtained(req, res, u)) return; // nothing below runs for an uninvited visitor if (p === '/api/config') return json(res, 200, { name: 'PolHunter', signupsOpen: signupsOpen(), outbound: outbound() }); if (p.startsWith('/api/')) return json(res, 404, { error: 'Not built yet.' }); // static: the public dir, index for / const safe = path.normalize(p).replace(/^(\.\.[/\\])+/, ''); const file = path.join(PUBLIC_DIR, safe === '/' || safe === '\\' ? 'index.html' : safe); if (!file.startsWith(PUBLIC_DIR)) { res.writeHead(400); return res.end(); } sendFile(res, file); }); server.listen(PORT, () => console.log(`PolHunter on :${PORT} — outbound: ${outbound() ? 'ON' : 'OFF'} — sign-ups: ${signupsOpen() ? 'open' : 'CLOSED'} — curtain: ${CURTAIN ? 'up' : 'down'}`));