Telegram: optional company payment-proof channel feed (same bot, separate chat)

config.telegramProofChatId (+ optional telegramProofTopicId, telegramProofEvents
'payouts'|'all') mirrors payout events with the recruiting copy plus the
Polygonscan receipt. Inert until the chat id is set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 07:03:23 -05:00
parent 860efc4912
commit 42a6caacb0
+21 -4
View File
@@ -156,11 +156,17 @@ function submitRateLimited(ip) {
// topic (e.g. the recruiting/new-members topic) with different copy.
function sendTelegram(text, topicId, replyMarkup) {
const c = getConfig();
if (!c.telegramBotToken || !c.telegramChatId) return;
return sendTelegramTo(c.telegramChatId, text, topicId != null ? topicId : c.telegramTopicId, replyMarkup);
}
// Same bot, any chat: the team forum, or the public payment-proof channel
// (config.telegramProofChatId — the bot just has to be an admin there).
function sendTelegramTo(chatId, text, topicId, replyMarkup) {
const c = getConfig();
if (!c.telegramBotToken || !chatId) return;
// No link previews: payout/team-build posts carry Polygonscan links, and the
// preview card tripled the height of every message in the group.
const payload = { chat_id: c.telegramChatId, text, disable_web_page_preview: true };
const thread = topicId != null ? topicId : c.telegramTopicId;
const payload = { chat_id: chatId, text, disable_web_page_preview: true };
const thread = topicId;
if (thread && /^[0-9]+$/.test(String(thread))) payload.message_thread_id = Number(thread);
if (replyMarkup) payload.reply_markup = replyMarkup;
fetch(`https://api.telegram.org/bot${c.telegramBotToken}/sendMessage`, {
@@ -1551,7 +1557,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});
}
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','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds','suiteAllowlist','suiteToolsInAlerts','suiteLevelOverride','directDefaultIds','announceEnabled','announceId','announceImg','announceMeetUrl','announceEyebrow','announceDateLabel','announceTimes','announceExpiresUTC'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;if('announceEnabled' in next)next.announceEnabled=!!next.announceEnabled;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','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','telegramProofChatId','telegramProofTopicId','telegramProofEvents','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds','suiteAllowlist','suiteToolsInAlerts','suiteLevelOverride','directDefaultIds','announceEnabled','announceId','announceImg','announceMeetUrl','announceEyebrow','announceDateLabel','announceTimes','announceExpiresUTC'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;if('announceEnabled' in next)next.announceEnabled=!!next.announceEnabled;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))?$/);
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.'});
@@ -1730,6 +1736,17 @@ chain.startIndexer(evt=>{
// 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){ const cta='https://'+String(c.recruitCtaUrl||'rmcircle.team').replace(/^https?:\/\//,''); sendTelegram(rm, c.telegramRecruitTopicId, {inline_keyboard:[[{text:'🚀 Get Started — rmcircle.team',url:cta+(cta.includes('?')?'&':'?')+'utm_source=tg'}]]}); } }
// company payment-proof channel: the recruiting copy plus the Polygonscan
// receipt, posted by the SAME bot to a separate chat. Payouts only unless
// telegramProofEvents is 'all'. Fires only when a proof chat id is set.
if(c.telegramProofChatId && (evt.type==='payout' || String(c.telegramProofEvents||'payouts')==='all')){
const pm=recruitMsg(evt);
if(pm){ const cta='https://'+String(c.recruitCtaUrl||'rmcircle.team').replace(/^https?:\/\//,'');
sendTelegramTo(c.telegramProofChatId, pm+(evt.tx?`
🔗 Proof on Polygonscan: https://polygonscan.com/tx/${evt.tx}`:''), c.telegramProofTopicId||null,
{inline_keyboard:[[{text:'🚀 Get Started — rmcircle.team',url:cta+(cta.includes('?')?'&':'?')+'utm_source=tgproof'}]]}); }
}
// admin email alert — same team-gated events, so deep-leg action still surfaces
if(c.teamAlertEmail){
const subj=evt.type==='registered'?`RM Circle team build: #${evt.id} registered under #${evt.referrerId}`