您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Beep on 5M+ money gain, suppress if caused by 'Change' button in trade. Singleton per tab. Toast always shown. Periodic fallback for missed changes.
// ==UserScript== // @name Torn - Beep on 5M+ Gain (Singleton + Status Toast) // @namespace zeshu.money.beep // @version 4.2 // @description Beep on 5M+ money gain, suppress if caused by 'Change' button in trade. Singleton per tab. Toast always shown. Periodic fallback for missed changes. // @author zeshu // @match https://www.torn.com/* // @grant none // ==/UserScript== (function () { 'use strict'; const THRESHOLD = 5000000; const SUPPRESS_DURATION_MS = 2000; const SINGLETON_KEY = 'moneyBeepActiveTab'; const LEADER_TIMEOUT = 15000; const HEARTBEAT_INTERVAL = 5000; let last = 0; let suppressBeep = false; let isLeader = false; // === Toast Display let persistentToast; function showToast(message) { if (!persistentToast) { persistentToast = document.createElement('div'); Object.assign(persistentToast.style, { position: 'fixed', top: '20px', left: '20px', background: 'rgba(30, 30, 30, 0.95)', color: '#fff', padding: '10px 16px', borderRadius: '8px', fontSize: '14px', zIndex: 99999, boxShadow: '0 4px 10px rgba(0, 0, 0, 0.4)', maxWidth: '300px', lineHeight: '1.4' }); document.body.appendChild(persistentToast); } persistentToast.textContent = message; } // === Audio Setup let audioContext; function getAudioContext() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } return audioContext; } ['click', 'keydown', 'touchstart'].forEach(evt => document.addEventListener(evt, () => { getAudioContext().resume().catch(console.warn); }, { once: true }) ); async function pleasantBeep() { const ctx = getAudioContext(); if (ctx.state !== 'running') { try { await ctx.resume(); } catch (e) { console.warn('[MoneyBeep] AudioContext blocked:', e); showToast('⚠️ Beep blocked until user interaction'); return; } } const gain = ctx.createGain(); gain.connect(ctx.destination); const tone = (f, t, d) => { const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.setValueAtTime(f, ctx.currentTime + t); o.connect(gain); gain.gain.setValueAtTime(0.4, ctx.currentTime + t); gain.gain.linearRampToValueAtTime(0, ctx.currentTime + t + d); o.start(ctx.currentTime + t); o.stop(ctx.currentTime + t + d); }; tone(880, 0.0, 0.2); tone(660, 0.25, 0.3); } function watchChangeButtonClicks() { document.body.addEventListener('click', (e) => { const btn = e.target?.closest('input[type="submit"].torn-btn'); if (btn && btn.value === 'Change') { suppressBeep = true; setTimeout(() => suppressBeep = false, SUPPRESS_DURATION_MS); } }); } function monitorMoney() { const el = document.querySelector('#user-money'); if (!el) { console.log('[MoneyBeep] #user-money not found — skipping'); return; } const getMoneyValue = () => parseInt(el.getAttribute('data-money')?.replace(/,/g, '') || '0', 10); const check = () => { const current = getMoneyValue(); if (last === 0) { last = current; console.log('[MoneyBeep] Initial money value set:', current); return; } const diff = current - last; if (diff >= THRESHOLD && !suppressBeep) { console.log(`[MoneyBeep] Gain detected: +${diff.toLocaleString()}`); pleasantBeep(); } else if (diff !== 0) { console.log(`[MoneyBeep] Money change: ${diff.toLocaleString()}`); } last = current; }; // Observe DOM attribute change const observer = new MutationObserver(check); observer.observe(el, { attributes: true, attributeFilter: ['data-money'] }); // Periodic fallback setInterval(check, 3000); check(); } // === Singleton Logic function tryBecomeLeader() { const now = Date.now(); const existing = parseInt(localStorage.getItem(SINGLETON_KEY) || '0', 10); const active = now - existing < LEADER_TIMEOUT; if (!active && document.visibilityState === 'visible') { // Claim leadership optimistically localStorage.setItem(SINGLETON_KEY, now.toString()); // Wait briefly, then recheck to see if another tab claimed it too setTimeout(() => { const check = parseInt(localStorage.getItem(SINGLETON_KEY) || '0', 10); const stillMine = Math.abs(check - now) < 100; // close enough match if (stillMine) { isLeader = true; const t = new Date().toLocaleTimeString(); showToast(`🟢 MoneyBeep active (${t})`); monitorMoney(); watchChangeButtonClicks(); setInterval(updateHeartbeat, HEARTBEAT_INTERVAL); } else { isLeader = false; const t = new Date().toLocaleTimeString(); showToast(`🟡 MoneyBeep lost race (${t})`); } }, 250); // small delay for contention resolution return true; } if (active && document.visibilityState === 'visible') { const t = new Date().toLocaleTimeString(); showToast(`🟡 MoneyBeep passive (${t})`); } return false; } function updateHeartbeat() { if (isLeader) { localStorage.setItem(SINGLETON_KEY, Date.now().toString()); } } window.addEventListener('storage', (e) => { if (e.key === SINGLETON_KEY && !document.hidden) { const now = Date.now(); const val = parseInt(e.newValue || '0', 10); if (now - val < LEADER_TIMEOUT) { isLeader = false; } } }); window.addEventListener('beforeunload', () => { if (isLeader) { localStorage.removeItem(SINGLETON_KEY); } }); // === Defer startup until page idle function defer(fn) { if ('requestIdleCallback' in window) { requestIdleCallback(fn, { timeout: 3000 }); } else { setTimeout(fn, 2000); } } window.addEventListener('load', () => { // Avoid running on pages without money display if (!document.querySelector('#user-money')) return; defer(() => { const becameLeader = tryBecomeLeader(); if (becameLeader) { monitorMoney(); watchChangeButtonClicks(); setInterval(updateHeartbeat, HEARTBEAT_INTERVAL); } }); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址