diff --git a/chatbot.js b/chatbot.js index 87c3ffd..016da7c 100644 --- a/chatbot.js +++ b/chatbot.js @@ -81,7 +81,7 @@ FACTS: - HOLDING TANK (Members > My line > Holding tank card): free members who joined with no sponsor wait there; a member who has switched on payouts AND bought their own $20+ package can Adopt one (first come, max 2 open adoptions, 7-day window; if the person never links a wallet or buys, they fall back into the tank; a person can be adopted twice at most). Adopting sets the sponsor, opens a chat and emails the member; their first purchase then binds to the adopter on-chain. Members can also "Release to tank" one of their own free referrals (pay it forward), but NOT someone they adopted less than 3 days ago: an adoption is a commitment, and a dropped adoption still counts toward that person's two-adoption lifetime limit. Releases are posted to the feed and Telegram like pickups. Admin sees the tank under Members. - DAILY CLAIM STREAK: finishing the daily ad set and claiming pays 5 credits on day 1, 7 on day 2, 10 from day 3, and 25 on every 7th consecutive day; miss a day and it restarts. After the set, verified visits (up to 20 a day, 1 credit each) keep earning, and the credits are meant to be spent on a campaign. - BLOG (public, instantadpay.com/blog): Marty's coaching and teaching articles on building a line, advertising that pays, and daily habits; each article has its own page and can be shared; RSS at /blog/feed.xml. Members who want to write their own articles: not offered today. -- ACHIEVEMENT BADGES ON TELEGRAM (2026-09-13): when a member unlocks Spark/Surge/Circuit/Nexus, their personalised badge image (username on the ribbon) is posted automatically to the team's Telegram payments topic and the main group, once per badge; the Overview badge card also has a 'Post to Telegram' button for badges unlocked earlier, and 'Share' downloads the image. +- ACHIEVEMENT BADGES ON TELEGRAM (2026-09-13): when a member unlocks Spark/Surge/Circuit/Nexus, their personalised badge image (username on the ribbon) is posted automatically to the team's Telegram payments topic and the main group, once per badge; members cannot trigger posts themselves (the 'Post to Telegram' button is admin-only); 'Share' downloads the image. - HOLDING TANK ALERTS: when new members land in the tank, a note at the top of every member's Overview names them (usernames) and a post goes to the team's Telegram payments topic; adopt from My line > Holding tank (your own $20 package required). - LEGACY WELCOME (former Faucet Wave / Tier One Ads members): they join through instantadpay.com/from/faucetwave or instantadpay.com/from/tieroneads and, if their email is on the legacy list, welcome-back credits are added automatically at signup (former advertisers 500, former earners 150; once per person; credits, not POL). They land in the holding tank like any member who joins without a sponsor. - PROMO CODES: partner site owners get a reusable code; a member redeems it on a join link (?promo=CODE) or in the Overview box "Have a promo code?" and receives free ad credits (amount set per code by the admin, one use per account; codes can cap uses or expire). Credits, not POL. diff --git a/public/assets/my.js b/public/assets/my.js index dd6a5b5..be3e94f 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -87,13 +87,16 @@ return '
' + '' + b.label + ' badge' + '
' + b.label + '
' + b.sub + '
' - + (got ? ' ' : '
๐Ÿ”’ locked
') + '
'; + + (got ? ' ' : '
๐Ÿ”’ locked
') + ''; }).join(''); strip.querySelectorAll('[data-badge]').forEach(btn => btn.addEventListener('click', () => shareBadge(btn.dataset.badge, d))); strip.querySelectorAll('[data-badgepost]').forEach(btn => - btn.addEventListener('click', () => postBadge(btn.dataset.badgepost, d, btn))); - fetch('/api/my/badge-posted').then(r => r.json()).then(r => (r.posted || []).forEach(k => { const b = strip.querySelector('[data-badgepost="' + k + '"]'); if (b) { b.textContent = 'Posted โœ“'; b.disabled = true; } })).catch(() => {}); + btn.addEventListener('click', () => postBadge(btn.dataset.badgepost, d, btn, true))); + fetch('/api/my/badge-posted').then(r => r.json()).then(r => { // the manual button is the admin's only (Marty, 2026-09-13) + if (r.canPost) strip.querySelectorAll('[data-badgepost]').forEach(b => { b.hidden = false; }); + (r.posted || []).forEach(k => { const b = strip.querySelector('[data-badgepost="' + k + '"]'); if (b) { b.textContent = 'Posted โœ“'; b.disabled = true; } }); + }).catch(() => {}); // celebrate anything newly granted this load, and post the branded badge to the team channels (Marty, 2026-09-13) if (d.milestonesGranted && d.milestonesGranted.length) { const total = d.milestonesGranted.reduce((s, g) => s + g.credited, 0); @@ -124,14 +127,14 @@ img.onerror = () => cb(null); img.src = b.img; } - async function postBadge(key, d, btn) { + async function postBadge(key, d, btn, manual) { if (btn && btn.disabled) return; if (btn) { btn.disabled = true; btn.textContent = 'Postingโ€ฆ'; } composeBadge(key, d, c => { if (!c) { if (btn) { btn.disabled = false; btn.textContent = 'Post to Telegram'; } return; } c.toBlob(async bl => { try { - const r = await (await fetch('/api/my/badge-post?key=' + encodeURIComponent(key), { method: 'POST', headers: { 'Content-Type': 'image/jpeg' }, body: bl })).json(); + const r = await (await fetch('/api/my/badge-post?key=' + encodeURIComponent(key) + (manual ? '&manual=1' : ''), { method: 'POST', headers: { 'Content-Type': 'image/jpeg' }, body: bl })).json(); if (r.error) throw new Error(r.error); if (btn) { btn.textContent = 'Posted โœ“'; btn.disabled = true; } if (!r.already) IAP.status('Your badge is posted in the team channels.', 'ok'); diff --git a/public/my.html b/public/my.html index 37f8930..84f25cc 100644 --- a/public/my.html +++ b/public/my.html @@ -915,7 +915,7 @@ - + diff --git a/server.js b/server.js index 842f6f1..a6eb72b 100644 --- a/server.js +++ b/server.js @@ -1293,6 +1293,8 @@ const server = http.createServer(async (req, res) => { if (!held.includes(key)) return json(res, 400, { error: 'You have not unlocked that badge yet.' }); const log = badgeLog(); const mine = log[s.email] || {}; if (mine[key]) return json(res, 200, { ok: true, already: true }); + // manual re-posts (the button) are Marty's only; members' badges go out automatically on unlock + if (u.searchParams.get('manual') === '1' && s.email !== ADMIN_EMAIL) return json(res, 403, { error: 'Badges post automatically when they unlock.' }); const a = await accounts.byEmail(s.email); const who = a && a.username ? '@' + a.username : (a && a.memberId ? 'member #' + a.memberId : 'a member'); const link = a && a.username ? 'instantadpay.com/join/' + a.username : 'instantadpay.com'; @@ -1311,7 +1313,7 @@ const server = http.createServer(async (req, res) => { if (p === '/api/my/badge-posted' && req.method === 'GET') { // which of my badges are already on Telegram const s = await auth.fromRequest(req); if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); - return json(res, 200, { posted: Object.keys(badgeLog()[s.email] || {}) }); + return json(res, 200, { posted: Object.keys(badgeLog()[s.email] || {}), canPost: s.email === ADMIN_EMAIL }); } if (p === '/api/my/tank/adopt' && req.method === 'POST') { const s = await auth.fromRequest(req);