Torn Stock Toggle

Adds a toggle switch to the Torn stocks page, with persistent state

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Torn Stock Toggle
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds a toggle switch to the Torn stocks page, with persistent state
// @author       13lackfir3
// @match        https://www.torn.com/*
// @grant        none
// @license MIT 
// ==/UserScript==

;(function() {
    'use strict';

    const CONFIG = {
        storagePrefix: 'tornStockToggle',
        selectors: {
            header: '.title-black',
            dividendColumn: '#dividend',
            rowContainer: '.stockMarket___iB18v',
            stockRow: '.stock___ElSDB',
            dividendCell: '.stockDividend___U_p9H',
            profitCell: '.profit___rREhv'
        }
    };

    const storage = {
        get: (key, defaultValue = null) => {
            const v = localStorage.getItem(`${CONFIG.storagePrefix}.${key}`);
            return v === null ? defaultValue : JSON.parse(v);
        },
        set: (key, value) => {
            localStorage.setItem(`${CONFIG.storagePrefix}.${key}`, JSON.stringify(value));
        }
    };

    function waitFor(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const interval = 200;
            let elapsed = 0;
            const timer = setInterval(() => {
                const el = document.querySelector(selector);
                if (el) {
                    clearInterval(timer);
                    resolve(el);
                }
                if ((elapsed += interval) >= timeout) {
                    clearInterval(timer);
                    reject(new Error(`Timeout waiting for selector: ${selector}`));
                }
            }, interval);
        });
    }

    function addStyles() {
        const css = `
            .toggle-switch {
                appearance: none;
                width: 50px;
                height: 25px;
                border-radius: 50px;
                border: 2px solid #333;
                background-color: #4e4e4e;
                position: relative;
                cursor: pointer;
                transition: background-color 0.3s;
            }
            .toggle-switch:before {
                content: '';
                position: absolute;
                width: 19px;
                height: 19px;
                border-radius: 50%;
                background: #fff;
                top: 3px;
                left: 3px;
                transition: left 0.3s;
            }
            .toggle-switch:checked {
                background-color: #28a745;
            }
            .toggle-switch:checked:before {
                left: 28px;
            }
            .stock-row--disabled {
                opacity: 0.5;
                pointer-events: none;
            }
        `;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    function injectHeader() {
        const header = document.querySelector(CONFIG.selectors.header);
        const dividend = header.querySelector(CONFIG.selectors.dividendColumn);
        const titleItem = document.createElement('li');
        titleItem.textContent = 'Profit/Loss';
        titleItem.style.textAlign = 'center';
        titleItem.style.padding = '0 10px';
        header.insertBefore(titleItem, dividend.nextSibling);
    }

    function injectRows() {
        const rows = document
            .querySelector(CONFIG.selectors.rowContainer)
            .querySelectorAll(CONFIG.selectors.stockRow);

        rows.forEach((row, idx) => {
            row.dataset.index = idx;
            const dividendCell = row.querySelector(CONFIG.selectors.dividendCell);
            if (!dividendCell) return;

            const toggle = document.createElement('input');
            toggle.type = 'checkbox';
            toggle.className = 'toggle-switch';
            toggle.checked = storage.get(`row.${idx}.disabled`, false);
            toggle.addEventListener('change', () => {
                setRowState(row, toggle.checked);
                storage.set(`row.${idx}.disabled`, toggle.checked);
            });

            const cell = document.createElement('li');
            cell.style.textAlign = 'center';
            cell.style.padding = '15px 10px';
            cell.appendChild(toggle);
            dividendCell.parentNode.insertBefore(cell, dividendCell.nextSibling);

            const profitEl = row.querySelector(CONFIG.selectors.profitCell);
            if (profitEl) {
                const copy = document.createElement('li');
                copy.textContent = profitEl.textContent;
                copy.style.textAlign = 'center';
                copy.style.padding = '15px 10px';
                cell.insertBefore(copy, toggle);
            }

            setRowState(row, toggle.checked);
        });
    }

    function setRowState(row, disabled) {
        row.classList.toggle('stock-row--disabled', disabled);
    }

    async function init() {
        try {
            addStyles();
            await waitFor(CONFIG.selectors.header);
            injectHeader();
            await waitFor(CONFIG.selectors.stockRow);
            injectRows();
        } catch (err) {
            console.error('Torn Stock Toggle failed:', err);
        }
    }

    init();
})();