Add recruiting-framed payout feed to a second Telegram topic
sendTelegram() now takes an optional topicId; the same team events that post to the internal team-build topic also fan out a recruiting-framed version (social proof + CTA to rmcircle.team, no internal/contact detail) to the new-members topic. Gated on config.telegramRecruitTopicId — dormant until set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -89,17 +89,39 @@ function submitRateLimited(ip) {
|
|||||||
if (!rec || now > rec.reset) { submitHits.set(ip, { count: 1, reset: now + 600000 }); return false; }
|
if (!rec || now > rec.reset) { submitHits.set(ip, { count: 1, reset: now + 600000 }); return false; }
|
||||||
rec.count++; return rec.count > 5;
|
rec.count++; return rec.count > 5;
|
||||||
}
|
}
|
||||||
function sendTelegram(text) {
|
// Post to the team Telegram. topicId overrides the default team-build topic
|
||||||
|
// (config.telegramTopicId) — used to fan the same event out to a second forum
|
||||||
|
// topic (e.g. the recruiting/new-members topic) with different copy.
|
||||||
|
function sendTelegram(text, topicId) {
|
||||||
const c = getConfig();
|
const c = getConfig();
|
||||||
if (!c.telegramBotToken || !c.telegramChatId) return;
|
if (!c.telegramBotToken || !c.telegramChatId) return;
|
||||||
const payload = { chat_id: c.telegramChatId, text };
|
const payload = { chat_id: c.telegramChatId, text };
|
||||||
if (c.telegramTopicId && /^[0-9]+$/.test(String(c.telegramTopicId))) payload.message_thread_id = Number(c.telegramTopicId);
|
const thread = topicId != null ? topicId : c.telegramTopicId;
|
||||||
|
if (thread && /^[0-9]+$/.test(String(thread))) payload.message_thread_id = Number(thread);
|
||||||
fetch(`https://api.telegram.org/bot${c.telegramBotToken}/sendMessage`, {
|
fetch(`https://api.telegram.org/bot${c.telegramBotToken}/sendMessage`, {
|
||||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
}).then(async r=>{ if(!r.ok) console.error('telegram sendMessage status', r.status, (await r.text().catch(()=>'')).slice(0,200)); })
|
}).then(async r=>{ if(!r.ok) console.error('telegram sendMessage status', r.status, (await r.text().catch(()=>'')).slice(0,200)); })
|
||||||
.catch(e=>console.error('telegram error', e.message));
|
.catch(e=>console.error('telegram error', e.message));
|
||||||
}
|
}
|
||||||
|
// Recruiting-framed version of a team event for the new-members topic: social
|
||||||
|
// proof + a call to action to the home page, no internal/contact detail.
|
||||||
|
function recruitMsg(evt) {
|
||||||
|
const home = 'rmcircle.team', tags = '#RMCircle #Polygon #Crypto';
|
||||||
|
if (evt.type === 'payout') {
|
||||||
|
const line = evt.kind === 'upline'
|
||||||
|
? `Member #${evt.toId} just earned ${evt.pol.toFixed(2)} POL${evt.gen ? ` — a Gen ${evt.gen} upgrade pass-up` : ''}, paid automatically as their team grew beneath them.`
|
||||||
|
: `Member #${evt.toId} just earned ${evt.pol.toFixed(2)} POL the moment a new teammate joined on their link.`;
|
||||||
|
return `💸 Another on-chain payout just landed! 🎉\n\n${line}\n\nInstant, automatic, and verifiable on the blockchain — no company holding the money. This is what building on The RM Circle looks like. 🚀\n\n👉 Start yours at ${home}\n\n${tags}`;
|
||||||
|
}
|
||||||
|
if (evt.type === 'registered') {
|
||||||
|
return `🔥 The team just grew!\n\nA new member joined The RM Circle and locked in their position on-chain. The momentum is real.\n\n👉 Claim your spot at ${home} 🚀\n\n${tags}`;
|
||||||
|
}
|
||||||
|
if (evt.type === 'upgraded') {
|
||||||
|
return `⚡ Level up!\n\nMember #${evt.id} just upgraded to ${evt.levelName} on The RM Circle — climbing the ranks and opening up bigger pass-ups.\n\n👉 Start building at ${home} 🚀\n\n${tags}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
const SENDGRID_KEY_FILE = path.join(DATA_DIR, 'sendgrid.key');
|
const SENDGRID_KEY_FILE = path.join(DATA_DIR, 'sendgrid.key');
|
||||||
function getSendgridKey() {
|
function getSendgridKey() {
|
||||||
if (process.env.SENDGRID_API_KEY) return process.env.SENDGRID_API_KEY;
|
if (process.env.SENDGRID_API_KEY) return process.env.SENDGRID_API_KEY;
|
||||||
@@ -528,7 +550,7 @@ async function handleApi(req,res,pathname){
|
|||||||
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors});
|
||||||
}
|
}
|
||||||
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
if(req.method==='PATCH'&&pathname==='/api/admin/config'){
|
||||||
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next});
|
||||||
}
|
}
|
||||||
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/);
|
||||||
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});
|
||||||
@@ -660,6 +682,9 @@ chain.startIndexer(evt=>{
|
|||||||
else text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (${evt.gen?`Gen ${evt.gen} `:''}upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`;
|
else text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (${evt.gen?`Gen ${evt.gen} `:''}upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`;
|
||||||
if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`;
|
if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`;
|
||||||
sendTelegram(text);
|
sendTelegram(text);
|
||||||
|
// recruiting-framed copy of the SAME event to the new-members topic
|
||||||
|
// (social proof, CTA -> home). Fires only when a recruit topic is set.
|
||||||
|
if(c.telegramRecruitTopicId){ const rm=recruitMsg(evt); if(rm) sendTelegram(rm, c.telegramRecruitTopicId); }
|
||||||
// admin email alert — same team-gated events, so deep-leg action still surfaces
|
// admin email alert — same team-gated events, so deep-leg action still surfaces
|
||||||
if(c.teamAlertEmail){
|
if(c.teamAlertEmail){
|
||||||
const subj=evt.type==='registered'?`RM Circle team build: #${evt.id} registered under #${evt.referrerId}`
|
const subj=evt.type==='registered'?`RM Circle team build: #${evt.id} registered under #${evt.referrerId}`
|
||||||
|
|||||||
Reference in New Issue
Block a user