Torn Keno Hot Numbers

Tracks Keno number frequency during a session and displays the top 10 hottest numbers. Waits for dynamic content.

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

// ==UserScript==
// @name         Torn Keno Hot Numbers
// @namespace    torn.keno.hotnumbers.tracker
// @version      2.4
// @description  Tracks Keno number frequency during a session and displays the top 10 hottest numbers. Waits for dynamic content.
// @author       Torn City Userscript Development Assistant
// @match        https://www.torn.com/page.php?sid=keno
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIGURATION & STATE ---
    const KENO_DATA_URL_FRAGMENT = 'page.php?rfcv=';
    let numberFrequency = {};


    // --- CORE LOGIC ---

    function addCustomStyles() {
        const style = document.createElement('style');
        style.innerHTML = `
            #keno-hot-numbers-container {
                background-color: #f2f2f2;
                border-radius: 5px;
                margin-bottom: 10px;
                border: 1px solid #ccc;
            }
            .keno-hot-numbers-header {
                padding: 10px;
                background-color: #e8e8e8;
                border-top-left-radius: 5px;
                border-top-right-radius: 5px;
                font-weight: bold;
                color: #333;
                border-bottom: 1px solid #ddd;
            }
            #keno-hot-numbers-body {
                padding: 10px;
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                justify-content: center;
                min-height: 40px;
                align-items: center;
                color: #666;
            }
            .hot-number-pill {
                background-color: #fff;
                border: 1px solid #ccc;
                border-radius: 20px;
                padding: 5px 10px;
                font-size: 14px;
                display: flex;
                align-items: center;
                gap: 5px;
                box-shadow: 0 1px 1px rgba(0,0,0,0.05);
            }
            .hot-number-pill strong {
                color: #000;
            }
            .hot-number-pill span {
                font-size: 12px;
                color: #555;
            }
        `;
        document.head.appendChild(style);
    }

    function createHotNumbersUI() {
        const kenoBoard = document.getElementById('boardContainer');
        // This check is now a failsafe, as the observer ensures it exists
        if (!kenoBoard) {
            console.error("Keno Hot Numbers: UI creation called but board still not found. This should not happen.");
            return;
        }
        // Avoid creating a duplicate if the script somehow runs twice
        if (document.getElementById('keno-hot-numbers-container')) return;

        const container = document.createElement('div');
        container.id = 'keno-hot-numbers-container';

        container.innerHTML = `
            <div class="keno-hot-numbers-header">Hot Numbers (Session)</div>
            <div id="keno-hot-numbers-body">
                Play a round to start tracking numbers...
            </div>
        `;

        kenoBoard.parentNode.insertBefore(container, kenoBoard);
    }

    function updateHotNumbersDisplay(drawnNumbers) {
        drawnNumbers.forEach(num => {
            numberFrequency[num] = (numberFrequency[num] || 0) + 1;
        });

        const sortedNumbers = Object.entries(numberFrequency)
            .sort(([, aCount], [, bCount]) => bCount - aCount)
            .slice(0, 10);

        const displayBody = document.getElementById('keno-hot-numbers-body');
        if (!displayBody) return;

        if (sortedNumbers.length === 0) {
            displayBody.innerHTML = 'Waiting for results...';
        } else {
            displayBody.innerHTML = sortedNumbers.map(([number, count]) => `
                <div class="hot-number-pill">
                    <strong>${number}</strong>
                    <span>(x${count})</span>
                </div>
            `).join('');
        }
    }

    function interceptFetch() {
        const originalFetch = window.fetch;

        window.fetch = async function(...args) {
            const response = await originalFetch(...args);
            const url = args[0];

            if (typeof url === 'string' && url.includes(KENO_DATA_URL_FRAGMENT)) {
                response.clone().json().then(data => {
                    if (data && data.randomNumbers && Array.isArray(data.randomNumbers)) {
                        updateHotNumbersDisplay(data.randomNumbers);
                    }
                }).catch(error => {
                    console.error("Keno Hot Numbers: Error parsing game data.", error);
                });
            }
            return response;
        };
    }

    // --- INITIALIZATION LOGIC ---

    /**
     * This function contains the actual script logic that runs
     * ONLY after we confirm the Keno board exists.
     */
    function initializeScript() {
        console.log("Keno Hot Numbers: Keno board found. Initializing script.");
        addCustomStyles();
        createHotNumbersUI();
        interceptFetch();
    }

    /**
     * Watches the DOM for changes and waits for the '#boardContainer'
     * element to be added to the page.
     */
    function waitForKenoBoard() {
        // Find the main content area to observe. '#keno-wrap' is a good candidate.
        const targetNode = document.getElementById('content-wrapper');
        if (!targetNode) {
            // If even the wrapper isn't there, wait for it first.
            window.addEventListener('load', waitForKenoBoard, { once: true });
            return;
        }

        const observer = new MutationObserver((mutationsList, obs) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    // Check if the board now exists
                    const kenoBoard = document.getElementById('boardContainer');
                    if (kenoBoard) {
                        initializeScript(); // Found it, run the main logic
                        obs.disconnect(); // Stop observing, our job is done
                        return;
                    }
                }
            }
        });

        // Start observing the target node for child additions/removals
        observer.observe(targetNode, { childList: true, subtree: true });

        // As a fallback, if the element is already there when the observer starts,
        // run the script immediately.
        if (document.getElementById('boardContainer')) {
            initializeScript();
            observer.disconnect();
        }
    }

    // --- SCRIPT ENTRY POINT ---
    waitForKenoBoard();

})();

QingJ © 2025

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