Viral links: any public page + ?ref=<username|code|id> sets the same last-touch sponsor cookie as /join, counts under a 'page' hook in link stats, then redirects to the clean URL; Promo tools > Viral links builder (pages list + paste any address, copy + share); blog posts show a signed-in member's own link; chatbot answer; QA walk proves redirect + cookie

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-21 05:54:55 -05:00
parent 337d5dd556
commit e2f88dad42
8 changed files with 113 additions and 5 deletions
+2
View File
@@ -22,6 +22,8 @@ function enabled() { return !!key(); }
// ---- canned answers: instant, free, always in voice ----
const KNOWLEDGE_DATE = '2026-09-18'; // bump whenever the canned answers / prompt are brought up to date
const CANNED = [
{ re: /(viral link|any page.{0,30}(link|referr)|blog.{0,40}(referral|my link|credit)|(referral|invite|my) link.{0,30}(blog|article|any page)|\?ref=|ref=)/i,
a: 'Any page on instantadpay.com becomes your referral link when you add ?ref= and your username to the end, for example instantadpay.com/blog/what-is-a-feeder-program?ref=yourname. Whoever opens it is tagged to you for 30 days, exactly like your invite link, so if they join from any page on the site the credit is yours. Promo tools > Viral links builds these links for you, and every blog article shows your link when you are signed in.' },
{ re: /(five dollar friday|5 dollar friday|\$5 friday|friday (bonus|promo|deal|special)|what happens on friday)/i,
a: 'Five Dollar Friday: every Friday (Central time), any ad package from $5 up earns +20% bonus credits, added the moment the purchase settles and labelled in your credit log. The first one is Friday, September 25. Every pack pays its sponsor line in the same on-chain transaction, so when the whole network buys on the same day every line gets paid at once. Five Fridays in a row earn the Five Fridays badge. Buy under My account > Buy packages.' },
// 2026-09-18: sessions were shortened to 4h on purpose. Members WILL ask why they keep being
+1 -1
View File
@@ -189,7 +189,7 @@ async function linkStats(email) {
const now = Date.now();
const views = await impl().views(tokens, 0);
const joined = await accounts.listByReferrer(tokens);
const ANG = ['', 'instant', 'adspend', 'free', 'ledger', 'two'];
const ANG = ['', 'instant', 'adspend', 'free', 'ledger', 'two', 'page']; // page = a viral link (any page + ?ref=)
const rows = {};
for (const k of ANG) rows[k] = { angle: k || 'plain', views30: 0, views: 0, joins: 0, buyers: 0 };
const src = {};
+17
View File
@@ -1,2 +1,19 @@
// public blog pages: the shared nav (with wallet status) and footer, nothing else
(function () { try { IAP.renderNav(location.pathname.indexOf('/leaderboard') === 0 ? 'leaderboard' : 'blog'); } catch (e) {} })();
// Viral links (2026-09-21): a signed-in member sees this article's address with their own ?ref= on the
// end, and the share buttons carry it too. Whoever opens it is tagged to them like an invite link.
(async function () {
try {
const share = document.querySelector('p.share'); if (!share) return;
const me = await (await fetch('/api/me')).json();
const tok = me && me.signedIn && (me.username || me.refCode); if (!tok) return;
const link = 'https://instantadpay.com' + location.pathname + '?ref=' + encodeURIComponent(tok);
share.querySelectorAll('a').forEach(a => { a.href = a.href.replace(encodeURIComponent('https://instantadpay.com' + location.pathname), encodeURIComponent(link)); });
const bar = document.createElement('div');
bar.id = 'viralBar';
bar.style.cssText = 'margin-top:18px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;background:var(--card, rgba(255,255,255,.03));display:flex;gap:10px;align-items:center;flex-wrap:wrap';
bar.innerHTML = '<div style="flex:1;min-width:220px"><div class="eyebrow" style="margin:0 0 4px">Your link for this article</div><div class="mono small" style="overflow-wrap:anywhere">' + link.replace(/[&<>]/g, '') + '</div><div class="muted small" style="margin-top:4px">Share this and anyone who joins from it is yours, same as your invite link.</div></div><button class="btn small" type="button">Copy link</button>';
bar.querySelector('button').onclick = async () => { try { await navigator.clipboard.writeText(link); IAP.status('Link copied.', 'ok'); } catch (e) { IAP.status('Copy failed.', 'bad'); } };
share.parentNode.insertBefore(bar, share);
} catch (e) {}
})();
+2 -2
View File
@@ -1523,7 +1523,7 @@
const rows = (r.angles || []).filter(a => a.views || a.joins || a.buyers);
if (!rows.length) { t.innerHTML = '<tr><td class="muted small">No views yet. Share your link and the numbers start here.</td></tr>'; return; }
t.innerHTML = '<tr><th>Hook</th><th>Views (30d)</th><th>Views (all)</th><th>Joined</th><th>Qualifying buyers</th></tr>'
+ rows.map(a => '<tr><td>' + esc(a.angle === 'plain' ? 'plain link' : '?v=' + a.angle) + '</td><td class="mono">' + a.views30 + '</td><td class="mono">' + a.views + '</td><td class="mono">' + a.joins + '</td><td class="mono">' + a.buyers + '</td></tr>').join('');
+ rows.map(a => '<tr><td>' + esc(a.angle === 'plain' ? 'plain link' : a.angle === 'page' ? 'any page (viral link)' : '?v=' + a.angle) + '</td><td class="mono">' + a.views30 + '</td><td class="mono">' + a.views + '</td><td class="mono">' + a.joins + '</td><td class="mono">' + a.buyers + '</td></tr>').join('');
const st = $('linkSrcTable');
if (st) {
const src = (r.sources || []).filter(x => x.views || x.joins);
@@ -1887,7 +1887,7 @@
if ($('tkGo')) { $('tkGo').addEventListener('click', tkGenerate); $('tkAgain').addEventListener('click', tkGenerate); $('tkCopy').addEventListener('click', async () => { try { await navigator.clipboard.writeText($('tkText').value); IAP.status('Copied.', 'ok'); } catch (e) { $('tkText').select(); } }); $('tkSendBc').addEventListener('click', tkSendBroadcast); }
// promo tools: pill menu switches between posts / swipes / banners / wall / videos
function setPromoSub(name) {
const ids = ['toolkit', 'posts', 'text', 'swipe', 'banners', 'wall', 'objections', 'videos'];
const ids = ['toolkit', 'posts', 'text', 'swipe', 'banners', 'wall', 'objections', 'videos', 'viral'];
if (name === 'toolkit') loadToolkit();
if (!ids.includes(name)) name = 'posts';
ids.forEach(id => { const el = $('promo-' + id); if (el) el.hidden = id !== name; });
+33
View File
@@ -198,6 +198,39 @@ window.IAPPromo = (function () {
vw.appendChild(card);
}
}
// viral link builder: any page on the site + ?ref=<you> (same cookie as the invite link)
const vp = $('viralPage');
if (vp && token && vp.dataset.filled !== token) {
vp.dataset.filled = token;
const origin = 'https://instantadpay.com', enc = encodeURIComponent;
const build = raw => {
let s = String(raw || '').trim(); if (!s) return '';
if (!/^https?:\/\//i.test(s)) s = origin + (s.charAt(0) === '/' ? '' : '/') + s;
let x; try { x = new URL(s); } catch (e) { return ''; }
if (!/(^|\.)instantadpay\.com$/i.test(x.hostname) || /^\/(join|from|api|admin)(\/|$)/.test(x.pathname)) return '';
x.searchParams.delete('ref'); x.searchParams.set('ref', token);
return origin + x.pathname + x.search;
};
const render = () => {
const typed = $('viralUrl').value.trim();
const l = build(typed || vp.value);
$('viralLink').textContent = l || (typed ? 'That is not a page on instantadpay.com.' : '…');
const sh = $('viralShare'); sh.innerHTML = '';
if (!l) return;
const title = (!typed && vp.selectedOptions[0]) ? vp.selectedOptions[0].textContent.replace(/^Article: /, '') : 'InstantAdPay';
[linkBtn('https://x.com/intent/tweet?url=' + enc(l) + '&text=' + enc(title), 'X'),
linkBtn('https://www.facebook.com/sharer/sharer.php?u=' + enc(l), 'Facebook'),
linkBtn('https://t.me/share/url?url=' + enc(l) + '&text=' + enc(title), 'Telegram'),
linkBtn('https://wa.me/?text=' + enc(title + ' ' + l), 'WhatsApp'),
linkBtn('https://www.linkedin.com/sharing/share-offsite/?url=' + enc(l), 'LinkedIn'),
linkBtn('mailto:?subject=' + enc(title) + '&body=' + enc(l), 'Email')].forEach(b => sh.appendChild(b));
if (navigator.share) { const b = document.createElement('button'); b.className = 'btn small sec'; b.type = 'button'; b.textContent = 'Share'; b.onclick = async () => { try { await navigator.share({ title, url: l }); } catch (e) {} }; sh.appendChild(b); }
};
fetch('/api/pages').then(r => r.json()).then(r => { vp.innerHTML = (r.pages || []).map(pg => '<option value="' + esc(pg.path) + '">' + esc(pg.title) + '</option>').join(''); render(); }).catch(() => { vp.innerHTML = '<option value="/">Home page</option>'; render(); });
vp.onchange = () => { $('viralUrl').value = ''; render(); };
$('viralUrl').oninput = render;
$('viralCopy').onclick = async () => { const l = $('viralLink').textContent; if (!/^https:/.test(l)) return; try { await navigator.clipboard.writeText(l); status('Link copied.', 'ok'); } catch (e) { status('Copy failed.', 'bad'); } };
}
// objections
const ob = $('promoObjections');
if (ob && ob.dataset.filled !== link) {
+13 -1
View File
@@ -744,6 +744,18 @@
<button class="chip-t" data-promo="wall" type="button" role="tab">Banner wall</button>
<button class="chip-t" data-promo="objections" type="button" role="tab">Objections</button>
<button class="chip-t" data-promo="videos" type="button" role="tab">Videos</button>
<button class="chip-t" data-promo="viral" type="button" role="tab">Viral links</button>
</div>
<div class="card promo-sec" id="promo-viral" hidden>
<div class="card-head"><h3>Viral link builder</h3><span class="sub">any page on the site becomes your link</span></div>
<p class="muted small" style="margin:0 0 10px">Pick a page or paste any instantadpay.com address. The link you get carries your name on the end. Whoever opens it is tagged to you for 30 days, exactly like your invite link, so a blog article you share can bring you a member.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px">
<label class="small">Page<select id="viralPage" style="width:100%"></select></label>
<label class="small">Or paste any page address<input id="viralUrl" type="text" placeholder="https://instantadpay.com/blog/..." autocomplete="off" style="width:100%"></label>
</div>
<div class="promo-strip" style="margin-top:10px;padding:0"><div><span class="eyebrow" style="margin:0 0 4px">Your viral link</span><div class="mono small" id="viralLink" style="overflow-wrap:anywhere">…</div></div><button class="btn small" id="viralCopy" type="button">Copy link</button></div>
<div id="viralShare" class="angle-btns" style="margin-top:10px"></div>
<p class="muted small" style="margin:10px 0 0">Views and joins from these links show under Link stats as the "any page" hook. Last touch still wins: the most recent link a person opened is the one that gets the credit.</p>
</div>
<div class="card promo-sec" id="promo-toolkit" hidden>
<div class="card-head"><h3>Your toolkit</h3><span class="sub" id="tkTierLine"></span></div>
@@ -1040,7 +1052,7 @@
</div>
<script src="/assets/common.js?v=20260916a"></script>
<script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260921a"></script>
<script src="/assets/my.js?v=20260924b"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
+16
View File
@@ -114,6 +114,22 @@ if (MODE === 'member' || MODE === 'all') {
}
await page.click('.bo-menu [data-pane="promo"]'); await page.waitForTimeout(800);
if (!(await page.$$('#promoPosts .promo-block')).length) note('bug', L + 'promo', 'no post cards rendered');
// viral links: the builder renders a ?ref= link; any page + ?ref=<member> redirects clean and sets the sponsor cookie; an unknown ref sets nothing
await page.click('.promo-pills [data-promo="viral"]'); await page.waitForTimeout(1200);
const vl = await page.$eval('#viralLink', e => e.textContent).catch(() => '');
if (!/^https:\/\/instantadpay\.com\/.*[?&]ref=[a-z0-9_]+$/i.test(vl)) note('bug', L + 'promo/viral', 'builder link not rendered: ' + vl);
const me = await page.evaluate(() => fetch('/api/me').then(r => r.json()));
const tok = me.username || me.refCode;
const rr = await page.request.get(LOCAL + '/blog?ref=' + tok + '&x=1', { maxRedirects: 0 });
const sc = (rr.headersArray().filter(h => h.name.toLowerCase() === 'set-cookie').map(h => h.value)).join('; ');
if (rr.status() !== 302 || rr.headers()['location'] !== '/blog?x=1') note('bug', L + 'viral/redirect', 'expected 302 to /blog?x=1, got ' + rr.status() + ' ' + rr.headers()['location']);
if (!new RegExp('iap\\.sponsor=' + tok + ';').test(sc) || !/iap\.angle=page;/.test(sc)) note('bug', L + 'viral/cookie', 'sponsor/angle cookie not set: ' + sc);
const ru = await page.request.get(LOCAL + '/?ref=nobody_zz9', { maxRedirects: 0 });
const su = (ru.headersArray().filter(h => h.name.toLowerCase() === 'set-cookie').map(h => h.value)).join('; ');
if (ru.status() !== 302 || /iap\.sponsor=/.test(su)) note('bug', L + 'viral/unknown', 'unknown ref must redirect without a sponsor cookie: ' + ru.status() + ' ' + su);
const rj = await page.request.get(LOCAL + '/join/' + tok + '?ref=' + tok, { maxRedirects: 0 });
if (rj.status() !== 200) note('bug', L + 'viral/join', '/join keeps its own ?ref handling, got ' + rj.status());
console.log('ok viral links: builder + redirect + cookie');
const chat = await page.$('#chatMenuBtn'); if (chat) { await chat.click(); await page.waitForTimeout(800); await domChecks(page, L + 'messages'); flush(bag, L + 'messages'); }
const lo = await page.$('#logoutLink'); if (lo) { await lo.click(); await page.waitForTimeout(1000); }
if (!(await page.$('#authArea:not([hidden])'))) note('bug', L + 'logout', 'auth card not shown after log out');
+29 -1
View File
@@ -1076,6 +1076,28 @@ const server = http.createServer(async (req, res) => {
// -- traffic log: public page views by referring domain (admin > Traffic)
if (req.method === 'GET' && (TRAFFIC_PAGES.has(p) || /^\/(join|from|wall|blog)\/[^/]+$/.test(p))) traffic.hit(p.startsWith('/blog/') ? '/blog/*' : p, req.headers.referer, req.headers['user-agent']);
// -- viral links (Marty, 2026-09-21): ANY public page + ?ref=<username|code|id> is that member's
// referral link. Same 30-day last-touch sponsor cookie as /join, the view counts under the
// "page" hook in link stats, then a redirect to the clean URL so canonical links stay clean
// for search engines. /join, /from and the APIs keep their own meaning of ?ref.
if ((req.method === 'GET' || req.method === 'HEAD') && u.searchParams.has('ref') && !p.startsWith('/api/') && !p.startsWith('/admin') && !/^\/(join|from)\//.test(p) && !/\.[a-z0-9]{2,5}$/i.test(p)) {
const raw = String(u.searchParams.get('ref') || '').trim().toLowerCase().slice(0, 40);
const tok = JOIN_ALIASES[raw] || raw;
u.searchParams.delete('ref');
const clean = p + (u.searchParams.toString() ? '?' + u.searchParams.toString() : '');
let known = false;
if (/^[a-z0-9_]{1,20}$/.test(tok)) { try { known = !!((await accounts.byCode(tok)) || (await accounts.byUsername(tok)) || (/^\d+$/.test(tok) && (await accounts.byMemberId(Number(tok))))); } catch (e) {} }
const set = [];
if (known) {
const cookieTail = `; Path=/; SameSite=Lax; Max-Age=${30 * 24 * 3600}${IS_PROD ? '; Secure' : ''}`;
set.push('iap.sponsor=' + tok + cookieTail); // last touch wins, exactly like /join
set.push('iap.angle=page' + cookieTail); // the join shows under the "any page" hook
if (!parseCookies(req)['iap.ref']) set.push('iap.ref=' + encodeURIComponent(coach.refHost(req.headers.referer)) + cookieTail); // first-touch source
if (req.method === 'GET') coach.recordView(tok, 'page', req.headers.referer);
}
res.writeHead(302, baseHeaders(Object.assign({ Location: clean, 'Cache-Control': 'no-store' }, set.length ? { 'Set-Cookie': set } : {})));
return res.end();
}
// -- join links: /join/<memberId or share code> — LAST-touch cookie (Marty,
// 2026-09-10): the link a visitor opened most recently is the sponsor shown
// and used, and it locks the moment the account is created (accounts.ensure
@@ -1129,6 +1151,12 @@ const server = http.createServer(async (req, res) => {
// -- public API
if (p === '/api/friday' && req.method === 'GET') return json(res, 200, friday.view(), { 'Cache-Control': 'public, max-age=60' });
// -- the pages a member can turn into a viral link (promo tools > Viral links)
if (p === '/api/pages' && req.method === 'GET') {
const pages = [{ path: '/', title: 'Home page' }, { path: '/blog', title: 'Blog' }, { path: '/earning', title: 'How earning works' }, { path: '/ledger', title: 'Live ledger' }, { path: '/leaderboard', title: 'Leaderboard' }, { path: '/contract', title: 'The contract' }, { path: '/plays', title: 'Team-building plays' }, { path: '/wallets', title: 'Wallet guide' }, { path: '/whats-new', title: "What's new" }, { path: '/shorts', title: 'Shorts' }];
try { for (const b of (await blog.listPublished()).slice(0, 40)) pages.push({ path: '/blog/' + b.slug, title: 'Article: ' + b.title, cover: b.cover || null }); } catch (e) {}
return json(res, 200, { pages });
}
if (p === '/api/config' && req.method === 'GET') {
const c = chain.getConfig();
// public copy of the site settings: never anything that looks like a credential
@@ -3144,7 +3172,7 @@ const server = http.createServer(async (req, res) => {
+ '<style>.bp{max-width:560px;text-align:center;padding:40px 16px 60px}.bp img{width:100%;max-width:520px;border-radius:18px;border:1px solid var(--line);box-shadow:0 20px 60px rgba(0,0,0,.45)}.bp h1{font-size:clamp(24px,5vw,34px);margin:22px 0 8px}.bp p{color:var(--muted);max-width:44ch;margin:0 auto 20px}</style></head>'
+ '<body><div class="wrap bp"><img src="' + img + '" alt="' + esc(label) + ' badge for @' + esc(un) + '"><h1>@' + esc(un) + ' unlocked <em>' + label + '</em></h1><p>' + esc(sub.charAt(0).toUpperCase() + sub.slice(1)) + '. On InstantAdPay every ad package that sells pays the sponsor in the same transaction, straight to their wallet, on-chain.</p>'
+ '<a class="btn" href="/join/' + esc(un) + '">Join @' + esc(un) + '\u2019s line free</a><p class="small" style="margin-top:26px">Advertising with a performance referral program. Not an investment; no income is guaranteed.</p></div>'
+ '<script src="/assets/common.js?v=20260913a"></script><script src="/assets/blog-page.js?v=20260912a"></script></body></html>';
+ '<script src="/assets/common.js?v=20260913a"></script><script src="/assets/blog-page.js?v=20260921a"></script></body></html>';
res.writeHead(200, baseHeaders({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=300' }));
return res.end(html);
}