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.6
// @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;            // 記錄當前頁面 URL
    let lastCheckTime = 0;                  // 上次檢查時間
    let isProcessing = false;               // 處理狀態標記
    const recentPopups = new Set();         // 最近彈出的點數記錄
    const DEBOUNCE_DELAY = 500;            // 防抖延遲時間(毫秒)
    const CHECK_INTERVAL = 3000;           // 檢查寶箱間隔(毫秒)
    const PANEL_UPDATE_INTERVAL = 2000;    // 面板更新間隔(毫秒)

    // 尋找主要的點數按鈕
    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();
                    // 匹配 +數字 後面可能跟著 Point/Points/點數/點 或者什麼都沒有
                    const match = text.match(/^\+(\d+)(?:\s*(?:Points?|點數?)?)?$/i);
                    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() {
        const observer = new MutationObserver((mutations) => {
            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);
                }
            }
        });

        // 設定觀察器配置
        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 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();
                return;
            }
        }
    }

    // 監控 URL 變化,切換頻道時重置計數
    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);
    });
})();