Verified visits (7th ad format): guaranteed unique human-visit packs

- Buy N verified visits flat up-front; each is a distinct member, dwell + captcha verified
- Verified visits earn sub-tab: open site (new tab), dwell, human-check, earn
- Per-viewer dedup (visit_seen unique), daily cap, completes at N; single-use tokens

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-06 09:21:59 -05:00
parent f30a462ca8
commit cb2542fca5
12 changed files with 188 additions and 31 deletions
+43
View File
@@ -35,6 +35,7 @@ fs.mkdirSync(UPLOADS_DIR, { recursive: true });
const uploadCounts = new Map(); // email:day -> uploads today
const gauntletTokens = new Map(); // email -> welcome-tour token (server-clock dwell floor)
const videoTokens = new Map(); // email -> watch-to-earn video token (server-clock watch floor)
const visitTokens = new Map(); // email -> verified-visit token (dwell + captcha floor)
// walk the referral chain upward via sponsorRef (code/username/member id)
async function uplineSlides(email, depth = 3) {
const out = [];
@@ -767,6 +768,48 @@ const server = http.createServer(async (req, res) => {
const status = await ads.recordVideoWatch(s.email);
return json(res, 200, { ok: true, credited: tier.reward, status });
}
// -- verified visits: view a member's site (new tab) for the dwell, pass a
// human check, and it counts as one guaranteed unique visit for the pack
if (p === '/api/my/visits' && req.method === 'GET') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const status = await ads.visitStatus(s.email);
if (status.count >= status.cap) return json(res, 200, { ad: null, status });
const ad = await ads.serveVisit(s.email);
if (!ad) return json(res, 200, { ad: null, status });
const token = crypto.randomBytes(16).toString('hex');
visitTokens.set(s.email, { token, ts: Date.now(), id: ad.id });
return json(res, 200, { ad, token, status });
}
if (p === '/api/my/visitchallenge' && req.method === 'GET') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const t = visitTokens.get(s.email);
if (!t || t.token !== String(u.searchParams.get('token') || '')) return json(res, 400, { error: 'That visit is no longer open.' });
const dwellMs = (ads.rates().visitDwellSeconds || 8) * 1000;
const age = Date.now() - t.ts;
if (age < dwellMs - 400) return json(res, 200, { early: true, wait: Math.ceil((dwellMs - age) / 1000) });
if (age > 5 * 60 * 1000) { visitTokens.delete(s.email); return json(res, 400, { error: 'That visit went stale. Load a fresh one.' }); }
const pick = CAPTCHA.slice().sort(() => Math.random() - 0.5).slice(0, 5);
const answer = Math.floor(Math.random() * pick.length);
t.challenge = { answer };
return json(res, 200, { prompt: pick[answer][1], options: pick.map(x => x[0]) });
}
if (p === '/api/my/visitdone' && req.method === 'POST') {
const s = await auth.fromRequest(req);
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
const b = await readBody(req);
const t = visitTokens.get(s.email);
if (!t || t.token !== String(b.token || '')) return json(res, 400, { error: 'That visit did not check out. Load the next one.' });
const dwellMs = (ads.rates().visitDwellSeconds || 8) * 1000;
if (Date.now() - t.ts < dwellMs - 400) return json(res, 400, { error: 'Give the site the full visit first.' });
if (!t.challenge) return json(res, 400, { error: 'Finish the quick check first.', retry: true });
if (Number(b.answer) !== t.challenge.answer) { t.challenge = null; return json(res, 400, { error: 'Wrong pick.', retry: true }); }
visitTokens.delete(s.email);
const r = await ads.completeVisit(s.email, t.id);
if (r.error) return json(res, 400, r);
return json(res, 200, Object.assign(r, { status: await ads.visitStatus(s.email) }));
}
// -- onsite solo ads: member inbox with read rewards
if (p === '/api/my/inbox' && req.method === 'GET') {
const s = await auth.fromRequest(req);