Twitch 自动点击忠诚点数宝箱和统计

自动点击 Twitch 忠诚点数宝箱,并监控所有点数增加,切换直播间累积归零

目前为 2025-04-29 提交的版本。查看 最新版本

// ==UserScript==
// @name         Twitch Auto Click Channel Points Chest and Statistics
// @name:zh-TW   Twitch 自動點擊忠誠點數寶箱和統計
// @name:zh-CN   Twitch 自动点击忠诚点数宝箱和统计
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  Automatically click the Twitch channel points chest, monitor all point increases, and reset the accumulated total when switching channels.
// @description:zh-TW 自動點擊 Twitch 忠誠點數寶箱,並監控所有點數增加,切換直播間累積歸零
// @description:zh-CN 自动点击 Twitch 忠诚点数宝箱,并监控所有点数增加,切换直播间累积归零
// @author       chatgpt
// @match        https://www.twitch.tv/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let totalPoints = 0;
    let lastUrl = location.href;
    let lastCheckTime = 0;
    let isProcessing = false;
    const recentPopups = new Set();
    const DEBOUNCE_DELAY = 500;
    const CHECK_INTERVAL = 3000; // 檢查寶箱的固定間隔為 3 秒
    const PANEL_UPDATE_INTERVAL = 2000; // 面板更新間隔縮短為 2 秒,確保面板優先顯示

    // 優先處理面板顯示
    function findMainBtn() {
        for (let i = 0; i < 3; i++) { // 最多嘗試3次
            const btn = (
                document.querySelector('button[aria-label*="點數"]') ||
                document.querySelector('button[aria-label*="Points"]') ||
                document.querySelector('button[aria-label*="忠誠"]') ||
                document.querySelector('button[aria-label*="Channel"]')
            );
            if (btn) return btn;
        }
        return null;
    }

    function createPanel() {
        const panel = document.createElement('span');
        panel.id = 'my-loyalty-points-panel';
        panel.style.cssText = `
            background: #18181b;
            color: #FFD600;
            padding: 2px 6px;
            margin-left: 6px;
            border-radius: 6px;
            font-size: 14px;
            vertical-align: middle;
            display: inline-block;
            z-index: 9999;
        `;
        panel.innerText = `${totalPoints} Point`;
        return panel;
    }

    function insertPanel() {
        const oldPanel = document.getElementById('my-loyalty-points-panel');
        if (oldPanel) oldPanel.remove();

        const mainBtn = findMainBtn();
        if (mainBtn && !mainBtn.querySelector('#my-loyalty-points-panel')) {
            const panel = createPanel();
            mainBtn.appendChild(panel);
            return true;
        }
        return false;
    }

    function updatePanel() {
        let panel = document.getElementById('my-loyalty-points-panel');
        if (!panel) {
            if (!insertPanel()) {
                // 如果插入失敗,設定短時間後重試
                setTimeout(updatePanel, 500);
                return;
            }
            panel = document.getElementById('my-loyalty-points-panel');
        }
        if (panel) {
            panel.innerText = `${totalPoints} Point`;
        }
    }

    // 點數監控相關
    function handlePopupNode(node) {
        if (
            node.classList &&
            node.classList.contains('Layout-sc-1xcs6mc-0') &&
            node.classList.contains('bgzAOg')
        ) {
            for (const child of node.childNodes) {
                if (child.nodeType === Node.TEXT_NODE) {
                    const text = child.textContent.trim();
                    const match = text.match(/^\+(\d+)\s*點?$/);
                    if (match) {
                        const key = text + '_' + Date.now();
                        for (let k of recentPopups) {
                            if (k.startsWith(text)) return;
                        }
                        recentPopups.add(key);
                        setTimeout(() => recentPopups.delete(key), 1000);

                        const add = parseInt(match[1], 10);
                        if (!isNaN(add)) {
                            totalPoints += add;
                            updatePanel();
                        }
                    }
                }
            }
        }
    }

    function observePointPopups() {
        let throttleTimer;
        const observer = new MutationObserver((mutations) => {
            if (throttleTimer) return;

            throttleTimer = setTimeout(() => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (!(node instanceof HTMLElement)) continue;
                        if (
                            node.classList.contains('Layout-sc-1xcs6mc-0') &&
                            node.classList.contains('bgzAOg')
                        ) {
                            handlePopupNode(node);
                        }
                        node.querySelectorAll && node.querySelectorAll('.Layout-sc-1xcs6mc-0.bgzAOg').forEach(handlePopupNode);
                    }
                }
                throttleTimer = null;
            }, 500); // 降低節流時間以確保不錯過點數更新
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: false,
            attributes: false
        });
    }

    // 寶箱點擊相關
    function isInDialog(node) {
        while (node) {
            if (
                (node.getAttribute && node.getAttribute('role') === 'dialog') ||
                (node.classList && node.classList.contains('tw-modal'))
            ) {
                return true;
            }
            node = node.parentElement;
        }
        return false;
    }

    function checkAndClickChest() {
        const now = Date.now();
        if (isProcessing || now - lastCheckTime < DEBOUNCE_DELAY) return;

        isProcessing = true;
        lastCheckTime = now;

        const iconDivs = document.querySelectorAll('.claimable-bonus__icon');
        for (const iconDiv of iconDivs) {
            const btn = iconDiv.closest('button');
            if (
                btn &&
                !btn.disabled &&
                btn.offsetParent !== null &&
                !isInDialog(btn)
            ) {
                btn.click();
                break;
            }
        }

        isProcessing = false;
    }

    function watchUrlChange() {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            totalPoints = 0;
            updatePanel();
            recentPopups.clear();
        }
    }

    function main() {
        // 優先初始化面板
        const initPanel = () => {
            if (!insertPanel()) {
                setTimeout(initPanel, 500);
            }
        };
        initPanel();

        // 初始化點數監控
        observePointPopups();

        // 設定面板更新間隔
        setInterval(() => {
            updatePanel();
        }, PANEL_UPDATE_INTERVAL);

        // 設定寶箱檢查和 URL 變化監控
        setInterval(() => {
            checkAndClickChest();
            watchUrlChange();
        }, CHECK_INTERVAL);
    }

    // 縮短初始啟動時間,優先啟動面板功能
    window.addEventListener('load', () => {
        setTimeout(() => {
            insertPanel();
            observePointPopups();
        }, 1000);

        // 延遲啟動其他功能
        setTimeout(main, 3000);
    });
})();