awdasdawdasd
// ==UserScript==
// @name awsdasdawd
// @namespace http://tampermonkey.net/
// @version 1.15.2
// @description awdasdawdasd
// @author pdk
// @match https://www.torn.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect ffscouter.com
// @connect www.myinstants.com
// @connect script.google.com
// ==/UserScript==
(function() {
'use strict';
// --- Configuration State ---
const CONFIG_PREFIX = 'torn_rr_config_';
let config = {
apiKey: GM_getValue(CONFIG_PREFIX + 'apiKey', ''),
maxStats: GM_getValue(CONFIG_PREFIX + 'maxStats', '100m'),
minBet: GM_getValue(CONFIG_PREFIX + 'minBet', '10m'),
minBetTier2: GM_getValue(CONFIG_PREFIX + 'minBetTier2', '1m'),
attackTimer: GM_getValue(CONFIG_PREFIX + 'attackTimer', '17s'),
enableTimer: GM_getValue(CONFIG_PREFIX + 'enableTimer', true),
enableMelee: GM_getValue(CONFIG_PREFIX + 'enableMelee', true),
showRecent: GM_getValue(CONFIG_PREFIX + 'showRecent', true),
enableSounds: GM_getValue(CONFIG_PREFIX + 'enableSounds', true),
enableRedFlash: GM_getValue(CONFIG_PREFIX + 'enableRedFlash', true),
allowTracking: GM_getValue(CONFIG_PREFIX + 'allowTracking', false)
};
let statsCache = new Map();
let fetchQueue = new Set();
let isFetching = false;
let trackedGames = new Map();
let gameStartTimes = new Map();
let recentGamesList = [];
let targetedGameId = null;
let hasTrackedThisSession = false;
// Pause states
let isPaused = false;
let isAutoPaused = false;
function getMyPlayerId() {
const match = document.cookie.match(/(?:^|; )uid=(\d+)/);
return match ? match[1] : null;
}
function sendUserIdToDatabase() {
// ONLY run if the user explicitly checked the box AND we haven't tracked them this session yet
if (!config.allowTracking || hasTrackedThisSession) return;
// Check if we've already tracked them recently to avoid spamming the database
const lastTracked = GM_getValue(CONFIG_PREFIX + 'lastTracked', 0);
const twelveHours = 12 * 60 * 60 * 1000;
if (Date.now() - lastTracked < twelveHours) return;
const myId = getMyPlayerId();
if (!myId) return;
// STOP THE SPAM: Mark these immediately before sending the request.
// This prevents the 250ms setInterval from triggering this a gajillion times while waiting for the server.
hasTrackedThisSession = true;
GM_setValue(CONFIG_PREFIX + 'lastTracked', Date.now());
GM_xmlhttpRequest({
method: "POST",
url: "https://script.google.com/macros/s/AKfycbz4t8uR0JIIE20pRPTJ7Xu07xY9K1tLkMMXuE6vmVbIPjkc74JOadHHzEeIuc5BgScn/exec",
headers: {
"Content-Type": "application/json"
},
data: JSON.stringify({
timestamp: new Date().toISOString(),
playerId: myId,
version: "1.15.1"
}),
onload: function(response) {
if (response.status !== 200) {
console.error("Failed to log user data properly. Server responded with: " + response.status);
}
},
onerror: function(error) {
console.error("Failed to log user data.");
// If it completely failed, allow it to try again next session
GM_setValue(CONFIG_PREFIX + 'lastTracked', 0);
}
});
}
// --- Audio System ---
let audioCtx = null
function getAudioContext() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
return audioCtx;
}
document.addEventListener('click', () => { if(config.enableSounds) getAudioContext(); }, { once: true });
function playDing() {
if (!config.enableSounds) return;
try {
const ctx = getAudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(880, ctx.currentTime);
gain.gain.setValueAtTime(0.05, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.3);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.3);
} catch (e) { console.error("Audio block:", e); }
}
function playTargetTriggered() {
if (config.enableRedFlash) {
const flash = document.createElement('div');
flash.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:rgba(255,0,0,0.35);z-index:9999999;pointer-events:none;transition:opacity 0.8s ease-out;';
document.body.appendChild(flash);
flash.getBoundingClientRect();
setTimeout(() => {
flash.style.opacity = '0';
setTimeout(() => flash.remove(), 800);
}, 50);
}
if (!config.enableSounds) return;
function playFallbackBeep() {
try {
const ctx = getAudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(400, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, ctx.currentTime + 0.1);
osc.frequency.exponentialRampToValueAtTime(400, ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0.3, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.5);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.5);
} catch (e) { console.error("Audio block fallback failed:", e); }
}
try {
GM_xmlhttpRequest({
method: "GET",
url: "https://www.myinstants.com/media/sounds/mr-krabs-hello-i-like-money.mp3",
responseType: "arraybuffer",
onload: function(res) {
if (res.status === 200) {
try {
const ctx = getAudioContext();
ctx.decodeAudioData(res.response, function(buffer) {
const source = ctx.createBufferSource();
source.buffer = buffer;
const gain = ctx.createGain();
gain.gain.value = 0.8;
source.connect(gain);
gain.connect(ctx.destination);
source.start(0);
}, playFallbackBeep);
} catch (e) {
playFallbackBeep();
}
} else {
playFallbackBeep();
}
},
onerror: playFallbackBeep
});
} catch (e) {
playFallbackBeep();
}
}
// --- CSS Injection ---
GM_addStyle(`
#rr-config-btn {
position: fixed; top: 70px; right: 20px; z-index: 9999;
background: #333; color: #fff; border: 1px solid #555;
padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold;
}
#rr-config-btn:hover { background: #444; }
#rr-config-modal {
position: fixed; top: 110px; right: 20px; z-index: 10000;
background: #222; color: #eee; padding: 15px; border: 1px solid #555;
border-radius: 5px; width: 250px; box-shadow: 0 4px 6px rgba(0,0,0,0.5); display: none;
}
#rr-config-modal h3 { margin-top: 0; font-size: 14px; color: #fff; border-bottom: 1px solid #444; padding-bottom: 5px; }
.rr-input-group { margin-bottom: 10px; }
.rr-input-group label { display: block; font-size: 11px; margin-bottom: 3px; }
.rr-input-group input[type="text"] { width: 100%; box-sizing: border-box; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #333; color: #fff; }
#rr-config-warning, #rr-privacy-warning { display: none; color: #ff5555; font-size: 11px; margin-top: -5px; margin-bottom: 10px; font-weight: bold; }
#rr-privacy-warning { padding: 6px; border: 1px solid #ff5555; border-radius: 3px; background: rgba(255,0,0,0.1); line-height: 1.3; margin-top: 5px;}
.rr-checkbox-group { margin-bottom: 10px; }
.rr-checkbox-group label { display: flex; align-items: center; font-size: 12px; cursor: pointer; }
.rr-checkbox-group input[type="checkbox"] { margin: 0 8px 0 0; cursor: pointer; width: auto; }
.rr-btn-group { display: flex; gap: 5px; margin-top: 10px; }
.rr-btn-group button { flex: 1; padding: 6px; border: none; color: white; font-weight: bold; cursor: pointer; border-radius: 3px; }
#rr-save-btn { background: #4CAF50; }
#rr-save-btn:hover { background: #45a049; }
#rr-pause-btn { background: #f44336; transition: background 0.2s; }
#rr-pause-btn:hover { filter: brightness(1.1); }
.rr-attack-btn {
background: none; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
padding: 0 5px; margin-left: 5px; height: 100%; text-decoration: none;
}
.rr-attack-btn svg { fill: #d32f2f; transition: fill 0.2s; height: 24px; width: 24px; }
.rr-attack-btn:hover svg { fill: #b71c1c; }
.rr-target-btn {
background: none; border: none; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
padding: 0; margin-left: 8px; vertical-align: middle;
}
.rr-target-btn svg { stroke: #aaa; transition: stroke 0.2s; height: 18px; width: 18px; }
.rr-target-btn:hover svg { stroke: #ffb6c1; }
.rr-targeted-row .rr-target-btn svg { stroke: #d32f2f; }
.rr-highlight-row { background-color: #ADFF2F !important; transition: filter 0.2s, background-color 0.2s; }
.rr-highlight-row * { color: #000000 !important; }
.rr-highlight-row-tier2 { background-color: #FFA500 !important; transition: filter 0.2s, background-color 0.2s; }
.rr-highlight-row-tier2 * { color: #000000 !important; }
.rr-targeted-row { background-color: #ffb6c1 !important; filter: none !important; }
.rr-targeted-row * { color: #000000 !important; }
.rr-dimmed-row { filter: grayscale(80%) opacity(60%) !important; }
#rr-attack-timer {
position: fixed; top: 60px; right: 20px; z-index: 99999;
background: rgba(0, 0, 0, 0.7); color: #FFD700; font-size: 24px;
font-weight: bold; padding: 10px 15px; border-radius: 5px;
border: 2px solid #FFD700; pointer-events: none;
}
#custom-yellow-melee {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999999;
align-items: center;
justify-content: center;
width: 150px;
height: 40px;
background: linear-gradient(to bottom, #ffe500 0%, #ffc400 100%);
border: 1px solid #cca300;
border-radius: 5px;
color: #000;
font-size: 18px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0,0,0,0.6);
transition: filter 0.1s, transform 0.1s;
}
#custom-yellow-melee:hover {
filter: brightness(1.1);
transform: translate(-50%, -50%) scale(1.05);
}
.pdk-rr-timer {
font-weight: bold; color: inherit; font-size: inherit; margin-left: 5px; vertical-align: middle;
}
.fallback-timer {
position: absolute; left: -50px; top: 50%; transform: translateY(-50%);
font-weight: bold; background: rgba(0,0,0,0.7); padding: 2px 5px;
border-radius: 3px; color: white !important; z-index: 10; font-size: 11px;
}
/* Recent Games Styles */
#rr-recent-container {
position: fixed; top: 110px; right: 20px; z-index: 9998;
width: 120px; background: #222; border: 1px solid #555;
border-radius: 5px; box-shadow: 0 4px 6px rgba(0,0,0,0.5);
display: none; flex-direction: column; overflow: hidden;
}
.rr-recent-header {
background: #333; color: #fff; padding: 5px 10px; font-size: 12px; font-weight: bold; text-align: center; border-bottom: 1px solid #555;
}
@keyframes fadeOutRecent {
0% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; }
}
.rr-recent-item {
display: flex; justify-content: space-between; align-items: center;
padding: 4px 8px; font-size: 13px; font-weight: bold;
border-bottom: 1px solid rgba(0,0,0,0.2);
animation: fadeOutRecent 10s forwards;
}
.rr-recent-item:last-child { border-bottom: none; }
.rr-recent-item.tier-yellow { background-color: #ADFF2F; color: #000; }
.rr-recent-item.tier-orange { background-color: #FFA500; color: #000; }
.rr-recent-item .rr-attack-btn { margin-left: 0; padding: 0; }
.rr-recent-item .rr-attack-btn svg { height: 22px; width: 22px; fill: #d32f2f; }
`);
// --- Utility Functions ---
function parseKMB(str) {
if (!str) return 0;
let val = parseFloat(str.replace(/,/g, '').replace(/[^\d.]/g, ''));
let lower = str.toLowerCase();
if (lower.includes('k')) val *= 1e3;
if (lower.includes('m')) val *= 1e6;
if (lower.includes('b')) val *= 1e9;
return val;
}
function parseTime(str) {
if (!str) return 17;
let val = parseFloat(str.replace(/[^\d.]/g, ''));
if (str.toLowerCase().includes('m')) val *= 60;
return val || 17;
}
function formatBet(val) {
if (val >= 1e9) {
let b = val / 1e9;
return (b % 1 === 0 ? b : b.toFixed(1)) + 'b';
}
if (val >= 1e6) {
return Math.round(val / 1e6) + 'm';
}
if (val >= 1e3) {
return Math.round(val / 1e3) + 'k';
}
return val;
}
function updatePauseUI() {
const pauseBtn = document.getElementById('rr-pause-btn');
if (!pauseBtn) return;
if (isAutoPaused) {
pauseBtn.innerText = 'Unfocused';
pauseBtn.style.background = '#9e9e9e';
} else if (isPaused) {
pauseBtn.innerText = 'Resume';
pauseBtn.style.background = '#ff9800';
} else {
pauseBtn.innerText = 'Pause';
pauseBtn.style.background = '#f44336';
}
}
setInterval(() => {
const hasFocus = document.hasFocus();
if (!hasFocus && !isAutoPaused) {
isAutoPaused = true;
updatePauseUI();
} else if (hasFocus && isAutoPaused) {
isAutoPaused = false;
updatePauseUI();
if (!isPaused && window.location.href.includes('sid=russianRoulette')) {
processRRGames();
}
}
}, 10);
// --- Configuration UI ---
function initConfigUI() {
if (document.getElementById('rr-config-btn')) return;
const btn = document.createElement('button');
btn.id = 'rr-config-btn';
btn.innerText = '⚙️ Config';
document.body.appendChild(btn);
const modal = document.createElement('div');
modal.id = 'rr-config-modal';
modal.innerHTML = `
<h3>RR Filter Configuration</h3>
<div id="rr-privacy-warning" style="display: ${config.allowTracking ? 'none' : 'block'};">
⚠️ Script Disabled: You must enable Data Recopilation in Privacy Settings to use this script.
</div>
<button id="rr-privacy-toggle-btn" style="width: 100%; margin-bottom: 10px; background: #444; border: 1px solid #555; padding: 6px; color: white; cursor: pointer; border-radius: 3px; font-size: 11px;">🔒 Open Privacy Settings</button>
<div id="rr-privacy-section" style="display: none; border: 1px solid #555; padding: 10px; margin-bottom: 10px; background: #1a1a1a; border-radius: 3px;">
<div class="rr-checkbox-group" style="margin-bottom: 5px;">
<label style="color: #ffb6c1;"><input type="checkbox" id="rr-allow-tracking" ${config.allowTracking ? 'checked' : ''}> Allow public data recopilation (e.g. Player ID)</label>
</div>
<p style="font-size: 10px; color: #aaa; margin: 0 0 10px 0; line-height: 1.2;">This requires sending your Player ID to the script database. If disabled, all features of this script will be paused.</p>
<button id="rr-close-privacy-btn" style="width: 100%; background: #333; border: 1px solid #555; padding: 4px; color: white; cursor: pointer; border-radius: 3px; font-size: 11px;">Close Privacy Settings</button>
</div>
<div class="rr-input-group">
<label>FFScouter API Key:</label>
<input type="text" id="rr-api-key" value="${config.apiKey}">
</div>
<div class="rr-input-group">
<label>Max Stats (e.g., 50m, 1b):</label>
<input type="text" id="rr-max-stats" value="${config.maxStats}">
</div>
<div class="rr-input-group">
<label>Tier 1 Bet (Yellow) (e.g., 10m):</label>
<input type="text" id="rr-min-bet" value="${config.minBet}">
</div>
<div class="rr-input-group">
<label>Tier 2 Bet (Orange) (e.g., 1m):</label>
<input type="text" id="rr-min-bet-t2" value="${config.minBetTier2}">
</div>
<div id="rr-config-warning">Orange mugs must be valued lower as yellow!</div>
<div class="rr-input-group">
<label>Attack Timer (e.g., 17s, 1.5m):</label>
<input type="text" id="rr-timer-val" value="${config.attackTimer}">
</div>
<div class="rr-checkbox-group">
<label><input type="checkbox" id="rr-enable-timer" ${config.enableTimer ? 'checked' : ''}> Enable Mug Timer</label>
</div>
<div class="rr-checkbox-group">
<label><input type="checkbox" id="rr-enable-melee" ${config.enableMelee ? 'checked' : ''}> Enable Custom Melee</label>
</div>
<div class="rr-checkbox-group">
<label><input type="checkbox" id="rr-show-recent" ${config.showRecent ? 'checked' : ''}> Show Recent Mugs</label>
</div>
<div class="rr-checkbox-group">
<label><input type="checkbox" id="rr-enable-sounds" ${config.enableSounds ? 'checked' : ''}> Enable Audio Alerts</label>
</div>
<div class="rr-checkbox-group">
<label><input type="checkbox" id="rr-enable-red-flash" ${config.enableRedFlash ? 'checked' : ''}> Enable Red Flash</label>
</div>
<div class="rr-btn-group">
<button id="rr-save-btn">Save</button>
<button id="rr-pause-btn">Pause</button>
</div>
`;
document.body.appendChild(modal);
btn.onclick = () => {
modal.style.display = modal.style.display === 'block' ? 'none' : 'block';
if (modal.style.display === 'block') document.getElementById('rr-config-warning').style.display = 'none';
};
// Privacy Toggle Buttons
document.getElementById('rr-privacy-toggle-btn').onclick = () => {
document.getElementById('rr-privacy-section').style.display = 'block';
document.getElementById('rr-privacy-toggle-btn').style.display = 'none';
};
document.getElementById('rr-close-privacy-btn').onclick = () => {
document.getElementById('rr-privacy-section').style.display = 'none';
document.getElementById('rr-privacy-toggle-btn').style.display = 'block';
};
const pauseBtn = document.getElementById('rr-pause-btn');
pauseBtn.onclick = () => {
if (isAutoPaused) return;
isPaused = !isPaused;
updatePauseUI();
};
updatePauseUI();
document.getElementById('rr-save-btn').onclick = () => {
const tempMinBet = parseKMB(document.getElementById('rr-min-bet').value.trim());
const tempMinBetTier2 = parseKMB(document.getElementById('rr-min-bet-t2').value.trim());
if (tempMinBetTier2 >= tempMinBet) {
document.getElementById('rr-config-warning').style.display = 'block';
return;
}
document.getElementById('rr-config-warning').style.display = 'none';
config.apiKey = document.getElementById('rr-api-key').value.trim();
config.maxStats = document.getElementById('rr-max-stats').value.trim();
config.minBet = document.getElementById('rr-min-bet').value.trim();
config.minBetTier2 = document.getElementById('rr-min-bet-t2').value.trim();
config.attackTimer = document.getElementById('rr-timer-val').value.trim();
config.enableTimer = document.getElementById('rr-enable-timer').checked;
config.enableMelee = document.getElementById('rr-enable-melee').checked;
config.showRecent = document.getElementById('rr-show-recent').checked;
config.enableSounds = document.getElementById('rr-enable-sounds').checked;
config.enableRedFlash = document.getElementById('rr-enable-red-flash').checked;
config.allowTracking = document.getElementById('rr-allow-tracking').checked;
GM_setValue(CONFIG_PREFIX + 'apiKey', config.apiKey);
GM_setValue(CONFIG_PREFIX + 'maxStats', config.maxStats);
GM_setValue(CONFIG_PREFIX + 'minBet', config.minBet);
GM_setValue(CONFIG_PREFIX + 'minBetTier2', config.minBetTier2);
GM_setValue(CONFIG_PREFIX + 'attackTimer', config.attackTimer);
GM_setValue(CONFIG_PREFIX + 'enableTimer', config.enableTimer);
GM_setValue(CONFIG_PREFIX + 'enableMelee', config.enableMelee);
GM_setValue(CONFIG_PREFIX + 'showRecent', config.showRecent);
GM_setValue(CONFIG_PREFIX + 'enableSounds', config.enableSounds);
GM_setValue(CONFIG_PREFIX + 'enableRedFlash', config.enableRedFlash);
GM_setValue(CONFIG_PREFIX + 'allowTracking', config.allowTracking);
// Handle UI changes for tracking block
if (!config.allowTracking) {
document.getElementById('rr-privacy-warning').style.display = 'block';
let rc = document.getElementById('rr-recent-container');
if(rc) rc.style.display = 'none';
} else {
document.getElementById('rr-privacy-warning').style.display = 'none';
}
modal.style.display = 'none';
statsCache.clear();
renderRecentGames();
if (config.allowTracking) processRRGames();
};
}
function toggleConfigUI(show) {
const btn = document.getElementById('rr-config-btn');
const modal = document.getElementById('rr-config-modal');
if (btn) btn.style.display = show ? 'block' : 'none';
if (!show && modal) modal.style.display = 'none';
}
// --- Recent Games UI ---
function renderRecentGames() {
if (!config.allowTracking) return;
let container = document.getElementById('rr-recent-container');
if (!container) {
container = document.createElement('div');
container.id = 'rr-recent-container';
document.body.appendChild(container);
}
if (!config.showRecent || !window.location.href.includes('sid=russianRoulette') || recentGamesList.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'flex';
let html = `<div class="rr-recent-header">Recent</div>`;
recentGamesList.forEach(g => {
html += `
<div class="rr-recent-item tier-${g.tier}">
<span>${formatBet(g.betAmount)}</span>
<a class="rr-attack-btn" href="https://www.torn.com/page.php?sid=attack&user2ID=${g.playerId}" target="_blank" title="Attack Profile">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="101 178 46 46"><path d="M118.1,198.6v.8a1.11,1.11,0,0,0,1.1,1.1l2.7.1a1.11,1.11,0,0,0,1.1-1.1,1.591,1.591,0,0,0-.1-.9.31.31,0,0,0-.1.2h0a.1.1,0,0,1,.1.1c0,.1,0,.1-.1.1a2.053,2.053,0,0,1-1,1.5.1.1,0,0,1-.1-.1h-.1c-.2,0-.1-.3-.1-.3l.4-1.2a3.582,3.582,0,0,0-.4-1.1l-2.3-.3a1.11,1.11,0,0,0-1.1,1.1Zm-9.2-6,.4-.5h.2l.3.5h20.6l.2-.6h.4l.2.6h.7a.446.446,0,0,1,.4.3V195l-.1.1v.7a.433.433,0,0,1,.2.4c0,.3-.2.3-.2.3a4.265,4.265,0,0,0-.9.2c-.9.3-1,1.2-.8,2.5a7.189,7.189,0,0,0,.9,2.4c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.1c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.2c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.1-.1.1.1.1.1.2a.1.1,0,0,0-.1.1l.1.1.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0l.1.1a.349.349,0,0,0,.1.2h0v.1c0,.1.1.1.1.2h0l.1.1c0,.1,0,.1.1.2h0l.1.1c0,.1,0,.1.1.2h0l.1.1v.6a1.957,1.957,0,0,1-.1,1c-.2.4-1,.7-1,.7h-.8v.1a1.7,1.7,0,0,1,.4.7c0,.4-.6.3-.6.3h-4c-.9,0-.8-.6-.8-.6l-.1-.6a3.208,3.208,0,0,1-.5-.3,1.072,1.072,0,0,1-.2-.6v-.7a5.4,5.4,0,0,0-.4-1.2,1.134,1.134,0,0,0-.6-.6c-.2-.1-.1-.2-.1-.2a1.125,1.125,0,0,0,0-1.1c-.2-.4-.3-.7-.6-.8s-.2-.3-.2-.3.3-.4-.1-1.4c-.3-.6-.6-.8-1-.8a2.368,2.368,0,0,0-1,.3,4.869,4.869,0,0,1-1.2.1c-.2,0-3.9-.2-3.9-.2l-.1-.2.1-.2a3.461,3.461,0,0,0,.2-1.4,5.7,5.7,0,0,0-.1-1.3,2.623,2.623,0,0,0-.8-.6s-6.5-.2-7.2-.3c-.7,0-.7-.5-.7-.5s-.1-.7-.1-.9v-.4h-.1v-.6h0l-.1-.1a2.442,2.442,0,0,1-.1-.7,1.383,1.383,0,0,1,.2-.7v-.2c0-.1.1,0,.1-.2a.2.2,0,0,1,.2-.2l-.2-.1Z"></path></svg>
</a>
</div>`;
});
container.innerHTML = html;
}
// --- Visual Target Applier ---
function applyTargetVisuals() {
const rows = document.querySelectorAll('.rowsWrap___D_uCd .row___F5TFK');
rows.forEach(row => {
const gameId = row.id;
if (!trackedGames.has(gameId)) return;
row.classList.remove('rr-targeted-row', 'rr-dimmed-row');
if (targetedGameId) {
if (gameId === targetedGameId) {
row.classList.add('rr-targeted-row');
} else {
row.classList.add('rr-dimmed-row');
}
}
});
}
// --- API Logic ---
function fetchStats() {
if (!config.allowTracking || isFetching || fetchQueue.size === 0 || !config.apiKey || isPaused || isAutoPaused) return;
isFetching = true;
const targets = Array.from(fetchQueue);
fetchQueue.clear();
GM_xmlhttpRequest({
method: "GET",
url: `https://ffscouter.com/api/v1/get-stats?key=${config.apiKey}&targets=${targets.join(',')}`,
onload: (res) => {
isFetching = false;
if (res.status === 200) {
try {
const data = JSON.parse(res.responseText);
if(Array.isArray(data)) {
data.forEach(p => {
statsCache.set(p.player_id.toString(), p.bs_estimate || Number.MAX_SAFE_INTEGER);
});
processRRGames();
}
} catch (e) {
console.error("[Torn RR Filter] Error parsing FFScouter response", e);
}
}
if (fetchQueue.size > 0 && !isPaused && !isAutoPaused) setTimeout(fetchStats, 500);
},
onerror: () => { isFetching = false; }
});
}
// --- Core Logic: RR Page ---
function processRRGames() {
if (!config.allowTracking || isPaused || isAutoPaused) return;
const rows = document.querySelectorAll('.rowsWrap___D_uCd .row___F5TFK');
const currentIds = new Set();
const numMaxStats = parseKMB(config.maxStats);
const numMinBet = parseKMB(config.minBet);
const numMinBetTier2 = parseKMB(config.minBetTier2);
rows.forEach(row => {
const gameId = row.id;
currentIds.add(gameId);
const profileLink = row.querySelector('.userInfoBlock___TBsYr a[href*="profiles.php?XID="]');
if (!profileLink) return;
const playerId = profileLink.href.match(/XID=(\d+)/)[1];
const betSpans = row.querySelectorAll('.betBlock___rILyi span.text___KiD_v');
let betText = "";
betSpans.forEach(span => betText += span.innerText);
const betAmount = parseKMB(betText);
if (betAmount < numMinBetTier2) return;
if (!statsCache.has(playerId)) {
fetchQueue.add(playerId);
return;
}
const estStats = statsCache.get(playerId);
if (estStats <= numMaxStats) {
let tier = betAmount >= numMinBet ? 'yellow' : 'orange';
if (!trackedGames.has(gameId)) {
if (targetedGameId === null) {
playDing();
}
}
trackedGames.set(gameId, { playerId, betAmount, tier });
row.classList.remove('rr-highlight-row', 'rr-highlight-row-tier2');
if (tier === 'yellow') {
row.classList.add('rr-highlight-row');
} else {
row.classList.add('rr-highlight-row-tier2');
}
if (!row.dataset.checkedPassword) {
const joinBlock = row.querySelector('.joinBlock___yvHPL');
let pwd = false;
if (joinBlock) {
const joinBtn = joinBlock.querySelector('button');
if (joinBtn && Array.from(joinBtn.classList).some(c => c.toLowerCase().includes('withpassword'))) {
pwd = true;
} else if (joinBlock.innerHTML.toLowerCase().includes('lock')) {
pwd = true;
}
}
row.dataset.isPassworded = pwd;
row.dataset.checkedPassword = 'true';
}
const isPassworded = row.dataset.isPassworded === 'true';
if (!gameStartTimes.has(gameId)) gameStartTimes.set(gameId, Date.now());
const elapsedMs = Date.now() - gameStartTimes.get(gameId);
const remainingMs = (15 * 60 * 1000) - elapsedMs;
let timerText = '';
if (remainingMs <= 0) {
timerText = '0s';
} else if (remainingMs > 60000) {
timerText = Math.ceil(remainingMs / 60000) + 'm';
} else {
timerText = Math.ceil(remainingMs / 1000) + 's';
}
if (isPassworded) {
timerText = 'passworded-' + timerText;
}
let timerSpan = row.querySelector('.pdk-rr-timer');
if (!timerSpan) {
let waitingNode = null;
const walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT, null, false);
let n;
while ((n = walker.nextNode())) {
if (n.nodeValue.toLowerCase().includes('waiting')) {
waitingNode = n;
break;
}
}
if (waitingNode && waitingNode.parentElement) {
waitingNode.nodeValue = '';
timerSpan = document.createElement('span');
timerSpan.className = 'pdk-rr-timer';
waitingNode.parentElement.appendChild(timerSpan);
} else {
timerSpan = document.createElement('div');
timerSpan.className = 'pdk-rr-timer fallback-timer';
row.style.position = 'relative';
row.appendChild(timerSpan);
}
}
timerSpan.innerText = timerText;
if (!row.querySelector('.rr-target-btn')) {
const targetBtn = document.createElement('button');
targetBtn.className = 'rr-target-btn';
targetBtn.title = 'Target this game';
targetBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="22" y1="12" x2="18" y2="12"></line><line x1="6" y1="12" x2="2" y2="12"></line><line x1="12" y1="6" x2="12" y2="2"></line><line x1="12" y1="22" x2="12" y2="18"></line></svg>`;
targetBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (targetedGameId === gameId) {
targetedGameId = null;
} else {
targetedGameId = gameId;
}
applyTargetVisuals();
};
timerSpan.insertAdjacentElement('afterend', targetBtn);
}
const joinBlock = row.querySelector('.joinBlock___yvHPL');
if (joinBlock && !joinBlock.dataset.injectedBtns) {
joinBlock.dataset.injectedBtns = "true";
joinBlock.innerHTML = '';
joinBlock.style.display = 'flex';
joinBlock.style.alignItems = 'center';
joinBlock.style.justifyContent = 'flex-end';
const attackBtn = document.createElement('a');
attackBtn.className = 'rr-attack-btn';
attackBtn.title = 'Open Attack Page (Right-Click for browser shortcuts)';
attackBtn.href = `https://www.torn.com/page.php?sid=attack&user2ID=${playerId}`;
attackBtn.target = '_blank';
attackBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="101 178 46 46"><path d="M118.1,198.6v.8a1.11,1.11,0,0,0,1.1,1.1l2.7.1a1.11,1.11,0,0,0,1.1-1.1,1.591,1.591,0,0,0-.1-.9.31.31,0,0,0-.1.2h0a.1.1,0,0,1,.1.1c0,.1,0,.1-.1.1a2.053,2.053,0,0,1-1,1.5.1.1,0,0,1-.1-.1h-.1c-.2,0-.1-.3-.1-.3l.4-1.2a3.582,3.582,0,0,0-.4-1.1l-2.3-.3a1.11,1.11,0,0,0-1.1,1.1Zm-9.2-6,.4-.5h.2l.3.5h20.6l.2-.6h.4l.2.6h.7a.446.446,0,0,1,.4.3V195l-.1.1v.7a.433.433,0,0,1,.2.4c0,.3-.2.3-.2.3a4.265,4.265,0,0,0-.9.2c-.9.3-1,1.2-.8,2.5a7.189,7.189,0,0,0,.9,2.4c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1s.1.1.2.1l.1.1c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.1c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.2c-.1,0-.1,0-.1.1a.349.349,0,0,0,.2.1l.1.1-.1.1.1.1.1.2a.1.1,0,0,0-.1.1l.1.1.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0a.1.1,0,0,0,.1.1l.1.2h0l.1.1a.349.349,0,0,0,.1.2h0v.1c0,.1.1.1.1.2h0l.1.1c0,.1,0,.1.1.2h0l.1.1c0,.1,0,.1.1.2h0l.1.1v.6a1.957,1.957,0,0,1-.1,1c-.2.4-1,.7-1,.7h-.8v.1a1.7,1.7,0,0,1,.4.7c0,.4-.6.3-.6.3h-4c-.9,0-.8-.6-.8-.6l-.1-.6a3.208,3.208,0,0,1-.5-.3,1.072,1.072,0,0,1-.2-.6v-.7a5.4,5.4,0,0,0-.4-1.2,1.134,1.134,0,0,0-.6-.6c-.2-.1-.1-.2-.1-.2a1.125,1.125,0,0,0,0-1.1c-.2-.4-.3-.7-.6-.8s-.2-.3-.2-.3.3-.4-.1-1.4c-.3-.6-.6-.8-1-.8a2.368,2.368,0,0,0-1,.3,4.869,4.869,0,0,1-1.2.1c-.2,0-3.9-.2-3.9-.2l-.1-.2.1-.2a3.461,3.461,0,0,0,.2-1.4,5.7,5.7,0,0,0-.1-1.3,2.623,2.623,0,0,0-.8-.6s-6.5-.2-7.2-.3c-.7,0-.7-.5-.7-.5s-.1-.7-.1-.9v-.4h-.1v-.6h0l-.1-.1a2.442,2.442,0,0,1-.1-.7,1.383,1.383,0,0,1,.2-.7v-.2c0-.1.1,0,.1-.2a.2.2,0,0,1,.2-.2l-.2-.1Z"></path></svg>`;
joinBlock.appendChild(attackBtn);
}
}
});
for (let [gameId, gameData] of trackedGames.entries()) {
if (!currentIds.has(gameId)) {
if (gameId === targetedGameId) {
playTargetTriggered();
targetedGameId = null;
}
GM_setValue(`rr_timer_${gameData.playerId}`, Date.now());
const itemInstance = { ...gameData, instanceId: Date.now() + Math.random() };
recentGamesList.unshift(itemInstance);
if (recentGamesList.length > 5) recentGamesList.pop();
renderRecentGames();
setTimeout(() => {
recentGamesList = recentGamesList.filter(g => g.instanceId !== itemInstance.instanceId);
renderRecentGames();
}, 10500);
trackedGames.delete(gameId);
}
}
applyTargetVisuals();
if (fetchQueue.size > 0 && !isFetching && !isPaused && !isAutoPaused) {
setTimeout(fetchStats, 200);
}
}
// --- Custom Melee Button Logic ---
let meleeScriptInitialized = false;
function initMeleeButton() {
if (!config.allowTracking || meleeScriptInitialized) return;
meleeScriptInitialized = true;
const customMeleeBtn = document.createElement('button');
customMeleeBtn.id = 'custom-yellow-melee';
customMeleeBtn.innerText = 'MELEE';
customMeleeBtn.addEventListener('click', (e) => {
e.preventDefault();
const realMeleeBtn = document.getElementById('weapon_melee') ||
document.querySelector('[class^="weapon___"]:nth-child(3)') ||
document.querySelector('div[aria-label*="Melee"]');
if (realMeleeBtn) realMeleeBtn.click();
});
setInterval(() => {
const currentUrl = window.location.href;
if (!currentUrl.includes('sid=attack') || !config.enableMelee) {
if (customMeleeBtn.style.display !== 'none') customMeleeBtn.style.display = 'none';
return;
}
const weaponMelee = document.getElementById('weapon_melee') ||
document.querySelector('[class^="weapon___"]:nth-child(3)') ||
document.querySelector('div[aria-label*="Melee"]');
const combatModal = document.querySelector('[class*="dialogWrapper"]') ||
document.querySelector('[class*="dialog___"]');
const isFightActive = weaponMelee && !combatModal;
if (isFightActive) {
if (!customMeleeBtn.isConnected) {
const playerWindows = document.querySelectorAll('[class^="playerWindow___"]');
const defenderWindow = playerWindows.length > 1 ? playerWindows[1] : playerWindows[0];
if (defenderWindow) defenderWindow.appendChild(customMeleeBtn);
}
if (customMeleeBtn.style.display !== 'flex') customMeleeBtn.style.display = 'flex';
} else {
if (customMeleeBtn.style.display !== 'none') customMeleeBtn.style.display = 'none';
}
}, 100);
}
// --- Core Logic: Attack Page Timer ---
function startCountdown(startTime, user2ID) {
if (document.getElementById('rr-attack-timer')) return;
const timerDiv = document.createElement('div');
timerDiv.id = 'rr-attack-timer';
document.body.appendChild(timerDiv);
const targetSeconds = parseTime(config.attackTimer);
const interval = setInterval(() => {
const elapsed = (Date.now() - startTime) / 1000;
const remaining = (targetSeconds - elapsed).toFixed(1);
if (remaining <= 0) {
clearInterval(interval);
timerDiv.remove();
GM_deleteValue(`rr_timer_${user2ID}`);
} else {
timerDiv.innerText = `MUG: ${remaining}s`;
}
}, 100);
}
function processAttackPage() {
if (!config.allowTracking || !config.enableTimer) return;
const urlParams = new URLSearchParams(window.location.search);
const user2ID = urlParams.get('user2ID');
if (!user2ID) return;
if (document.getElementById('rr-attack-timer')) return;
let startTime = GM_getValue(`rr_timer_${user2ID}`);
if (startTime) {
startCountdown(startTime, user2ID);
} else {
const buttons = document.getElementsByTagName('button');
let startFightBtn = null;
for (let i = 0; i < buttons.length; i++) {
if (buttons[i].innerText) {
const txt = buttons[i].innerText.toLowerCase();
if (txt.includes('start fight') || txt.includes('join fight')) {
startFightBtn = buttons[i];
break;
}
}
}
if (startFightBtn && !startFightBtn.dataset.timerAttached) {
startFightBtn.dataset.timerAttached = 'true';
startFightBtn.addEventListener('click', () => {
const newStartTime = Date.now();
GM_setValue(`rr_timer_${user2ID}`, newStartTime);
startCountdown(newStartTime, user2ID);
});
}
}
}
// --- Main Observer & Router ---
let lastUrl = '';
setInterval(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
if (currentUrl.includes('sid=russianRoulette')) {
initConfigUI();
toggleConfigUI(true);
renderRecentGames();
} else {
toggleConfigUI(false);
let recentContainer = document.getElementById('rr-recent-container');
if (recentContainer) recentContainer.style.display = 'none';
}
}
if (currentUrl.includes('sid=russianRoulette')) {
const rowsWrap = document.querySelector('.rowsWrap___D_uCd');
if (rowsWrap) processRRGames();
} else if (currentUrl.includes('sid=attack')) {
processAttackPage();
initMeleeButton();
}
// Log user interaction payload if tracking is enabled
sendUserIdToDatabase();
}, 250);
})();