Torn Pickpocketing Helper (Rebuilt from Originals)

A final, working merger based on the core principles of the original working scripts.

目前為 2025-06-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Torn Pickpocketing Helper (Rebuilt from Originals)
// @namespace    torn.pickpocketing.helper.rebuilt.v15
// @version      15.0
// @description  A final, working merger based on the core principles of the original working scripts.
// @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 v15.0] Script loading.");

    // --- Configuration ---
    const SETTINGS = {
        cyclistAlerts: { enabled: true, soundUrl: 'https://audio.jukehost.co.uk/gxd2HB9RibSHhr13OiW6ROCaaRbD8103', highlightColor: '#00ff00' },
        difficultyColors: { enabled: true, showCategoryText: true }
    };

    // --- Data Definitions (from original scripts) ---
    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" } };

    // --- State Management ---
    let isCyclistAlertsEnabled = SETTINGS.cyclistAlerts.enabled;
    let isDifficultyColorsEnabled = SETTINGS.difficultyColors.enabled;
    let wasCyclistVisibleLastRun = false; // Flag to control sound alerts

    /**
     * Master function to apply all styles. Uses DOM scraping for all visual info.
     */
    function applyAllStyling() {
        // --- Safely get user skill from DOM ---
        const skillButton = document.querySelector('button[aria-label^="Skill:"]');
        if (!skillButton) {
            // console.log("[PPHelper v15.0] Skill button not found, waiting for DOM update.");
            return; // Cannot proceed without skill value
        }
        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; }

        // Find all target rows using a stable selector (the commit button)
        const commitButtons = document.querySelectorAll('button[aria-label="Pickpocket, 5 nerve"]');
        let isCyclistVisibleThisRun = false;

        commitButtons.forEach(button => {
            const container = button.closest('div[class*="crimeOptionWrapper"]');
            if (!container) return;

            const titleElement = container.querySelector('div[class*="titleAndProps"] > div');
            if (!titleElement) return;

            const originalText = titleElement.textContent.trim().split(' (')[0];
            
            // Reset styles before applying new ones
            titleElement.textContent = originalText;
            titleElement.style.color = '';
            titleElement.style.fontWeight = '';
            container.style.borderLeft = '';
            container.style.backgroundColor = '';

            // Apply difficulty colors
            if (isDifficultyColorsEnabled) {
                const category = Object.keys(markGroups).find(cat => markGroups[cat].includes(originalText));
                if (category) {
                    titleElement.style.color = categoryColorMap[category];
                    container.style.borderLeft = `3px solid ${activeTierColors[category]}`;
                    if (SETTINGS.difficultyColors.showCategoryText && window.innerWidth > 400) {
                        titleElement.textContent = `${originalText} (${category})`;
                    }
                }
            }

            // Check for Cyclist using its unique icon background-position
            // This is the most reliable visual cue from the original Cyclist script.
            const iconElement = container.querySelector('div[class*="icon___"]');
            if (originalText === 'Cyclist' && iconElement && $(iconElement).css('background-position-y') === '0px') {
                isCyclistVisibleThisRun = true;
                if (isCyclistAlertsEnabled) {
                    container.style.backgroundColor = SETTINGS.cyclistAlerts.highlightColor;
                    titleElement.style.fontWeight = 'bold';
                }
            }
        });

        // Play sound only when the Cyclist first appears
        if (isCyclistAlertsEnabled && isCyclistVisibleThisRun && !wasCyclistVisibleLastRun) {
            playAlertSound();
        }
        wasCyclistVisibleLastRun = isCyclistVisibleThisRun;
    }

    /**
     * The single, reliable trigger. Listens for API data updates.
     */
    function initializeCrimeObserver() {
        interceptFetch("torn.com", "/page.php?sid=crimesData", () => {
            // Wait a moment for the DOM to update after the data is received.
            setTimeout(applyAllStyling, 250);
        });
    }

    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></div>`;
            $(pickpocketingRoot).prepend(controlsContainer);

            const cyclistBtn = $('#cyclist-toggle-btn');
            const colorsBtn = $('#colors-toggle-btn');
            const testSoundBtn = $('#test-sound-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': '' });
            }

            function forceRefresh() {
                const refreshButton = document.querySelector('div[class*="refresh-icon_"]');
                if (refreshButton) refreshButton.click();
            }

            cyclistBtn.on('click', () => { isCyclistAlertsEnabled = !isCyclistAlertsEnabled; updateButtons(); forceRefresh(); });
            colorsBtn.on('click', () => { isDifficultyColorsEnabled = !isDifficultyColorsEnabled; updateButtons(); forceRefresh(); });
            testSoundBtn.on('click', () => playAlertSound());

            updateButtons();
        });
    }

    // --- Utility Functions ---
    function playAlertSound() { const audio = new Audio(SETTINGS.cyclistAlerts.soundUrl); audio.play().catch(error => console.error("[PPHelper v15.0] Audio 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 v15.0] Intercept failed:", error)); } return response; }); }; }

    // --- Script Entry Point ---
    setupInterface();
    initializeCrimeObserver();
    setTimeout(applyAllStyling, 1000); // Initial styling on load

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址