diff --git a/server.js b/server.js index f336772..2996db7 100644 --- a/server.js +++ b/server.js @@ -497,6 +497,35 @@ async function handleApi(req,res,pathname){ return json(res,404,{error:'API endpoint not found'}); } +// Serve /my/:id and /join/:id with personalized Open Graph / Twitter tags so a +// shared personal referral link previews nicely (social scrapers don't run JS, +// so the per-ID meta must be in the static HTML). Strips any baked-in og/twitter +// tags and injects a fresh, ID-specific set using the branded card image. +function serveMemberPage(req,res,file,kind,id){ + let html; try{ html=fs.readFileSync(file,'utf8'); }catch(e){ return staticFile(req,res,path.join(PUBLIC_DIR,'404.html'),404); } + const base='https://rmcircle.saasy.top'; + const url=kind==='join'?`${base}/join/${id}`:`${base}/my/${id}`; + const title=kind==='join' + ? `You're invited to the RM Circle team by Member #${id}` + : `Member #${id}'s RM Circle team — live on the blockchain`; + const desc=kind==='join' + ? `A team-first crypto build where every payout is verified on Polygon. See Member #${id}'s real on-chain position and join their team. No income is guaranteed.` + : `Member #${id}'s live position, team, and payments — read straight from the blockchain. Get your 2, help your 2. No income is guaranteed.`; + const img=`${base}/og-card.jpg`; + const og=[ + ``, + ``, + ``, + ``, + ``, + ``, + `` + ].join(''); + html=html.replace(/]*>/gi,''); + html=html.replace(/(]*>)/i,`$1${og}`); + res.writeHead(200,securityHeaders({'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-cache'})); + res.end(html); +} const server=http.createServer(async(req,res)=>{ try{ const u=new URL(req.url,`http://${req.headers.host||'localhost'}`),pathname=decodeURIComponent(u.pathname); @@ -510,6 +539,10 @@ const server=http.createServer(async(req,res)=>{ const body=`${ok?'Unsubscribed':'Invalid link'}`; res.writeHead(200,securityHeaders({'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-store'}));return res.end(body); } + { let mj; + if((mj=pathname.match(/^\/my\/(\d{1,15})$/)))return serveMemberPage(req,res,path.join(PUBLIC_DIR,'my.html'),'my',mj[1]); + if((mj=pathname.match(/^\/join\/(\d{1,15})$/)))return serveMemberPage(req,res,path.join(PUBLIC_DIR,'join.html'),'join',mj[1]); + } let file; if(pathname==='/')file=path.join(PUBLIC_DIR,'index.html');else if(pathname==='/start'||pathname==='/start/')file=path.join(PUBLIC_DIR,'start.html');else if(pathname==='/training'||pathname==='/training/')file=path.join(PUBLIC_DIR,'training.html');else if(pathname==='/admin'||pathname==='/admin/')file=path.join(PUBLIC_DIR,'admin.html');else if(pathname==='/my'||pathname==='/my/'||/^\/my\/\d{1,15}$/.test(pathname))file=path.join(PUBLIC_DIR,'my.html');else if(pathname==='/contract'||pathname==='/contract/')file=path.join(PUBLIC_DIR,'contract.html');else if(pathname==='/disclaimer'||pathname==='/disclaimer/')file=path.join(PUBLIC_DIR,'disclaimer.html');else if(/^\/join\/\d{1,15}$/.test(pathname))file=path.join(PUBLIC_DIR,'join.html');else if(pathname==='/join'||pathname==='/join/'){res.writeHead(302,{Location:'/start'});return res.end();}else{ const safe=path.normalize(pathname).replace(/^([.][.][/\\])+/, '').replace(/^[/\\]+/,'');file=path.join(PUBLIC_DIR,safe);if(!file.startsWith(PUBLIC_DIR))file='';