Spaces:
Running
Running
Download tg.js from CyberMax-tools/stoopshake: direct link, hf CLI and curl.
- Browser
- Download file 8.01 kB
-
https://huggingface.co/spaces/CyberMax-tools/stoopshake/resolve/main/tg.js
- Command line
-
hf download hf://spaces/CyberMax-tools/stoopshake/tg.js
-
curl -L -o tg.js https://huggingface.co/spaces/CyberMax-tools/stoopshake/resolve/main/tg.js
8.01 kB
| // Stoopshake as a Telegram Mini App (opened from @CyberMaxTools_bot: menu button, /play, challenge deep links, the | |
| // pinned channel post). Inside Telegram: telegram-web-app.js, full-height stage with safe areas, Telegram theme colours, | |
| // haptics, sharing into chats, and purchases with Telegram Stars only (Telegram's rule for digital goods in Mini Apps): | |
| // Crew Pass (full game) $2.99 = 235 Stars, Dice & Table Pack $1.99 = 155 Stars (same USD price points as the web, 0.013 USD/Star rule). | |
| // Payment: data Worker /tg/game-invoice -> WebApp.openInvoice; ownership comes back from /tg/game-owned as signed codes | |
| // that the normal unlock check accepts, so a Telegram purchase also unlocks the web game. (c) 2026 CyberMax. | |
| const DATA = 'https://data.cybermaxtools.com'; | |
| export const BOT = 'CyberMaxTools_bot'; | |
| export const ITEMS = { 'stoopshake-full': { stars: 235, usd: '$2.99', name: 'the Crew Pass' }, 'stoopshake-skins': { stars: 155, usd: '$1.99', name: 'the Dice & Table Pack' } }; | |
| const q = new URLSearchParams(location.search); | |
| export const inTG = q.has('tg') || /tgWebAppData=/.test(location.hash); | |
| let W = null; | |
| export const app = () => W; | |
| function load() { | |
| return new Promise((res) => { | |
| if (window.Telegram && window.Telegram.WebApp) { res(window.Telegram.WebApp); return; } | |
| const s = document.createElement('script'); s.src = 'https://telegram.org/js/telegram-web-app.js'; | |
| s.onload = () => res(window.Telegram && window.Telegram.WebApp); s.onerror = () => res(null); document.head.appendChild(s); | |
| }); | |
| } | |
| function insets() { | |
| if (!W) return; | |
| const a = W.safeAreaInset || {}; const c = W.contentSafeAreaInset || {}; | |
| const r = document.documentElement.style; | |
| r.setProperty('--tgt', `${(a.top || 0) + (c.top || 0)}px`); r.setProperty('--tgb', `${(a.bottom || 0) + (c.bottom || 0)}px`); | |
| r.setProperty('--tgh', `${W.viewportStableHeight || window.innerHeight}px`); | |
| } | |
| /** Start the Mini App shell. onResize: re-layout the canvas; onBack: Telegram back button pressed. */ | |
| export async function init({ onResize, onBack } = {}) { | |
| if (!inTG) return null; | |
| document.body.classList.add('tg'); | |
| W = await load(); | |
| if (!W || !W.initData) { document.body.classList.remove('tg'); W = null; return null; } // opened with ?tg=1 outside Telegram: plain web game | |
| try { W.ready(); W.expand(); } catch { /* old client */ } | |
| try { if (W.disableVerticalSwipes) W.disableVerticalSwipes(); } catch { /* < 7.7 */ } | |
| try { W.setHeaderColor('#0B0912'); W.setBackgroundColor('#07060C'); if (W.setBottomBarColor) W.setBottomBarColor('#07060C'); } catch { /* < 6.1 */ } | |
| const tp = W.themeParams || {}; | |
| if (tp.button_color) document.documentElement.style.setProperty('--tg-btn', tp.button_color); | |
| insets(); | |
| for (const ev of ['viewportChanged', 'safeAreaChanged', 'contentSafeAreaChanged']) { try { W.onEvent(ev, () => { insets(); onResize && onResize(); }); } catch { /* */ } } | |
| try { W.BackButton.onClick(() => onBack && onBack()); } catch { /* */ } | |
| onResize && onResize(); | |
| return W; | |
| } | |
| export function back(show) { try { if (W) (show ? W.BackButton.show() : W.BackButton.hide()); } catch { /* */ } } | |
| export function haptic(kind) { | |
| if (!W || !W.HapticFeedback) return; | |
| try { | |
| if (kind === 'select') W.HapticFeedback.selectionChanged(); | |
| else if (kind === 'success' || kind === 'error' || kind === 'warning') W.HapticFeedback.notificationOccurred(kind); | |
| else W.HapticFeedback.impactOccurred(kind || 'light'); | |
| } catch { /* */ } | |
| } | |
| /** Share into a Telegram chat (the native chat picker). */ | |
| export function share(text, url) { | |
| const link = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`; | |
| try { W.openTelegramLink(link); } catch { window.open(link, '_blank'); } | |
| } | |
| export const playUrl = () => `https://t.me/${BOT}?start=dice`; | |
| async function post(path, body) { | |
| const r = await fetch(DATA + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, initData: W.initData }) }); | |
| return r.json().catch(() => ({})); | |
| } | |
| /** What this Telegram user already bought (codes the normal unlock check accepts). */ | |
| export async function owned() { if (!W) return {}; try { return (await post('/tg/game-owned', {})).codes || {}; } catch { return {}; } } | |
| /** Buy with Stars: opens Telegram's own payment sheet. Resolves 'paid' | 'cancelled' | 'failed' | 'pending'. */ | |
| export async function buy(item) { | |
| if (!W) return 'failed'; | |
| let j; try { j = await post('/tg/game-invoice', { item }); } catch { j = {}; } | |
| if (!j.link) return 'failed'; | |
| return new Promise((res) => { try { W.openInvoice(j.link, (status) => res(status)); } catch { res('failed'); } }); | |
| } | |
| /** The Stars purchase sheet shown instead of the web checkout inside Telegram. */ | |
| export function sheet(item, { perks = [], onPaid } = {}) { | |
| const it = ITEMS[item]; let d = document.getElementById('tgbuy'); | |
| if (!d) { d = document.createElement('dialog'); d.id = 'tgbuy'; d.className = 'funpass'; document.body.appendChild(d); d.addEventListener('click', (e) => { if (e.target === d || e.target.closest('[data-close]')) d.close(); }); } | |
| d.innerHTML = `<div class="fp"><button class="fp-x" data-close aria-label="Close">×</button><p class="fp-kick">Stoopshake</p><h2>Get ${it.name}: ${it.stars} ⭐</h2> | |
| <ul class="fp-perks">${perks.map((p) => `<li>${p}</li>`).join('')}<li>One payment, yours for good (also on the web with the code we send you)</li></ul> | |
| <p class="fp-row"><button class="btn pass" type="button" data-buy>Pay ${it.stars} ⭐ with Telegram Stars</button></p> | |
| <p class="fp-small">Paid through Telegram. 14-day promise: if it doesn't work as described, we fix it or refund your Stars (/paysupport in @${BOT}).</p></div>`; | |
| d.querySelector('[data-buy]').addEventListener('click', async (e) => { | |
| e.target.disabled = true; e.target.textContent = 'Opening Telegram payment…'; | |
| const st = await buy(item); | |
| if (st === 'paid') { haptic('success'); d.close(); onPaid && onPaid(); } else { e.target.disabled = false; e.target.textContent = `Pay ${it.stars} ⭐ with Telegram Stars`; if (st === 'failed') haptic('error'); } | |
| }); | |
| if (d.showModal) d.showModal(); else d.setAttribute('open', ''); | |
| } | |
| // ---------- growth loops (all optional for the player; rewards are cosmetic only) | |
| export const CHANNEL = 'cybermaxtools'; | |
| const startParam = () => { try { return (W && W.initDataUnsafe && W.initDataUnsafe.start_param) || ''; } catch { return ''; } }; | |
| export function refFromLaunch() { const r = q.get('ref') || (/^r_([a-z0-9]{6,16})$/.exec(startParam()) || [])[1] || ''; return /^[a-z0-9]{6,16}$/.test(r) ? r : ''; } | |
| /** First open: records who invited this player (only for players we have never seen). */ | |
| export async function hello() { if (!W) return {}; try { return await post('/tg/game-hello', { game: 'stoopshake', ref: refFromLaunch() }); } catch { return {}; } } | |
| /** After the first finished round: records the invite as played (cosmetic rewards only). */ | |
| export async function played() { if (!W) return {}; try { return await post('/tg/game-played', {}); } catch { return {}; } } | |
| /** Everything this player has: paid codes, free cosmetics, invite link, channel follow. */ | |
| export async function status() { if (!W) return {}; try { return await post('/tg/game-owned', {}); } catch { return {}; } } | |
| export function invite(link) { share('Shake dice with me on Stoopshake: free 3D street dice (4-5-6). No real money, just bragging rights.', link); } | |
| export function joinChannel(onBack) { | |
| try { W.openTelegramLink(`https://t.me/${CHANNEL}`); } catch { window.open(`https://t.me/${CHANNEL}`, '_blank'); } | |
| const again = () => { try { W.offEvent('activated', again); } catch { /* */ } onBack && onBack(); }; | |
| try { W.onEvent('activated', again); } catch { setTimeout(() => onBack && onBack(), 8000); } | |
| } | |
| /** Challenge a friend: the native Telegram share sheet with the game's own challenge link (state lives in the link). */ | |
| export function challenge(text, url) { share(text, url); } | |