您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A definitive, working merger of Cyclist Ring and Pickpocketing Colors using direct DOM observation.
当前为
// ==UserScript== // @name Torn Pickpocketing Helper (Definitive Build - Fixed v2) // @namespace torn.pickpocketing.helper.rebuilt.v12.2 // @version 12.2 // @description A definitive, working merger of Cyclist Ring and Pickpocketing Colors using direct DOM observation. // @author Microbes & Korbrm (Rebuilt by AI Assistant) // @match https://www.torn.com/loader.php?sid=crimes* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; console.log("[PPHelper v12.2] Script loading."); // --- Configuration --- const SETTINGS = { cyclistAlerts: { enabled: true, soundUrl: 'https://audio.jukehost.co.uk/gxd2HB9RibSHhr13OiW6ROCaaRbD8103', highlightColor: '#00ff00', highlightOpacity: '0.3' }, difficultyColors: { enabled: true, showCategoryText: true }, debug: false // Set to true for debugging }; // --- Data Definitions --- const markGroups = { "Safe": ["Drunk man", "Drunk woman", "Homeless person", "Junkie", "Elderly man", "Elderly woman"], "Moderately Unsafe": ["Classy lady", "Laborer", "Postal worker", "Young man", "Young woman", "Student"], "Unsafe": ["Rich kid", "Sex worker", "Thug"], "Risky": ["Jogger", "Businessman", "Businesswoman", "Gang member", "Mobster"], "Dangerous": ["Cyclist"], "Very Dangerous": ["Police officer"] }; const categoryColorMap = { "Safe": "#37b24d", "Moderately Unsafe": "#74b816", "Unsafe": "#f59f00", "Risky": "#f76707", "Dangerous": "#f03e3e", "Very Dangerous": "#7048e8" }; const skillTiers = { tier1: { "Safe": "#37b24d", "Moderately Unsafe": "#f76707", "Unsafe": "#f03e3e", "Risky": "#f03e3e", "Dangerous": "#f03e3e", "Very Dangerous": "#7048e8" }, tier2: { "Safe": "#37b24d", "Moderately Unsafe": "#37b24d", "Unsafe": "#f76707", "Risky": "#f03e3e", "Dangerous": "#f03e3e", "Very Dangerous": "#7048e8" }, tier3: { "Safe": "#37b24d", "Moderately Unsafe": "#37b24d", "Unsafe": "#37b24d", "Risky": "#f76707", "Dangerous": "#f03e3e", "Very Dangerous": "#7048e8" }, tier4: { "Safe": "#37b24d", "Moderately Unsafe": "#37b24d", "Unsafe": "#37b24d", "Risky": "#37b24d", "Dangerous": "#f76707", "Very Dangerous": "#7048e8" }, tier5: { "Safe": "#37b24d", "Moderately Unsafe": "#37b24d", "Unsafe": "#37b24d", "Risky": "#37b24d", "Dangerous": "#37b24d", "Very Dangerous": "#7048e8" } }; // Create normalized lookup for better matching const normalizedMarkGroups = {}; Object.keys(markGroups).forEach(category => { normalizedMarkGroups[category] = markGroups[category].map(name => normalizeText(name)); }); // --- State Management --- let isCyclistAlertsEnabled = SETTINGS.cyclistAlerts.enabled; let isDifficultyColorsEnabled = SETTINGS.difficultyColors.enabled; let wasCyclistVisibleLastRun = false; let lastCyclistCheckTime = 0; /** * Normalize text for better matching */ function normalizeText(text) { return text.toLowerCase() .trim() .replace(/\s+/g, ' ') // Replace multiple spaces with single space .replace(/[^\w\s]/g, '') // Remove special characters except spaces .trim(); } /** * Extract clean target name from element text */ function extractTargetName(text) { if (!text) return ''; // Remove category text in parentheses let cleanText = text.split(' (')[0].trim(); // Remove any other common suffixes or prefixes cleanText = cleanText.replace(/^\s*[-•]\s*/, ''); // Remove bullet points cleanText = cleanText.replace(/\s*\[.*?\]\s*/, ''); // Remove bracketed text return cleanText.trim(); } /** * Find category for a target name with multiple matching strategies */ function findTargetCategory(targetName) { if (!targetName) return null; const normalizedTarget = normalizeText(targetName); // Strategy 1: Exact normalized match for (const [category, names] of Object.entries(normalizedMarkGroups)) { if (names.includes(normalizedTarget)) { if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Exact match: "${targetName}" -> "${category}"`); } return category; } } // Strategy 2: Partial match (contains) for (const [category, originalNames] of Object.entries(markGroups)) { for (const name of originalNames) { const normalizedName = normalizeText(name); if (normalizedTarget.includes(normalizedName) || normalizedName.includes(normalizedTarget)) { if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Partial match: "${targetName}" contains "${name}" -> "${category}"`); } return category; } } } // Strategy 3: Word-by-word match const targetWords = normalizedTarget.split(' '); for (const [category, originalNames] of Object.entries(markGroups)) { for (const name of originalNames) { const nameWords = normalizeText(name).split(' '); const matchingWords = targetWords.filter(word => nameWords.includes(word)); if (matchingWords.length > 0 && matchingWords.length === nameWords.length) { if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Word match: "${targetName}" matches "${name}" -> "${category}"`); } return category; } } } if (SETTINGS.debug) { console.log(`[PPHelper v12.2] No match found for: "${targetName}" (normalized: "${normalizedTarget}")`); } return null; } /** * Enhanced cyclist detection with multiple fallback methods */ function findCyclistTargets() { const cyclists = []; // Method 1: Direct text search in title elements const titleElements = document.querySelectorAll('div[class*="titleAndProps"] > div'); titleElements.forEach(titleElement => { const text = extractTargetName(titleElement.textContent); if (normalizeText(text) === normalizeText('Cyclist')) { const container = titleElement.closest('div[class*="crimeOptionWrapper"]'); if (container) { cyclists.push({ container, titleElement, text }); } } }); // Method 2: Search all text content for cyclist references if (cyclists.length === 0) { const allContainers = document.querySelectorAll('div[class*="crimeOptionWrapper"]'); allContainers.forEach(container => { const allText = normalizeText(container.textContent); if (allText.includes('cyclist')) { const titleElement = container.querySelector('div[class*="titleAndProps"] > div'); if (titleElement) { cyclists.push({ container, titleElement, text: extractTargetName(titleElement.textContent) }); } } }); } if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Found ${cyclists.length} cyclist targets`); } return cyclists; } /** * Apply styling to a single target container */ function styleTarget(container, titleElement, targetName, category, activeTierColors) { if (!category) return false; // Apply difficulty colors if (isDifficultyColorsEnabled) { const categoryColor = categoryColorMap[category]; const tierColor = activeTierColors[category]; if (categoryColor && tierColor) { titleElement.style.color = categoryColor; container.style.borderLeft = `3px solid ${tierColor}`; // Add category text if enabled and screen is wide enough if (SETTINGS.difficultyColors.showCategoryText && window.innerWidth > 400) { if (!titleElement.textContent.includes(`(${category})`)) { titleElement.textContent = `${targetName} (${category})`; } } if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Styled "${targetName}" as "${category}" with colors ${categoryColor}/${tierColor}`); } return true; } } return false; } /** * Enhanced styling function with better error handling and timing */ function applyAllStyling() { try { const skillButton = document.querySelector('button[aria-label^="Skill:"]'); if (!skillButton) { if (SETTINGS.debug) { console.log("[PPHelper v12.2] No skill button found, retrying..."); } return; } const skillText = skillButton.getAttribute('aria-label'); const currentSkill = parseFloat(skillText.replace('Skill: ', '')); let activeTierColors; if (currentSkill < 10) { activeTierColors = skillTiers.tier1; } else if (currentSkill < 35) { activeTierColors = skillTiers.tier2; } else if (currentSkill < 65) { activeTierColors = skillTiers.tier3; } else if (currentSkill < 80) { activeTierColors = skillTiers.tier4; } else { activeTierColors = skillTiers.tier5; } const commitButtons = document.querySelectorAll('button[aria-label="Pickpocket, 5 nerve"]'); let isCyclistVisibleThisRun = false; let styledCount = 0; let totalTargets = 0; if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Current skill: ${currentSkill}, Found ${commitButtons.length} targets`); } // Process each target commitButtons.forEach((button, index) => { const container = button.closest('div[class*="crimeOptionWrapper"]'); if (!container) return; const titleElement = container.querySelector('div[class*="titleAndProps"] > div'); if (!titleElement) return; totalTargets++; // Reset all styles first container.style.backgroundColor = ''; container.style.borderLeft = ''; container.style.boxShadow = ''; container.classList.remove('cyclist-highlight'); titleElement.style.color = ''; titleElement.style.fontWeight = ''; titleElement.style.textShadow = ''; // Extract and clean the target name const originalText = titleElement.textContent.trim(); const targetName = extractTargetName(originalText); // Reset to clean text titleElement.textContent = targetName; if (SETTINGS.debug && index < 3) { // Only log first 3 for brevity console.log(`[PPHelper v12.2] Processing target ${index + 1}: "${originalText}" -> "${targetName}"`); } // Find category and apply styling const category = findTargetCategory(targetName); if (category) { const styled = styleTarget(container, titleElement, targetName, category, activeTierColors); if (styled) styledCount++; // Check for cyclist if (normalizeText(targetName) === normalizeText('Cyclist')) { isCyclistVisibleThisRun = true; if (isCyclistAlertsEnabled) { // Apply cyclist-specific highlighting container.style.backgroundColor = SETTINGS.cyclistAlerts.highlightColor + SETTINGS.cyclistAlerts.highlightOpacity; container.style.boxShadow = `0 0 15px ${SETTINGS.cyclistAlerts.highlightColor}`; container.style.border = `2px solid ${SETTINGS.cyclistAlerts.highlightColor}`; container.classList.add('cyclist-highlight'); titleElement.style.fontWeight = 'bold'; titleElement.style.textShadow = `0 0 5px ${SETTINGS.cyclistAlerts.highlightColor}`; if (SETTINGS.debug) { console.log("[PPHelper v12.2] Cyclist highlighted successfully"); } } } } else if (SETTINGS.debug) { console.log(`[PPHelper v12.2] No category found for: "${targetName}"`); } }); if (SETTINGS.debug) { console.log(`[PPHelper v12.2] Styled ${styledCount}/${totalTargets} targets. Cyclist visible: ${isCyclistVisibleThisRun}`); } // Sound alert logic with cooldown const currentTime = Date.now(); if (isCyclistAlertsEnabled && isCyclistVisibleThisRun && !wasCyclistVisibleLastRun) { if (currentTime - lastCyclistCheckTime > 2000) { // 2 second cooldown console.log("[PPHelper v12.2] Cyclist has appeared! Playing sound."); playAlertSound(); lastCyclistCheckTime = currentTime; } } wasCyclistVisibleLastRun = isCyclistVisibleThisRun; } catch (error) { console.error("[PPHelper v12.2] Error in applyAllStyling:", error); } } /** * Enhanced observer with multiple trigger methods and better timing */ function initializeCrimeObserver() { // Method 1: Intercept fetch requests interceptFetch("torn.com", "/page.php?sid=crimesData", () => { if (SETTINGS.debug) { console.log("[PPHelper v12.2] Intercepted crimesData, triggering style update."); } // Multiple attempts with increasing delays setTimeout(applyAllStyling, 100); setTimeout(applyAllStyling, 300); setTimeout(applyAllStyling, 600); }); // Method 2: DOM mutation observer for dynamic content const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // Element node if (node.querySelector && ( node.querySelector('div[class*="crimeOptionWrapper"]') || node.querySelector('button[aria-label="Pickpocket, 5 nerve"]') || normalizeText(node.textContent).includes('cyclist') || normalizeText(node.textContent).includes('police') || normalizeText(node.textContent).includes('student') || normalizeText(node.textContent).includes('business') )) { shouldUpdate = true; } } }); } }); if (shouldUpdate) { if (SETTINGS.debug) { console.log("[PPHelper v12.2] DOM mutation detected, updating styles."); } setTimeout(applyAllStyling, 100); setTimeout(applyAllStyling, 300); // Second attempt for good measure } }); // Observe the entire crimes section const crimesSection = document.querySelector('.pickpocketing-root') || document.body; observer.observe(crimesSection, { childList: true, subtree: true, characterData: true }); // Method 3: Periodic check as fallback setInterval(() => { if (document.querySelector('button[aria-label="Pickpocket, 5 nerve"]')) { applyAllStyling(); } }, 10000); // Every 10 seconds } /** * Enhanced interface setup */ function setupInterface() { waitForElementToExist('.pickpocketing-root').then((pickpocketingRoot) => { if (document.getElementById('pp-helper-controls')) return; const controlsContainer = ` <div id="pp-helper-controls" style="margin-bottom: 10px; display: flex; gap: 10px; flex-wrap: wrap;"> <a id="cyclist-toggle-btn" class="torn-btn"></a> <a id="colors-toggle-btn" class="torn-btn"></a> <a id="test-sound-btn" class="torn-btn" style="background: #2196F3; color: white;">Test Sound</a> <a id="debug-toggle-btn" class="torn-btn" style="background: #9C27B0; color: white;">Debug: ${SETTINGS.debug ? 'ON' : 'OFF'}</a> </div> `; $(pickpocketingRoot).prepend(controlsContainer); const cyclistBtn = $('#cyclist-toggle-btn'); const colorsBtn = $('#colors-toggle-btn'); const testSoundBtn = $('#test-sound-btn'); const debugBtn = $('#debug-toggle-btn'); function updateButtons() { cyclistBtn.text(`Cyclist Alerts: ${isCyclistAlertsEnabled ? 'ON' : 'OFF'}`) .css(isCyclistAlertsEnabled ? { 'background': '#4CAF50', 'color': 'white' } : { 'background': '', 'color': '' }); colorsBtn.text(`Difficulty Colors: ${isDifficultyColorsEnabled ? 'ON' : 'OFF'}`) .css(isDifficultyColorsEnabled ? { 'background': '#4CAF50', 'color': 'white' } : { 'background': '', 'color': '' }); debugBtn.text(`Debug: ${SETTINGS.debug ? 'ON' : 'OFF'}`); } function forceRefresh() { const refreshButton = document.querySelector('div[class*="refresh-icon_"]'); if (refreshButton) { refreshButton.click(); } else { // Fallback: trigger styling directly with multiple attempts setTimeout(applyAllStyling, 100); setTimeout(applyAllStyling, 500); setTimeout(applyAllStyling, 1000); } } cyclistBtn.on('click', () => { isCyclistAlertsEnabled = !isCyclistAlertsEnabled; updateButtons(); forceRefresh(); }); colorsBtn.on('click', () => { isDifficultyColorsEnabled = !isDifficultyColorsEnabled; updateButtons(); forceRefresh(); }); testSoundBtn.on('click', () => { console.log("[PPHelper v12.2] Testing sound manually."); playAlertSound(); }); debugBtn.on('click', () => { SETTINGS.debug = !SETTINGS.debug; updateButtons(); console.log(`[PPHelper v12.2] Debug mode ${SETTINGS.debug ? 'enabled' : 'disabled'}`); }); updateButtons(); // Initial styling application with multiple attempts setTimeout(applyAllStyling, 500); setTimeout(applyAllStyling, 1000); setTimeout(applyAllStyling, 2000); }); } // --- Enhanced Utility Functions --- function playAlertSound() { try { const audio = new Audio(SETTINGS.cyclistAlerts.soundUrl); audio.volume = 0.7; audio.play().catch(error => { console.error("[PPHelper v12.2] Audio failed:", error); // Fallback: try to create a simple beep try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.value = 800; gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); oscillator.start(); oscillator.stop(audioContext.currentTime + 0.5); } catch (beepError) { console.error("[PPHelper v12.2] Fallback beep also failed:", beepError); } }); } catch (error) { console.error("[PPHelper v12.2] Sound creation failed:", error); } } function waitForElementToExist(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(() => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { subtree: true, childList: true }); }); } function interceptFetch(url, q, callback) { const originalFetch = window.fetch; window.fetch = function(...args) { return originalFetch.apply(this, args).then(response => { if (response.url.includes(url) && response.url.includes(q)) { response.clone().json().then(json => { callback(json); }).catch(error => { console.error("[PPHelper v12.2] Intercept JSON parsing failed:", error); callback(null); }); } return response; }); }; } // --- Script Entry Point --- console.log("[PPHelper v12.2] Initializing..."); // Wait for page to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setupInterface(); initializeCrimeObserver(); }); } else { setupInterface(); initializeCrimeObserver(); } // Add CSS for cyclist highlighting const style = document.createElement('style'); style.textContent = ` .cyclist-highlight { animation: cyclistPulse 2s infinite; } @keyframes cyclistPulse { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } } `; document.head.appendChild(style); console.log("[PPHelper v12.2] Script fully loaded and initialized."); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址