Torn Pickpocket Module

Enhanced pickpocketing with color coding and auto-picker - Framework Module

目前為 2025-09-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Torn Pickpocket Module
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Enhanced pickpocketing with color coding and auto-picker - Framework Module
// @match        https://www.torn.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @license MIT (With Credit)
// ==/UserScript==

(function() {
    'use strict';

    // Use unsafeWindow to match framework
    const globalWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

    console.log('[PICKPOCKET] Module starting...');

    // PICKPOCKETING CONFIG
    const categoryColorMap = {
        "Safe": "#37b24d",
        "Moderately Unsafe": "#74b816",
        "Unsafe": "#f59f00",
        "Risky": "#f76707",
        "Dangerous": "#f03e3e",
        "Very Dangerous": "#7048e8",
    };

    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 activityTypes = ["Walking", "Stumbling", "Loitering", "Listening to music", "Distracted", "Soliciting", "Running", "Jogging"];

    // Settings
    let ppSettings = {
        coloringEnabled: GM_getValue('ppColoringEnabled', true),
        autoPickerEnabled: GM_getValue('ppAutoPickerEnabled', false),
        targetCategories: GM_getValue('ppTargetCategories', ["Safe"]),
        targetActivities: GM_getValue('ppTargetActivities', ["Stumbling"]),
        autoPickDelay: GM_getValue('ppAutoPickDelay', 3000),
        maxNerve: GM_getValue('ppMaxNerve', 5),
        randomDelay: GM_getValue('ppRandomDelay', true),
        maxActions: GM_getValue('ppMaxActions', 50),
        safetyMode: GM_getValue('ppSafetyMode', true)
    };

    // State
    let processedTargets = new Set();
    let autoPickerInterval = null;
    let actionCount = 0;
    let sessionStart = Date.now();

    function saveSettings() {
        Object.keys(ppSettings).forEach(key => {
            GM_setValue(`pp${key.charAt(0).toUpperCase() + key.slice(1)}`, ppSettings[key]);
        });
    }

    function log(msg, type = 'info') {
        if (globalWindow.TornFramework && globalWindow.TornFramework.log) {
            globalWindow.TornFramework.log(msg, type, 'PICKPOCKET');
        } else {
            console.log(`[PICKPOCKET] ${msg}`);
        }
    }

    function addRandomDelay(baseDelay) {
        if (!ppSettings.randomDelay) return baseDelay;
        const variance = baseDelay * 0.3;
        return baseDelay + (Math.random() * variance * 2 - variance);
    }

    function isRateLimited() {
        if (!ppSettings.safetyMode) return false;
        if (actionCount >= ppSettings.maxActions) {
            return true;
        }
        return false;
    }

    function processCurrentTargets() {
        if (!ppSettings.coloringEnabled) return;

        try {
            const targetElements = document.querySelectorAll('.titleAndProps___DdeVu > div');

            targetElements.forEach((titleDiv) => {
                const titleText = titleDiv.textContent.trim().split(' (')[0];
                if (!titleText) return;

                let category = null;
                for (const cat in markGroups) {
                    if (markGroups[cat].includes(titleText)) {
                        category = cat;
                        break;
                    }
                }

                if (category) {
                    titleDiv.style.color = categoryColorMap[category];
                    titleDiv.style.fontWeight = 'bold';
                    titleDiv.style.textShadow = '0 1px 2px rgba(0,0,0,0.5)';

                    if (!titleDiv.textContent.includes(`(${category})`)) {
                        titleDiv.textContent = `${titleText} (${category})`;
                    }
                }
            });
        } catch (error) {
            log(`Target processing failed: ${error.message}`, 'error');
        }
    }

    function getCurrentNerve() {
        try {
            const nerveElement = document.querySelector('.bar-value___NTdce');
            if (nerveElement) {
                const nerveText = nerveElement.textContent;
                const match = nerveText.match(/(\d+)/);
                return match ? parseInt(match[1]) : 0;
            }

            const altNerveElement = document.querySelector('[class*="nerve"] .bar-value, .nerve-bar .bar-value');
            if (altNerveElement) {
                const match = altNerveElement.textContent.match(/(\d+)/);
                return match ? parseInt(match[1]) : 0;
            }

            return 100;
        } catch (error) {
            log(`Nerve reading failed: ${error.message}`, 'error');
            return 0;
        }
    }

    function getCategoryPriority(category) {
        const priorities = {
            "Safe": 1,
            "Moderately Unsafe": 2,
            "Unsafe": 3,
            "Risky": 4,
            "Dangerous": 5,
            "Very Dangerous": 6
        };
        return priorities[category] || 999;
    }

    function findBestTarget() {
        if (isRateLimited()) {
            log('Rate limited - skipping target search', 'warning');
            return null;
        }

        try {
            const crimeOptions = document.querySelectorAll('.crime-option');
            const currentNerve = getCurrentNerve();

            if (currentNerve < 1) {
                log('Insufficient nerve for pickpocketing', 'warning');
                return null;
            }

            const validTargets = [];

            for (const option of crimeOptions) {
                const titleElement = option.querySelector('.titleAndProps___DdeVu > div');
                const activityElement = option.querySelector('.activity___e7mdA');
                const buttonElement = option.querySelector('.commit-button');

                if (!titleElement || !buttonElement) continue;

                const titleText = titleElement.textContent.trim().split(' (')[0];
                const activityText = activityElement ? activityElement.textContent.trim().split('\n')[0] : '';

                const ariaLabel = buttonElement.getAttribute('aria-label') || '';
                const nerveMatch = ariaLabel.match(/(\d+)\s*nerve/i);
                const nerveCost = nerveMatch ? parseInt(nerveMatch[1]) : 5;

                if (currentNerve < nerveCost || nerveCost > ppSettings.maxNerve) continue;
                if (buttonElement.getAttribute('aria-disabled') === 'true') continue;

                let targetCategory = null;
                for (const cat in markGroups) {
                    if (markGroups[cat].includes(titleText)) {
                        targetCategory = cat;
                        break;
                    }
                }

                if (!targetCategory || !ppSettings.targetCategories.includes(targetCategory)) continue;

                if (ppSettings.targetActivities.length > 0) {
                    if (!ppSettings.targetActivities.some(activity => activityText.includes(activity))) continue;
                }

                validTargets.push({
                    element: buttonElement,
                    title: titleText,
                    activity: activityText,
                    category: targetCategory,
                    nerve: nerveCost,
                    priority: getCategoryPriority(targetCategory)
                });
            }

            validTargets.sort((a, b) => {
                if (a.priority !== b.priority) return a.priority - b.priority;
                return a.nerve - b.nerve;
            });

            return validTargets[0] || null;

        } catch (error) {
            log(`Target search failed: ${error.message}`, 'error');
            return null;
        }
    }

    function performAutoPick(isTest = false) {
        try {
            const target = findBestTarget();

            if (!target) {
                const msg = isTest ? 'No valid targets available for test' : 'No targets match current criteria';
                log(msg, 'warning');
                return false;
            }

            if (isTest) {
                log(`Test successful: Would pick ${target.title} (${target.category}) - ${target.activity} [${target.nerve} nerve]`, 'success');
                return true;
            }

            if (ppSettings.safetyMode && isRateLimited()) {
                log('Safety mode: Action blocked due to rate limiting', 'warning');
                return false;
            }

            target.element.click();
            actionCount++;

            log(`Auto-picked: ${target.title} (${target.category}) [${target.nerve} nerve] - Activity: ${target.activity}`, 'success');

            processedTargets.add(target.title + target.activity);
            setTimeout(() => {
                processedTargets.delete(target.title + target.activity);
            }, 30000);

            return true;

        } catch (error) {
            log(`Auto pick failed: ${error.message}`, 'error');
            return false;
        }
    }

    function startAutoPicker() {
        if (autoPickerInterval) return;

        try {
            log('Auto picker starting...', 'success');

            autoPickerInterval = setInterval(() => {
                if (ppSettings.autoPickerEnabled && isOnPickpocketPage()) {
                    if (ppSettings.safetyMode && isRateLimited()) {
                        log('Auto picker paused - rate limit reached', 'warning');
                        return;
                    }
                    performAutoPick();
                }
            }, addRandomDelay(ppSettings.autoPickDelay));

        } catch (error) {
            log(`Auto picker start failed: ${error.message}`, 'error');
        }
    }

    function stopAutoPicker() {
        if (autoPickerInterval) {
            clearInterval(autoPickerInterval);
            autoPickerInterval = null;
            log('Auto picker stopped', 'warning');
        }
    }

    function isOnPickpocketPage() {
        return window.location.href.includes('crimes') &&
               (document.querySelector('.pickpocketing-root') !== null ||
                document.querySelector('[class*="pickpocket"]') !== null ||
                document.querySelector('.crime-option') !== null);
    }

    // Create menu section HTML
    const menuSection = `
        <h4 style="margin: 0 0 12px 0; color: #37b24d;">👤 Pickpocketing</h4>

        <div style="display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; margin-bottom: 12px;">
            <label style="display: flex; align-items: center; cursor: pointer;">
                <input type="checkbox" id="ppColoringEnabled" ${ppSettings.coloringEnabled ? 'checked' : ''}>
                <span style="margin-left: 8px;">Target Color Coding</span>
            </label>
            <div style="font-size: 10px; color: #999;">Visual aid</div>
        </div>

        <div style="display: flex; align-items: center; margin-bottom: 12px; padding: 8px; background: rgba(55,178,77,0.1); border-radius: 6px;">
            <label style="display: flex; align-items: center; cursor: pointer; flex-grow: 1;">
                <input type="checkbox" id="ppAutoPickerEnabled" ${ppSettings.autoPickerEnabled ? 'checked' : ''}>
                <span style="margin-left: 8px; font-weight: bold; color: #37b24d;">🤖 Auto Picker</span>
            </label>
            <div id="ppAutoPickStatus" style="padding: 4px 8px; border-radius: 4px; font-size: 10px; font-weight: bold; background: ${ppSettings.autoPickerEnabled ? '#37b24d' : '#666'}; color: white;">
                ${ppSettings.autoPickerEnabled ? 'ACTIVE' : 'DISABLED'}
            </div>
        </div>

        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px;">
            <div>
                <label style="display: block; margin-bottom: 4px; font-size: 10px;">Base Delay (ms):</label>
                <input type="number" id="ppAutoPickDelay" value="${ppSettings.autoPickDelay}" min="1000" max="30000" step="500" style="width: 100%; padding: 6px; background: #333; color: white; border: 1px solid #555; border-radius: 4px; font-size: 11px;">
            </div>
            <div>
                <label style="display: block; margin-bottom: 4px; font-size: 10px;">Max Nerve Cost:</label>
                <input type="number" id="ppMaxNerve" value="${ppSettings.maxNerve}" min="1" max="50" style="width: 100%; padding: 6px; background: #333; color: white; border: 1px solid #555; border-radius: 4px; font-size: 11px;">
            </div>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 6px; font-weight: bold; font-size: 11px;">🎯 Target Categories:</label>
            <div id="ppCategoryCheckboxes" style="max-height: 120px; overflow-y: auto; border: 1px solid #555; padding: 8px; border-radius: 4px; background: rgba(0,0,0,0.3);">
                ${Object.keys(markGroups).map(category => `
                    <label style="display: block; margin-bottom: 4px; cursor: pointer; font-size: 11px; padding: 2px 4px; border-radius: 3px; transition: background 0.2s;">
                        <input type="checkbox" class="ppCategoryCheck" value="${category}" ${ppSettings.targetCategories.includes(category) ? 'checked' : ''}>
                        <span style="margin-left: 6px; color: ${categoryColorMap[category]}; font-weight: bold;">●</span>
                        <span style="margin-left: 4px;">${category}</span>
                    </label>
                `).join('')}
            </div>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 6px; font-weight: bold; font-size: 11px;">🚶 Target Activities:</label>
            <div id="ppActivityCheckboxes" style="max-height: 100px; overflow-y: auto; border: 1px solid #555; padding: 8px; border-radius: 4px; background: rgba(0,0,0,0.3);">
                ${activityTypes.map(activity => `
                    <label style="display: block; margin-bottom: 3px; cursor: pointer; font-size: 10px; padding: 2px 4px; border-radius: 3px; transition: background 0.2s;">
                        <input type="checkbox" class="ppActivityCheck" value="${activity}" ${ppSettings.targetActivities.includes(activity) ? 'checked' : ''}>
                        <span style="margin-left: 6px;">${activity}</span>
                    </label>
                `).join('')}
            </div>
        </div>

        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px;">
            <div>
                <label style="display: block; margin-bottom: 4px; font-size: 10px;">Max Actions/Hour:</label>
                <input type="number" id="ppMaxActions" value="${ppSettings.maxActions}" min="10" max="200" style="width: 100%; padding: 6px; background: #333; color: white; border: 1px solid #555; border-radius: 4px; font-size: 10px;">
            </div>
            <div style="display: flex; align-items: end;">
                <label style="display: flex; align-items: center; cursor: pointer;">
                    <input type="checkbox" id="ppSafetyMode" ${ppSettings.safetyMode ? 'checked' : ''}>
                    <span style="margin-left: 6px; font-size: 10px;">Safety Mode</span>
                </label>
            </div>
        </div>

        <button id="ppTestPick" style="width: 100%; padding: 8px; background: linear-gradient(45deg, #37b24d, #51cf66); border: none; color: white; border-radius: 6px; cursor: pointer; font-size: 11px; font-weight: bold; box-shadow: 0 2px 8px rgba(55,178,77,0.3); transition: all 0.2s;">🧪 Test Auto Pick</button>
    `;

    function setupEventHandlers() {
        // Coloring toggle
        const coloringToggle = document.getElementById('ppColoringEnabled');
        if (coloringToggle) {
            coloringToggle.onchange = function() {
                ppSettings.coloringEnabled = this.checked;
                saveSettings();
                setTimeout(processCurrentTargets, 100);
                log(`Color coding ${this.checked ? 'enabled' : 'disabled'}`, 'info');
            };
        }

        // Auto picker toggle
        const autoPickToggle = document.getElementById('ppAutoPickerEnabled');
        if (autoPickToggle) {
            autoPickToggle.onchange = function() {
                ppSettings.autoPickerEnabled = this.checked;

                const status = document.getElementById('ppAutoPickStatus');
                if (status) {
                    status.textContent = ppSettings.autoPickerEnabled ? 'ACTIVE' : 'DISABLED';
                    status.style.background = ppSettings.autoPickerEnabled ? '#37b24d' : '#666';
                }

                if (ppSettings.autoPickerEnabled && isOnPickpocketPage() && !isRateLimited()) {
                    startAutoPicker();
                } else {
                    stopAutoPicker();
                }

                saveSettings();
                log(`Auto picker ${this.checked ? 'enabled' : 'disabled'}`, 'info');
            };
        }

        // Number inputs
        ['ppAutoPickDelay', 'ppMaxNerve', 'ppMaxActions'].forEach(id => {
            const input = document.getElementById(id);
            if (input) {
                input.onchange = function() {
                    const setting = id.replace('pp', '').charAt(0).toLowerCase() + id.replace('pp', '').slice(1);
                    ppSettings[setting] = parseInt(this.value);
                    saveSettings();

                    // Restart auto picker if running and delay changed
                    if (id === 'ppAutoPickDelay' && autoPickerInterval) {
                        stopAutoPicker();
                        startAutoPicker();
                    }
                };
            }
        });

        // Safety mode toggle
        const safetyToggle = document.getElementById('ppSafetyMode');
        if (safetyToggle) {
            safetyToggle.onchange = function() {
                ppSettings.safetyMode = this.checked;
                saveSettings();
                log(`Safety mode ${this.checked ? 'enabled' : 'disabled'}`, 'info');
            };
        }

        // Category checkboxes
        document.querySelectorAll('.ppCategoryCheck').forEach(checkbox => {
            checkbox.onchange = () => {
                ppSettings.targetCategories = Array.from(document.querySelectorAll('.ppCategoryCheck:checked')).map(cb => cb.value);
                saveSettings();
                log(`Target categories updated: ${ppSettings.targetCategories.join(', ')}`, 'info');
            };
        });

        // Activity checkboxes
        document.querySelectorAll('.ppActivityCheck').forEach(checkbox => {
            checkbox.onchange = () => {
                ppSettings.targetActivities = Array.from(document.querySelectorAll('.ppActivityCheck:checked')).map(cb => cb.value);
                saveSettings();
                log(`Target activities updated: ${ppSettings.targetActivities.join(', ')}`, 'info');
            };
        });

        // Test button
        const testBtn = document.getElementById('ppTestPick');
        if (testBtn) {
            testBtn.onclick = () => {
                log('Testing auto pick functionality...', 'warning');
                const result = performAutoPick(true);
                if (!result) {
                    log('Test failed - check your target settings or visit crimes page', 'error');
                }
            };
        }
    }

    // Module configuration
    const moduleConfig = {
        name: 'Pickpocket',
        version: '1.1',
        description: 'Enhanced pickpocketing with color coding and auto-picker',
        menuSection: menuSection,
        initialize: function() {
            log('Pickpocket module initializing...', 'info');

            setupEventHandlers();

            // Start observer for target processing
            const observer = new MutationObserver(() => {
                if (ppSettings.coloringEnabled) {
                    clearTimeout(observer.debounceTimer);
                    observer.debounceTimer = setTimeout(processCurrentTargets, 300);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });

            // Initial target processing
            setTimeout(processCurrentTargets, 1000);

            // Auto-start if enabled and on pickpocket page
            if (ppSettings.autoPickerEnabled && isOnPickpocketPage()) {
                setTimeout(startAutoPicker, 2000);
                log('Auto picker will start in 2 seconds (on pickpocket page)', 'info');
            }

            log('Pickpocket module initialized successfully', 'success');
        },
        cleanup: function() {
            stopAutoPicker();
            log('Pickpocket module cleaned up', 'info');
        },
        isActive: function() {
            return ppSettings.autoPickerEnabled && autoPickerInterval !== null && isOnPickpocketPage();
        }
    };

    function initializePickpocketModule() {
        log('Attempting to register pickpocket module with framework...', 'info');

        // Register with framework
        if (globalWindow.TornFramework.registerModule(moduleConfig)) {
            log('Pickpocket module registered successfully', 'success');

            // Page change detection
            let currentUrl = window.location.href;
            const urlObserver = new MutationObserver(() => {
                if (window.location.href !== currentUrl) {
                    currentUrl = window.location.href;

                    if (!isOnPickpocketPage() && autoPickerInterval) {
                        stopAutoPicker();
                        log('Left pickpocket page - auto picker stopped', 'info');
                    } else if (isOnPickpocketPage() && ppSettings.autoPickerEnabled && !autoPickerInterval) {
                        setTimeout(startAutoPicker, 1000);
                        log('Entered pickpocket page - auto picker starting', 'info');
                    }
                }
            });

            urlObserver.observe(document.body, { childList: true, subtree: true });
        } else {
            log('Failed to register pickpocket module', 'error');
        }
    }

    // Wait for framework
    function waitForFramework() {
        console.log('[PICKPOCKET] Checking for framework...');
        if (globalWindow.TornFramework && globalWindow.TornFramework.initialized) {
            console.log('[PICKPOCKET] Framework found, initializing module');
            initializePickpocketModule();
        } else {
            console.log('[PICKPOCKET] Framework not ready, waiting...');
            setTimeout(waitForFramework, 500);
        }
    }

    // Start initialization
    waitForFramework();

})();