Telegram Mini App v1: initData auth bridge into the existing site
- POST /api/public/tg-webapp-auth: HMAC-verifies WebApp initData against the companion bot token (12h freshness, timing-safe), maps chat -> member via tg-links.json, mints a message session -> linked members land on /my/<id> with zero login - /app entry page (vendored telegram-web-app.js keeps CSP script-src 'self'); unlinked users get the one-time wallet-link instructions - tg-app.js on all pages: no-op in browsers; inside the webview lazy-loads the SDK, expands, themes header/background #071421, wires native BackButton - Bot menu button set programmatically to open /app; /start + help mention it - Synced chat.js canned answer + AI system prompt (Mini App facts) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -89,6 +89,16 @@ function verifyChallenge(address, signature) {
|
|||||||
saveSessions();
|
saveSessions();
|
||||||
return { token, id };
|
return { token, id };
|
||||||
}
|
}
|
||||||
|
// Mini App sessions: identity was already proved once (wallet-verified
|
||||||
|
// Telegram link), and Telegram re-proves the chat via signed initData — so a
|
||||||
|
// session can be minted without a fresh wallet signature. No address attached.
|
||||||
|
function mintSession(id) {
|
||||||
|
if (!Number.isInteger(id) || id < 1) return null;
|
||||||
|
const token = crypto.randomBytes(32).toString('hex');
|
||||||
|
sessions.set(token, { address: null, id, expires: Date.now() + SESSION_TTL, via: 'tg' });
|
||||||
|
saveSessions();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
function authFromCookie(req) {
|
function authFromCookie(req) {
|
||||||
const m = /(?:^|;\s*)ctb\.msid=([^;]+)/.exec(req.headers.cookie || '');
|
const m = /(?:^|;\s*)ctb\.msid=([^;]+)/.exec(req.headers.cookie || '');
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
@@ -168,4 +178,4 @@ function adminList() {
|
|||||||
return getMessages().slice(-300).reverse().map(m => ({ mid: m.mid, fromId: m.fromId, toId: m.toId || null, org: !!m.org, body: m.body, ts: m.ts, readCount: Object.keys(m.read || {}).length }));
|
return getMessages().slice(-300).reverse().map(m => ({ mid: m.mid, fromId: m.fromId, toId: m.toId || null, org: !!m.org, body: m.body, ts: m.ts, readCount: Object.keys(m.read || {}).length }));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { init, makeChallenge, verifyChallenge, authFromCookie, sessionCookie, send, inbox, markRead, unreadCount, adminList, ADDR_RE };
|
module.exports = { init, makeChallenge, verifyChallenge, mintSession, authFromCookie, sessionCookie, send, inbox, markRead, unreadCount, adminList, ADDR_RE };
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>RM Circle</title><link rel="icon" type="image/png" href="/favicon.png"><link rel="stylesheet" href="/styles.css">
|
||||||
|
<!-- Telegram Mini App entry point. Vendored WebApp JS (CSP: script-src 'self'). -->
|
||||||
|
<script src="/telegram-web-app.js"></script></head>
|
||||||
|
<body>
|
||||||
|
<main class="wrap hero" style="text-align:center;padding-top:14vh">
|
||||||
|
<div id="st-loading">
|
||||||
|
<img src="/logo.jpg" alt="RM Circle" style="width:72px;height:72px;border-radius:18px;box-shadow:0 6px 24px rgba(0,0,0,.45)">
|
||||||
|
<h1 style="font-size:22px;margin:18px 0 6px">Opening your dashboard…</h1>
|
||||||
|
<p style="color:var(--muted)">Verifying your Telegram link.</p>
|
||||||
|
</div>
|
||||||
|
<div id="st-unlinked" style="display:none">
|
||||||
|
<div class="eyebrow">One-time setup</div>
|
||||||
|
<h1 style="font-size:22px">Link your position first</h1>
|
||||||
|
<p style="color:var(--muted);max-width:420px;margin:10px auto">This app shows YOUR live dashboard — payments, team, coaching. To prove a position is yours, link it once with your wallet (a free signature, it can't move funds):</p>
|
||||||
|
<p style="color:var(--muted);max-width:420px;margin:10px auto;text-align:left">1. Open <b>rmcircle.team/my</b> in your browser<br>2. Enter your member ID → <b>Messages</b> → sign in with your wallet<br>3. Tap <b>“Connect Telegram”</b> — it deep-links right back to this bot</p>
|
||||||
|
<p style="margin:18px 0 8px"><button id="btn-open-site" class="btn btn-primary">Open the dashboard to link ↗</button></p>
|
||||||
|
<p><button id="btn-browse" class="btn">Just looking? See how the team works</button></p>
|
||||||
|
</div>
|
||||||
|
<div id="st-error" style="display:none">
|
||||||
|
<h1 style="font-size:22px">Hmm, that didn't verify</h1>
|
||||||
|
<p id="err-detail" style="color:var(--muted);max-width:420px;margin:10px auto">Could not verify the Telegram launch data.</p>
|
||||||
|
<p><button id="btn-retry" class="btn btn-primary">Try again</button></p>
|
||||||
|
</div>
|
||||||
|
<div id="st-notg" style="display:none">
|
||||||
|
<h1 style="font-size:22px">This page opens inside Telegram</h1>
|
||||||
|
<p style="color:var(--muted);max-width:420px;margin:10px auto">It's the RM Circle Mini App — open the companion bot in Telegram and tap the ☰ menu button. In a normal browser, use the regular dashboard instead.</p>
|
||||||
|
<a class="btn btn-primary" href="/my">Open the dashboard</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="/app.js"></script></body></html>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Telegram Mini App entry: verify initData server-side, then land the linked
|
||||||
|
// member on THEIR dashboard with a minted session — zero login. Unlinked
|
||||||
|
// users get the one-time wallet-link instructions instead.
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var tg = window.Telegram && window.Telegram.WebApp;
|
||||||
|
function show(id) {
|
||||||
|
['st-loading', 'st-unlinked', 'st-error', 'st-notg'].forEach(function (x) {
|
||||||
|
var el = document.getElementById(x); if (el) el.style.display = x === id ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function on(id, fn) { var el = document.getElementById(id); if (el) el.addEventListener('click', fn); }
|
||||||
|
on('btn-open-site', function () {
|
||||||
|
var url = location.origin + '/my';
|
||||||
|
if (tg && tg.openLink) tg.openLink(url); else location.href = url;
|
||||||
|
});
|
||||||
|
on('btn-browse', function () { location.href = '/start'; });
|
||||||
|
on('btn-retry', function () { location.reload(); });
|
||||||
|
|
||||||
|
if (!tg || !tg.initData) { show('st-notg'); return; }
|
||||||
|
// Flag the webview session so tg-app.js activates on every page after this one.
|
||||||
|
try { sessionStorage.setItem('rmcTg', '1'); } catch (e) {}
|
||||||
|
tg.ready();
|
||||||
|
try { tg.expand(); } catch (e) {}
|
||||||
|
try { tg.setHeaderColor('#071421'); tg.setBackgroundColor('#071421'); } catch (e) {}
|
||||||
|
|
||||||
|
fetch('/api/public/tg-webapp-auth', {
|
||||||
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ initData: tg.initData })
|
||||||
|
}).then(function (r) { return r.json(); }).then(function (d) {
|
||||||
|
if (d && d.ok && d.linked) { location.replace('/my/' + d.id); return; }
|
||||||
|
if (d && d.ok) { show('st-unlinked'); return; }
|
||||||
|
var el = document.getElementById('err-detail');
|
||||||
|
if (el && d && d.error) el.textContent = d.error;
|
||||||
|
show('st-error');
|
||||||
|
}).catch(function () { show('st-error'); });
|
||||||
|
})();
|
||||||
+2
-2
@@ -35,8 +35,8 @@
|
|||||||
a:()=>`Great question — and we checked it on-chain, not just in theory. The contract needs <strong>no one</strong> to keep it running: joins, upgrades, placement and every payout are fully automatic — no button anyone has to press, no expiry. If the creators walked away, lost their keys, or vanished, member payments keep flowing exactly as coded. We also verified that the founder, development and fee wallets are <strong>ordinary wallets, not programs</strong> — an ordinary wallet always accepts an incoming payment even if its key is lost forever, so a dead admin wallet can't jam a single member payment (at worst the project's <em>own</em> fee sits there uncollected). And the contract holds no stored balance — every payment is delivered in the same transaction. Full write-up in section 6 of <a href="/contract">rmcircle.team/contract</a>.`},
|
a:()=>`Great question — and we checked it on-chain, not just in theory. The contract needs <strong>no one</strong> to keep it running: joins, upgrades, placement and every payout are fully automatic — no button anyone has to press, no expiry. If the creators walked away, lost their keys, or vanished, member payments keep flowing exactly as coded. We also verified that the founder, development and fee wallets are <strong>ordinary wallets, not programs</strong> — an ordinary wallet always accepts an incoming payment even if its key is lost forever, so a dead admin wallet can't jam a single member payment (at worst the project's <em>own</em> fee sits there uncollected). And the contract holds no stored balance — every payment is delivered in the same transaction. Full write-up in section 6 of <a href="/contract">rmcircle.team/contract</a>.`},
|
||||||
{k:['pyramid','ponzi','pyramid scheme','ponzi scheme','mlm','recruiting scheme','is this a scheme'],
|
{k:['pyramid','ponzi','pyramid scheme','ponzi scheme','mlm','recruiting scheme','is this a scheme'],
|
||||||
a:()=>`A pyramid or Ponzi scheme funnels everyone's money to a central company and pays earlier joiners out of later joiners' deposits — and you can't verify any of it. This is the opposite: <strong>no company holds the money</strong>. A public smart contract on Polygon sends each payment person-to-person in the same transaction it arrives, and you can read the code and every payout yourself on-chain — nothing pooled, nothing hidden, rules that can't be changed. It <em>is</em> a team build, so it takes real effort and carries real crypto risk — not a passive investment. But you don't have to trust anyone; verify it at <a href="/contract">rmcircle.team/contract</a>. No income is guaranteed.`},
|
a:()=>`A pyramid or Ponzi scheme funnels everyone's money to a central company and pays earlier joiners out of later joiners' deposits — and you can't verify any of it. This is the opposite: <strong>no company holds the money</strong>. A public smart contract on Polygon sends each payment person-to-person in the same transaction it arrives, and you can read the code and every payout yourself on-chain — nothing pooled, nothing hidden, rules that can't be changed. It <em>is</em> a team build, so it takes real effort and carries real crypto risk — not a passive investment. But you don't have to trust anyone; verify it at <a href="/contract">rmcircle.team/contract</a>. No income is guaranteed.`},
|
||||||
{k:['telegram bot','connect telegram','payout ping','telegram notification','message my downline','contact my downline','reach my downline'],
|
{k:['telegram bot','connect telegram','payout ping','telegram notification','message my downline','contact my downline','reach my downline','mini app','miniapp','telegram app','dashboard in telegram'],
|
||||||
a:()=>`Link your position to our Telegram companion bot: open your <a href="/my">dashboard</a> → Messages → sign in with your wallet → tap "Connect Telegram". Once linked you get an instant DM whenever your position catches a payment, team messages reach you natively in Telegram (reply right there to answer), you're pinged when someone joins on your link, and "links" gives you all your invite links. Messaging still follows your matrix lines only — same rules as the site.`},
|
a:()=>`Link your position to our Telegram companion bot: open your <a href="/my">dashboard</a> → Messages → sign in with your wallet → tap "Connect Telegram". Once linked you get an instant DM whenever your position catches a payment, team messages reach you natively in Telegram (reply right there to answer), you're pinged when someone joins on your link, and "links" gives you all your invite links. Linked members can also tap the bot's ☰ menu button to open the <strong>Mini App</strong> — your full live dashboard, promo tools, and the Circle Method right inside Telegram, no login needed. Messaging still follows your matrix lines only — same rules as the site.`},
|
||||||
{k:['cash out','cashout','spend my crypto','withdraw','off ramp','off-ramp','gift card','giftcard','get my money out','turn into cash','convert to dollars'],
|
{k:['cash out','cashout','spend my crypto','withdraw','off ramp','off-ramp','gift card','giftcard','get my money out','turn into cash','convert to dollars'],
|
||||||
a:()=>`Three good paths, easiest first: (1) <strong>E-gift cards</strong> — send POL to CWallet (cwallet.com), swap to a US-dollar token there (their internal swaps are virtually free), and buy gift cards for brands you already use — groceries, gas, Amazon. eGifter (egifter.com) also takes crypto directly. (2) <strong>Straight cash-out</strong> via a regulated exchange in your country (Coinbase, Kraken…): send, sell, withdraw to your bank. (3) <strong>Keep it working</strong> — many members leave catches in the wallet to fund their next level. Full guide: <a href="/training#spending">Spending what you earn</a>. Honest notes: those are independent custodial services — only move what you're about to spend; taxes may apply where you live; not financial advice.`},
|
a:()=>`Three good paths, easiest first: (1) <strong>E-gift cards</strong> — send POL to CWallet (cwallet.com), swap to a US-dollar token there (their internal swaps are virtually free), and buy gift cards for brands you already use — groceries, gas, Amazon. eGifter (egifter.com) also takes crypto directly. (2) <strong>Straight cash-out</strong> via a regulated exchange in your country (Coinbase, Kraken…): send, sell, withdraw to your bank. (3) <strong>Keep it working</strong> — many members leave catches in the wallet to fund their next level. Full guide: <a href="/training#spending">Spending what you earn</a>. Honest notes: those are independent custodial services — only move what you're about to spend; taxes may apply where you live; not financial advice.`},
|
||||||
{k:['circle method','recruiting course','how do i recruit','recruiting training','get my two','get your two','how to invite','module 1','lessons'],
|
{k:['circle method','recruiting course','how do i recruit','recruiting training','get my two','get your two','how to invite','module 1','lessons'],
|
||||||
|
|||||||
@@ -30,4 +30,4 @@
|
|||||||
</div></section>
|
</div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -25,4 +25,4 @@
|
|||||||
</div></section>
|
</div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. Not an offer, solicitation, or guarantee of income. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. Not an offer, solicitation, or guarantee of income. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -111,4 +111,4 @@
|
|||||||
<script src="/qrlib.js"></script>
|
<script src="/qrlib.js"></script>
|
||||||
<script src="/fast-start.js"></script>
|
<script src="/fast-start.js"></script>
|
||||||
<script src="/chat.js" defer></script>
|
<script src="/chat.js" defer></script>
|
||||||
<script src="/translate.js" defer></script></body></html>
|
<script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -74,4 +74,4 @@
|
|||||||
</div></section>
|
</div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. No income is guaranteed. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/training">Training</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. No income is guaranteed. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/training">Training</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
+1
-1
@@ -23,4 +23,4 @@
|
|||||||
<section class="section"><div class="wrap"><div class="card" style="text-align:center;padding:34px"><div class="eyebrow">Ready to start?</div><h2 style="font-size:38px;margin:10px 0">See the current team placement.</h2><p style="max-width:680px;margin:0 auto 20px;color:var(--muted)">The onboarding page automatically shows the sponsor position the team is currently helping. Always use the sponsor shown there instead of an old screenshot or saved link.</p><a class="btn btn-primary" href="/start">Open Getting Started Instructions →</a></div></div></section>
|
<section class="section"><div class="wrap"><div class="card" style="text-align:center;padding:34px"><div class="eyebrow">Ready to start?</div><h2 style="font-size:38px;margin:10px 0">See the current team placement.</h2><p style="max-width:680px;margin:0 auto 20px;color:var(--muted)">The onboarding page automatically shows the sponsor position the team is currently helping. Always use the sponsor shown there instead of an old screenshot or saved link.</p><a class="btn btn-primary" href="/start">Open Getting Started Instructions →</a></div></div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/bridge.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/bridge.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
+1
-1
@@ -29,4 +29,4 @@
|
|||||||
<div class="callout warning" style="margin-top:12px;max-width:880px;margin-left:auto;margin-right:auto"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div></div></section>
|
<div class="callout warning" style="margin-top:12px;max-width:880px;margin-left:auto;margin-right:auto"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div></div></section>
|
||||||
|
|
||||||
</main><footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a></div></footer>
|
</main><footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/join.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/join.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
+1
-1
@@ -31,4 +31,4 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon and are historical facts, not a promise of future results. Participation involves cryptocurrency and smart-contract risk. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon and are historical facts, not a promise of future results. Participation involves cryptocurrency and smart-contract risk. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/qrlib.js"></script><script src="/rmc-wallet.js"></script><script src="/my.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/qrlib.js"></script><script src="/rmc-wallet.js"></script><script src="/my.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
+1
-1
@@ -16,4 +16,4 @@
|
|||||||
<div class="callout warning" style="margin-top:12px"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div><div id="supportBox" class="notice" style="margin-top:12px"></div><div id="supportLinkWrap" class="hidden" style="margin-top:10px"><a id="supportLink" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Open Team Support ↗</a></div></section></div>
|
<div class="callout warning" style="margin-top:12px"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div><div id="supportBox" class="notice" style="margin-top:12px"></div><div id="supportLinkWrap" class="hidden" style="margin-top:10px"><a id="supportLink" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Open Team Support ↗</a></div></section></div>
|
||||||
<figure class="roadmap-figure"><a href="/roadmap.webp" target="_blank" rel="noopener"><img src="/roadmap.webp" alt="RM Circle Premium Team Build Roadmap — core strategy, step-by-step guide, premium levels, and duplication formula" width="1149" height="1369" loading="lazy"></a><figcaption>This roadmap is the plan every member follows — tap to view full size.</figcaption></figure></div></main>
|
<figure class="roadmap-figure"><a href="/roadmap.webp" target="_blank" rel="noopener"><img src="/roadmap.webp" alt="RM Circle Premium Team Build Roadmap — core strategy, step-by-step guide, premium levels, and duplication formula" width="1149" height="1369" loading="lazy"></a><figcaption>This roadmap is the plan every member follows — tap to view full size.</figcaption></figure></div></main>
|
||||||
<footer class="wrap disclaimer">This is an independent RM Circle Team Build onboarding resource, not an owner/principal page. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/my">Already joined? Open your Member Dashboard →</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">This is an independent RM Circle Team Build onboarding resource, not an owner/principal page. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/my">Already joined? Open your Member Dashboard →</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/start.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/start.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
|||||||
|
// In-Telegram polish for every site page. No-ops in a normal browser; inside
|
||||||
|
// the Mini App webview it lazy-loads the vendored Telegram SDK (CSP stays
|
||||||
|
// script-src 'self') and wires theme + BackButton so pages feel native.
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
var inTg = false;
|
||||||
|
try { inTg = sessionStorage.getItem('rmcTg') === '1'; } catch (e) {}
|
||||||
|
if (!inTg && (window.TelegramWebviewProxy !== undefined || /tgWebApp(Data|Platform)/.test(location.hash))) inTg = true;
|
||||||
|
if (!inTg) return;
|
||||||
|
try { sessionStorage.setItem('rmcTg', '1'); } catch (e) {}
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
var tg = window.Telegram && window.Telegram.WebApp;
|
||||||
|
if (!tg || !tg.initData) return;
|
||||||
|
tg.ready();
|
||||||
|
try { tg.expand(); } catch (e) {}
|
||||||
|
try { tg.setHeaderColor('#071421'); tg.setBackgroundColor('#071421'); } catch (e) {}
|
||||||
|
// Back button mirrors webview history: /app enters via location.replace, so
|
||||||
|
// the landing dashboard has no history and stays clean; any deeper page
|
||||||
|
// (tools, training, another member view) gets a native back arrow.
|
||||||
|
var bb = tg.BackButton;
|
||||||
|
if (bb && history.length > 1) {
|
||||||
|
bb.show();
|
||||||
|
bb.onClick(function () { history.back(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.Telegram && window.Telegram.WebApp) { boot(); return; }
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.src = '/telegram-web-app.js';
|
||||||
|
s.onload = boot;
|
||||||
|
document.head.appendChild(s);
|
||||||
|
})();
|
||||||
+1
-1
@@ -372,4 +372,4 @@ Watch the short training, look at the live payment feed, then ask me anything.
|
|||||||
</div></section>
|
</div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">Independent RM Circle Team Build team resource. Informational only — not an earnings guarantee or investment advice. Cryptocurrency participation carries risk of loss.<div class="footer-links"><a href="/">Strategy</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/disclaimer">Disclaimers</a></div></footer>
|
<footer class="wrap disclaimer">Independent RM Circle Team Build team resource. Informational only — not an earnings guarantee or investment advice. Cryptocurrency participation carries risk of loss.<div class="footer-links"><a href="/">Strategy</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/disclaimer">Disclaimers</a></div></footer>
|
||||||
<script src="/tools.js"></script><script src="/translate.js" defer></script></body></html>
|
<script src="/tools.js"></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -83,4 +83,4 @@
|
|||||||
</div></section>
|
</div></section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="wrap disclaimer">This is an independent RM Circle Team Build training resource. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
<footer class="wrap disclaimer">This is an independent RM Circle Team Build training resource. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/training.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script></body></html>
|
<script src="/track.js"></script><script src="/training.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -82,4 +82,4 @@
|
|||||||
<script src="/qrlib.js"></script>
|
<script src="/qrlib.js"></script>
|
||||||
<script src="/fast-start.js"></script>
|
<script src="/fast-start.js"></script>
|
||||||
<script src="/chat.js" defer></script>
|
<script src="/chat.js" defer></script>
|
||||||
<script src="/translate.js" defer></script></body></html>
|
<script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ FACTS:
|
|||||||
- Current team sponsor: ${a ? `ID ${a.id}${c.showSponsorName && a.name ? ` (${a.name})` : ''}, ${a.directs}/2 directs` : 'shown on the start page'}. ${waiting} placement(s) waiting. Placements rotate as positions qualify — always verify on https://rmcircle.team/start right before joining.
|
- Current team sponsor: ${a ? `ID ${a.id}${c.showSponsorName && a.name ? ` (${a.name})` : ''}, ${a.directs}/2 directs` : 'shown on the start page'}. ${waiting} placement(s) waiting. Placements rotate as positions qualify — always verify on https://rmcircle.team/start right before joining.
|
||||||
- Site pages: https://rmcircle.team/ (strategy overview + roadmap + live team stats), https://rmcircle.team/start (current sponsor + join steps), https://rmcircle.team/training (THE CIRCLE METHOD — the team's free 10-lesson course in 3 modules. M1 Get Your Two: L1 mindset, L2 warm list, L3 the conversation, L4 objections. M2 Help Your Two: L5 dashboard-as-coaching-desk, L6 first 48 hours, L7 stalled people & pass-ups, L8 timing upgrades to catches. M3 Teach the Teachers: L9 run the same play, L10 the 20-minute weekly rhythm. ROUTING RULE — answer with the lesson: how do I find people→L2 (/training#lesson-2); what do I say→L3; pyramid objection→L4; new member just joined→L6; someone stalled→L7; should I upgrade→L8; overwhelmed→L10. Deep links: /training#lesson-N — plus 7 how-to videos — team overview, wallet setup, funding, the new connect-wallet join flow on the site, the dApp backup method, how payments work, and a full 14-min Member Dashboard walkthrough — + spillover article), https://rmcircle.team/how-pay-works (the two income streams shown as a pay-flow diagram + Premium/Standard tier comparison), https://rmcircle.team/contract (plain-language security review of the verified smart contract — code can't change, no pooled funds, locked rules, honest list of operator powers), https://rmcircle.team/weekly-rhythm (printable 20-minute Weekly Rhythm routine + 4-week habit tracker from Method Lesson 10, personalized like the Fast Start sheet), https://rmcircle.team/fast-start (printable 48-Hour Fast Start checklist — personalized with the member's invite link and a scannable QR code when opened from their dashboard; prints clean black-on-white, and prints in whatever language the member selected with the 🌐 button), https://rmcircle.team/my (member dashboard — its "Your team" panel opens with an organization bar: total members in your org, generations deep, qualified count below you, POL earned below you, and its approximate USD value at an hourly-cached POL price; the matrix under it drills leg by leg), https://rmcircle.team/tools (for existing team members who want to promote — share-ready promo videos (including the “Pocket Change” curiosity hook video — 25 ways people flush pocket change weekly with nothing to show for it, then the side-hustle flip; it deliberately shows no URL so the poster's invite link in the caption/description carries the credit, and matching pocket-change post copy sits in the Social posts section), copy-paste social posts, short/long email swipes, a downloadable banner kit in every standard size, and an Official RM Circle Media library (13 vertical social videos + 15 graphics from the creators — pair them with your own invite link in the caption; each curiosity video also has a MATCHED invite link (adds ?v=<hook> to the member's /join link) that makes the landing page continue that video's hook — recommend it when members ask which link to use with a video); open it from the gold Promo Tools button on your dashboard and every post/swipe arrives pre-personalized with YOUR invite link; to write promos in their own voice, mybrandedvoice.com), https://rmcircle.team/disclaimer (affiliate/earnings/risk disclosures).
|
- Site pages: https://rmcircle.team/ (strategy overview + roadmap + live team stats), https://rmcircle.team/start (current sponsor + join steps), https://rmcircle.team/training (THE CIRCLE METHOD — the team's free 10-lesson course in 3 modules. M1 Get Your Two: L1 mindset, L2 warm list, L3 the conversation, L4 objections. M2 Help Your Two: L5 dashboard-as-coaching-desk, L6 first 48 hours, L7 stalled people & pass-ups, L8 timing upgrades to catches. M3 Teach the Teachers: L9 run the same play, L10 the 20-minute weekly rhythm. ROUTING RULE — answer with the lesson: how do I find people→L2 (/training#lesson-2); what do I say→L3; pyramid objection→L4; new member just joined→L6; someone stalled→L7; should I upgrade→L8; overwhelmed→L10. Deep links: /training#lesson-N — plus 7 how-to videos — team overview, wallet setup, funding, the new connect-wallet join flow on the site, the dApp backup method, how payments work, and a full 14-min Member Dashboard walkthrough — + spillover article), https://rmcircle.team/how-pay-works (the two income streams shown as a pay-flow diagram + Premium/Standard tier comparison), https://rmcircle.team/contract (plain-language security review of the verified smart contract — code can't change, no pooled funds, locked rules, honest list of operator powers), https://rmcircle.team/weekly-rhythm (printable 20-minute Weekly Rhythm routine + 4-week habit tracker from Method Lesson 10, personalized like the Fast Start sheet), https://rmcircle.team/fast-start (printable 48-Hour Fast Start checklist — personalized with the member's invite link and a scannable QR code when opened from their dashboard; prints clean black-on-white, and prints in whatever language the member selected with the 🌐 button), https://rmcircle.team/my (member dashboard — its "Your team" panel opens with an organization bar: total members in your org, generations deep, qualified count below you, POL earned below you, and its approximate USD value at an hourly-cached POL price; the matrix under it drills leg by leg), https://rmcircle.team/tools (for existing team members who want to promote — share-ready promo videos (including the “Pocket Change” curiosity hook video — 25 ways people flush pocket change weekly with nothing to show for it, then the side-hustle flip; it deliberately shows no URL so the poster's invite link in the caption/description carries the credit, and matching pocket-change post copy sits in the Social posts section), copy-paste social posts, short/long email swipes, a downloadable banner kit in every standard size, and an Official RM Circle Media library (13 vertical social videos + 15 graphics from the creators — pair them with your own invite link in the caption; each curiosity video also has a MATCHED invite link (adds ?v=<hook> to the member's /join link) that makes the landing page continue that video's hook — recommend it when members ask which link to use with a video); open it from the gold Promo Tools button on your dashboard and every post/swipe arrives pre-personalized with YOUR invite link; to write promos in their own voice, mybrandedvoice.com), https://rmcircle.team/disclaimer (affiliate/earnings/risk disclosures).
|
||||||
- UPGRADING FROM THE DASHBOARD: a qualified member can upgrade their level directly on their dashboard (rmcircle.team/my/<id>) — an "Upgrade" card appears with the exact next-level cost read live from the contract; they connect the wallet that OWNS the position, confirm one transaction, done. The site never touches the funds (wallet pays the contract directly). If the wallet doesn't cover the cost, the card offers the MoonPay card-buy option. On phones, open the page inside the wallet app's browser.
|
- UPGRADING FROM THE DASHBOARD: a qualified member can upgrade their level directly on their dashboard (rmcircle.team/my/<id>) — an "Upgrade" card appears with the exact next-level cost read live from the contract; they connect the wallet that OWNS the position, confirm one transaction, done. The site never touches the funds (wallet pays the contract directly). If the wallet doesn't cover the cost, the card offers the MoonPay card-buy option. On phones, open the page inside the wallet app's browser.
|
||||||
- TELEGRAM COMPANION BOT: members can link their position (dashboard → Messages → "Connect Telegram", wallet-verified) to get instant payout DMs, native Telegram delivery of team messages (reply in Telegram to answer — matrix-line rules still apply), joined-on-your-link pings, and their invite/angle links via the "links" command. This finally lets members reach their downline as real people instead of just IDs — while handles stay private (the bot relays).
|
- TELEGRAM COMPANION BOT: members can link their position (dashboard → Messages → "Connect Telegram", wallet-verified) to get instant payout DMs, native Telegram delivery of team messages (reply in Telegram to answer — matrix-line rules still apply), joined-on-your-link pings, and their invite/angle links via the "links" command. This finally lets members reach their downline as real people instead of just IDs — while handles stay private (the bot relays). Linked members can also tap the bot's ☰ menu button to open the MINI APP — the full live dashboard, promo tools, and Circle Method training right inside Telegram with zero login (Telegram itself proves who they are). The website stays fully available too; the Mini App is a convenience door, not a replacement.
|
||||||
- MESSAGES (on-site, wallet-verified): every member dashboard has a Messages panel — sign in once with the wallet that owns your position (a free signature, cannot move funds), then message your upline or anyone in your own team, or broadcast to your whole team. Spam-proof by design: messaging only works along your own matrix lines, so strangers can't message you. Unread messages show as a bell on your dashboard. Members are told the team admin can review messages for abuse. No email address needed.
|
- MESSAGES (on-site, wallet-verified): every member dashboard has a Messages panel — sign in once with the wallet that owns your position (a free signature, cannot move funds), then message your upline or anyone in your own team, or broadcast to your whole team. Spam-proof by design: messaging only works along your own matrix lines, so strangers can't message you. Unread messages show as a bell on your dashboard. Members are told the team admin can review messages for abuse. No email address needed.
|
||||||
- BUYING POL WITH A CARD (for people brand new to crypto): the site links to MoonPay (moonpay.com/buy/pol) on the training page, the start page, and automatically on the join page when a connected wallet's balance is short. Guidance to give: choose POL on the POLYGON network, send it to YOUR OWN wallet address, buy about entry + gas (~385 POL). When explaining gas, use the car analogy: just like a car needs fuel to get anywhere, every blockchain transaction burns a tiny bit of POL to move — keep a little in the tank beyond the entry, because a wallet with an empty tank cannot make the trip. MoonPay is an independent company (merchant of record) — it handles ID verification and charges its own card fee (~4.5%); this site never touches or holds anyone's money. First purchases can take a few minutes to arrive.
|
- BUYING POL WITH A CARD (for people brand new to crypto): the site links to MoonPay (moonpay.com/buy/pol) on the training page, the start page, and automatically on the join page when a connected wallet's balance is short. Guidance to give: choose POL on the POLYGON network, send it to YOUR OWN wallet address, buy about entry + gas (~385 POL). When explaining gas, use the car analogy: just like a car needs fuel to get anywhere, every blockchain transaction burns a tiny bit of POL to move — keep a little in the tank beyond the entry, because a wallet with an empty tank cannot make the trip. MoonPay is an independent company (merchant of record) — it handles ID verification and charges its own card fee (~4.5%); this site never touches or holds anyone's money. First purchases can take a few minutes to arrive.
|
||||||
- LANGUAGE: always reply in the language the member writes in — translate program terms naturally and keep level names (Scintilla, Ascensus, ...) as-is. Site pages have a floating 🌐 Translate button (bottom-left) that machine-translates any page and remembers the choice.
|
- LANGUAGE: always reply in the language the member writes in — translate program terms naturally and keep level names (Scintilla, Ascensus, ...) as-is. Site pages have a floating 🌐 Translate button (bottom-left) that machine-translates any page and remembers the choice.
|
||||||
@@ -632,6 +632,23 @@ async function handleApi(req,res,pathname){
|
|||||||
if(!r.url)return json(res,200,{error:'The Telegram bot is warming up — try again in a minute.'});
|
if(!r.url)return json(res,200,{error:'The Telegram bot is warming up — try again in a minute.'});
|
||||||
return json(res,200,{url:r.url,linked:!!tgbot.memberChat(s2.id)});
|
return json(res,200,{url:r.url,linked:!!tgbot.memberChat(s2.id)});
|
||||||
}
|
}
|
||||||
|
if(req.method==='POST'&&pathname==='/api/public/tg-webapp-auth'){
|
||||||
|
// Telegram Mini App auth bridge: signed initData (HMAC-verified against the
|
||||||
|
// companion bot token) proves the Telegram account; the wallet-verified
|
||||||
|
// link in tg-links.json maps it to a member — so linked members land on
|
||||||
|
// their dashboard with zero login. Never creates links, only reads them.
|
||||||
|
const ip=String(req.headers['x-forwarded-for']||req.socket.remoteAddress||'').split(',')[0].trim();
|
||||||
|
if(memberLookupLimited(ip))return json(res,429,{error:'Too many requests — wait a minute.'});
|
||||||
|
const b=await bodyJson(req).catch(()=>null);
|
||||||
|
if(!b||typeof b.initData!=='string')return json(res,400,{error:'Invalid request.'});
|
||||||
|
const v=tgbot.verifyInitData(b.initData);
|
||||||
|
if(v.error)return json(res,401,{error:'Could not verify the Telegram launch data — close and reopen the app.'});
|
||||||
|
const memberId=tgbot.chatMember(v.userId);
|
||||||
|
if(!memberId)return json(res,200,{ok:true,linked:false});
|
||||||
|
const tok=messages.mintSession(memberId);
|
||||||
|
if(!tok)return json(res,500,{error:'Session error — try again.'});
|
||||||
|
return json(res,200,{ok:true,linked:true,id:memberId},{'Set-Cookie':messages.sessionCookie(tok)});
|
||||||
|
}
|
||||||
if(req.method==='POST'&&pathname==='/api/public/msg-send'){
|
if(req.method==='POST'&&pathname==='/api/public/msg-send'){
|
||||||
const s=messages.authFromCookie(req);
|
const s=messages.authFromCookie(req);
|
||||||
if(!s)return json(res,401,{error:'Not signed in.'});
|
if(!s)return json(res,401,{error:'Not signed in.'});
|
||||||
@@ -841,7 +858,7 @@ const server=http.createServer(async(req,res)=>{
|
|||||||
if((mj=pathname.match(/^\/join\/(\d{1,15})$/)))return serveMemberPage(req,res,path.join(PUBLIC_DIR,'join.html'),'join',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;
|
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(pathname==='/how-pay-works'||pathname==='/how-pay-works/')file=path.join(PUBLIC_DIR,'how-pay-works.html');else if(pathname==='/tools'||pathname==='/tools/')file=path.join(PUBLIC_DIR,'tools.html');else if(pathname==='/fast-start'||pathname==='/fast-start/')file=path.join(PUBLIC_DIR,'fast-start.html');else if(pathname==='/weekly-rhythm'||pathname==='/weekly-rhythm/')file=path.join(PUBLIC_DIR,'weekly-rhythm.html');else if(pathname==='/direct-join'||pathname==='/direct-join/'){if(!getSession(req)){res.writeHead(302,{Location:'/admin'});return res.end();}file=path.join(ROOT,'private','direct-join.html');}else if(pathname==='/join-now'||pathname==='/join-now/'){if(!getConfig().dappFallbackPublic){res.writeHead(302,{Location:'/start'});return res.end();}file=path.join(ROOT,'private','join-now.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:'/join-now'});return res.end();}else{
|
if(pathname==='/')file=path.join(PUBLIC_DIR,'index.html');else if(pathname==='/app'||pathname==='/app/')file=path.join(PUBLIC_DIR,'app.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(pathname==='/how-pay-works'||pathname==='/how-pay-works/')file=path.join(PUBLIC_DIR,'how-pay-works.html');else if(pathname==='/tools'||pathname==='/tools/')file=path.join(PUBLIC_DIR,'tools.html');else if(pathname==='/fast-start'||pathname==='/fast-start/')file=path.join(PUBLIC_DIR,'fast-start.html');else if(pathname==='/weekly-rhythm'||pathname==='/weekly-rhythm/')file=path.join(PUBLIC_DIR,'weekly-rhythm.html');else if(pathname==='/direct-join'||pathname==='/direct-join/'){if(!getSession(req)){res.writeHead(302,{Location:'/admin'});return res.end();}file=path.join(ROOT,'private','direct-join.html');}else if(pathname==='/join-now'||pathname==='/join-now/'){if(!getConfig().dappFallbackPublic){res.writeHead(302,{Location:'/start'});return res.end();}file=path.join(ROOT,'private','join-now.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:'/join-now'});return res.end();}else{
|
||||||
const safe=path.normalize(pathname).replace(/^([.][.][/\\])+/, '').replace(/^[/\\]+/,'');file=path.join(PUBLIC_DIR,safe);if(!file.startsWith(PUBLIC_DIR))file='';
|
const safe=path.normalize(pathname).replace(/^([.][.][/\\])+/, '').replace(/^[/\\]+/,'');file=path.join(PUBLIC_DIR,safe);if(!file.startsWith(PUBLIC_DIR))file='';
|
||||||
}
|
}
|
||||||
if(file&&staticFile(req,res,file))return;return staticFile(req,res,path.join(PUBLIC_DIR,'404.html'),404);
|
if(file&&staticFile(req,res,file))return;return staticFile(req,res,path.join(PUBLIC_DIR,'404.html'),404);
|
||||||
|
|||||||
@@ -35,6 +35,28 @@ async function dm(chatId, text, extra) {
|
|||||||
return api('sendMessage', Object.assign({ chat_id: chatId, text, disable_web_page_preview: true }, extra || {}));
|
return api('sendMessage', Object.assign({ chat_id: chatId, text, disable_web_page_preview: true }, extra || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Mini App: verify Telegram WebApp initData (HMAC per Bot API spec) ------
|
||||||
|
// secret_key = HMAC_SHA256(key="WebAppData", bot_token); hash covers the
|
||||||
|
// sorted key=value lines of every field except hash itself.
|
||||||
|
const INITDATA_MAX_AGE_S = 12 * 3600;
|
||||||
|
function verifyInitData(initData) {
|
||||||
|
const t = token(); if (!t) return { error: 'bot-offline' };
|
||||||
|
if (typeof initData !== 'string' || !initData || initData.length > 4096) return { error: 'bad-initdata' };
|
||||||
|
let params; try { params = new URLSearchParams(initData); } catch (e) { return { error: 'bad-initdata' }; }
|
||||||
|
const hash = params.get('hash');
|
||||||
|
if (!hash || !/^[0-9a-f]{64}$/.test(hash)) return { error: 'bad-initdata' };
|
||||||
|
params.delete('hash');
|
||||||
|
const dcs = [...params.entries()].map(([k, v]) => `${k}=${v}`).sort().join('\n');
|
||||||
|
const secret = crypto.createHmac('sha256', 'WebAppData').update(t).digest();
|
||||||
|
const check = crypto.createHmac('sha256', secret).update(dcs).digest('hex');
|
||||||
|
if (!crypto.timingSafeEqual(Buffer.from(check), Buffer.from(hash))) return { error: 'bad-hash' };
|
||||||
|
const authDate = Number(params.get('auth_date')) || 0;
|
||||||
|
if (Math.abs(Date.now() / 1000 - authDate) > INITDATA_MAX_AGE_S) return { error: 'stale' };
|
||||||
|
let user = null; try { user = JSON.parse(params.get('user') || 'null'); } catch (e) {}
|
||||||
|
if (!user || !user.id) return { error: 'no-user' };
|
||||||
|
return { userId: user.id, user };
|
||||||
|
}
|
||||||
|
|
||||||
// --- setup: learn our username + point the webhook at ourselves -------------
|
// --- setup: learn our username + point the webhook at ourselves -------------
|
||||||
async function ensureWebhook(baseUrl) {
|
async function ensureWebhook(baseUrl) {
|
||||||
const d = load();
|
const d = load();
|
||||||
@@ -42,6 +64,9 @@ async function ensureWebhook(baseUrl) {
|
|||||||
if (me && me.ok) { d.u = me.result.username; save(d); }
|
if (me && me.ok) { d.u = me.result.username; save(d); }
|
||||||
const secret = webhookSecret();
|
const secret = webhookSecret();
|
||||||
if (!secret) return;
|
if (!secret) return;
|
||||||
|
// Menu button (bottom-left ☰ in the private chat) opens the Mini App.
|
||||||
|
// Idempotent; BotFather /newapp is only needed for t.me/<bot>/<app> links.
|
||||||
|
await api('setChatMenuButton', { menu_button: { type: 'web_app', text: 'Open App', web_app: { url: `${baseUrl}/app` } } });
|
||||||
const url = `${baseUrl}/api/tg-hook/${secret}`;
|
const url = `${baseUrl}/api/tg-hook/${secret}`;
|
||||||
const info = await api('getWebhookInfo', {});
|
const info = await api('getWebhookInfo', {});
|
||||||
if (info && info.ok && info.result.url === url) return;
|
if (info && info.ok && info.result.url === url) return;
|
||||||
@@ -228,7 +253,7 @@ async function handleUpdate(update) {
|
|||||||
d.members[String(rec.id)] = chatId;
|
d.members[String(rec.id)] = chatId;
|
||||||
d.chats[String(chatId)] = rec.id;
|
d.chats[String(chatId)] = rec.id;
|
||||||
save(d);
|
save(d);
|
||||||
await dm(chatId, `✅ Linked to position #${rec.id}!\n\nFrom now on:\n💰 You get a DM the moment your position catches a payment\n📨 Team messages reach you here — reply to answer\n🎉 You're pinged when someone joins on your link\n\nTry: coach · team · links — or: msg <id> <your message>\nYour weekly 20-minute digest arrives Saturdays 10am Central (rhythm to adjust).`);
|
await dm(chatId, `✅ Linked to position #${rec.id}!\n\nFrom now on:\n💰 You get a DM the moment your position catches a payment\n📨 Team messages reach you here — reply to answer\n🎉 You're pinged when someone joins on your link\n\nTry: coach · team · links — or: msg <id> <your message>\n📱 Tap the ☰ menu button (next to the message box) to open your full dashboard right inside Telegram — no login needed.\nYour weekly 20-minute digest arrives Saturdays 10am Central (rhythm to adjust).`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await dm(chatId, `That link code is expired or already used. Get a fresh one from the Messages panel on your dashboard: https://rmcircle.team/my`);
|
await dm(chatId, `That link code is expired or already used. Get a fresh one from the Messages panel on your dashboard: https://rmcircle.team/my`);
|
||||||
@@ -241,7 +266,7 @@ async function handleUpdate(update) {
|
|||||||
if (!linked) { await dm(chatId, `You're not linked yet. Open https://rmcircle.team/my → Messages → "Connect Telegram".`); return; }
|
if (!linked) { await dm(chatId, `You're not linked yet. Open https://rmcircle.team/my → Messages → "Connect Telegram".`); return; }
|
||||||
|
|
||||||
if (/^\/?links$/i.test(text)) { await dm(chatId, linksText(linked)); return; }
|
if (/^\/?links$/i.test(text)) { await dm(chatId, linksText(linked)); return; }
|
||||||
if (/^\/?help$/i.test(text)) { await dm(chatId, `Commands:\nlinks — your invite + angle links\ncoach — who to help + which lesson to send\nteam — your org numbers\nmsg <id> <text> — message a teammate\nlesson <1-10> — grab any Circle Method lesson link\nrhythm — your weekly 20-minute digest (rhythm now / rhythm sat 9 / rhythm off)\nReply to any 📨 message to answer it.\nDashboard: https://rmcircle.team/my/${linked}`); return; }
|
if (/^\/?help$/i.test(text)) { await dm(chatId, `Commands:\nlinks — your invite + angle links\ncoach — who to help + which lesson to send\nteam — your org numbers\nmsg <id> <text> — message a teammate\nlesson <1-10> — grab any Circle Method lesson link\nrhythm — your weekly 20-minute digest (rhythm now / rhythm sat 9 / rhythm off)\nReply to any 📨 message to answer it.\n📱 The ☰ menu button opens your full dashboard inside Telegram — no login.\nDashboard: https://rmcircle.team/my/${linked}`); return; }
|
||||||
|
|
||||||
if (/^\/?coach$/i.test(text)) { const d = await fetchMember(linked); await dm(chatId, coachText(d)); return; }
|
if (/^\/?coach$/i.test(text)) { const d = await fetchMember(linked); await dm(chatId, coachText(d)); return; }
|
||||||
if (/^\/?team$/i.test(text)) { const d = await fetchMember(linked); await dm(chatId, teamText(d)); return; }
|
if (/^\/?team$/i.test(text)) { const d = await fetchMember(linked); await dm(chatId, teamText(d)); return; }
|
||||||
@@ -275,4 +300,4 @@ async function handleUpdate(update) {
|
|||||||
} catch (e) { console.error('tgbot handleUpdate', e.message); }
|
} catch (e) { console.error('tgbot handleUpdate', e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { init, handleUpdate, notifyEvent, notifyMessage, makeLinkCode, webhookSecret, memberChat };
|
module.exports = { init, handleUpdate, notifyEvent, notifyMessage, makeLinkCode, webhookSecret, memberChat, chatMember, verifyInitData };
|
||||||
|
|||||||
Reference in New Issue
Block a user