Twitch Auto Click Channel Points Chest and Statistics

Automatically click the Twitch channel points chest, monitor all point increases, and reset the accumulated total when switching channels.

התקן את הסקריפט?
סקריפטים מומלצים של יוצר זה

אולי תאהב גם את Twitch Screenshot Helper.

התקן את הסקריפט
// ==UserScript==
// @name         Twitch Auto Click Channel Points Chest and Statistics
// @name:zh-TW   Twitch 自動點擊忠誠點數寶箱和統計
// @name:zh-CN   Twitch 自动点击忠诚点数宝箱和统计
// @namespace    http://tampermonkey.net/
// @version      2.8
// @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; // 記錄目前網址(用於偵測切台)
    const recentPopups = new Set(); // 避免重複記錄同一筆提示
    let observer = null; // MutationObserver 實例

    // 🔧 建立統計面板元件
    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 findMainBtn() {
        return (
            document.querySelector('button[aria-label*="點數"]') ||
            document.querySelector('button[aria-label*="Points"]') ||
            document.querySelector('button[aria-label*="忠誠"]') ||
            document.querySelector('button[aria-label*="Channel"]')
        );
    }

    // 📌 將統計面板插入畫面
    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) {
            insertPanel();
            panel = document.getElementById('my-loyalty-points-panel');
        }
        if (panel) panel.innerText = `${totalPoints} Point`;
    }

    // 📥 處理每一個提示彈窗(+10 +50 這種)
    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*點?$/); // 擷取 +10 / +50 這種格式
                    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); // 1 秒後移除舊紀錄

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

    // 🧿 初始化 MutationObserver,觀察彈窗出現
    function initObserver() {
        if (observer) observer.disconnect();

        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);
                }
            }
        });

        // 使用動畫幀延遲,確保 document.body 可用
        const startObserving = () => {
            if (!document.body) {
                requestAnimationFrame(startObserving);
                return;
            }
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        };
        startObserving();
    }

    // 📛 判斷是否在 modal 對話框中(避免誤觸)
    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;
            }
        }
    }

    // 🔁 切換頻道時,重置統計資料與監聽器
    function watchUrlChange() {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            totalPoints = 0;
            updatePanel();
            recentPopups.clear();
            initObserver(); // 重新啟用監聽器(確保新頁面也能抓到)
        }
    }

    // ✅ 等待 DOM 完整載入後再執行初始化
    function waitForDOMReady(callback) {
        const check = () => {
            if (document.readyState === 'complete') {
                callback();
            } else {
                requestAnimationFrame(check);
            }
        };
        check();
    }

    // 🧩 主邏輯啟動點
    function main() {
        waitForDOMReady(() => {
            insertPanel();
            updatePanel();
            initObserver();

            // 每 3 秒檢查一次:更新面板、點寶箱、檢查換台
            setInterval(() => {
                updatePanel();
                checkAndClickChest();
                watchUrlChange();
            }, 3000);
        });
    }

    main(); // ✅ 執行腳本
})();