Greasy Fork is available in English.

YouTube — Auto Switch Live Chat

Automatically switch YouTube live chat to "All Messages" and supports Replay Chat. Optimized for high discoverability and low performance impact.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          YouTube — Auto Switch Live Chat
// @name:zh-CN    YouTube — 自动切换直播聊天室
// @name:zh-TW    YouTube — 自動切換即時聊天室
// @name:ja       YouTube — ライブチャット自動切替
// @name:ko       YouTube — 실시간 채팅 자동 전환
// @name:es       YouTube — Cambio automático de chat en vivo
// @name:fr       YouTube — Commutateur automatique de chat en direct
// @name:de       YouTube — Automatischer Live-Chat-Wechsel
// @name:it       YouTube — Cambio automatico della chat dal vivo
// @name:ru       YouTube — Автоматическое переключение чата
// @name:pt       YouTube — Alternância automática de chat ao vivo
// @name:nl       YouTube — Automatisch Live Chat Schakelen
// @namespace     greasyfork-Auto Switch Live Chat
// @version       1.3.9
// @description    Automatically switch YouTube live chat to "All Messages" and supports Replay Chat. Optimized for high discoverability and low performance impact.
// @description:zh-CN 自动将 YouTube 直播聊天切换到「所有消息」並支援重播聊天室。已優化可發現性與效能。
// @description:zh-TW 自動將 YouTube 即時聊天切換到「所有訊息」並支援重播聊天室。已優化可發現性與效能。
// @description:ja YouTubeライブチャットを自動で「すべてのチャット」に切替、リプレイチャット対応。検出性とパフォーマンスを最適化。
// @description:ko YouTube 실시간 채팅을 자동으로 "모든 메시지"로 전환하며 다시보기 채팅 지원. 감지 및 성능 최적화.
// @description:es Cambia automáticamente el chat en vivo de YouTube a "Todos los mensajes" y soporta Chat de Repetición. Optimizado para alta detección y bajo impacto de rendimiento.
// @description:fr Bascule automatiquement le chat en direct de YouTube sur "Tous les messages" et supporte Chat de Rediffusion. Optimisé pour une haute détectabilité et un faible impact sur les performances.
// @description:de Schaltet den YouTube Live-Chat automatisch auf "Alle Nachrichten" um und unterstützt Replay-Chat. Optimiert für hohe Auffindbarkeit und geringe Leistungsauswirkungen.
// @description:it Passa automaticamente la chat dal vivo di YouTube a "Tutti i messaggi" e supporta Chat di Replay. Ottimizzato per l'alta rilevabilità e il basso impatto sulle prestazioni.
// @description:ru Автоматически переключает чат YouTube на "Все сообщения" и поддерживает чат повтора. Оптимизирован для высокой обнаруживаемости и низкого воздействия на производительность.
// @description:pt Alterna automaticamente o chat ao vivo do YouTube para "Todas as mensagens" e suporta o chat de repetição. Otimizado para alta detecção e baixo impacto no desempenho.
// @description:nl Schakelt YouTube live chat automatisch naar "Alle berichten" en ondersteunt Replay Chat. Geoptimaliseerd voor hoge vindbaarheid en lage prestatie-impact.
// @license        MIT
// @author         凝流
// @match          https://www.youtube.com/*
// @icon           https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant none
// @run-at document-idle
// ==/UserScript==

(function () {
    'use strict';

    // ==================== 配置 / Configuration ====================
    // - 腳本運作的參數配置 (Script behavior parameters)
    const CONFIG = {
        MAX_RETRIES: 25,
        RETRY_INTERVAL: 1000,
        OBSERVER_TIMEOUT: 6000, // 用於所有等待超時 (Used for all waiting timeouts)
        SPA_DELAY: 300, // 僅用於初始掃描 (Only for initial scan)
        DEBUG: true // 設為 true 可查看詳細日誌 (Set to true for detailed logs)
    };

    // ==================== 多語言關鍵字 / Multi-language Keywords ====================
    // - 原始關鍵字 (Raw keywords)
    const _KEYWORDS_RAW = {
        // 點擊開啟選單的按鈕文字 (Text for button to open the menu)
        button: [
            // --- Live Chat Keywords ---
            'top chat', 'top-chat', // English
            '重點', '直播聊天', // Traditional Chinese
            '重点', '直播聊天室', '重要消息', // Simplified Chinese
            'トップチャット', 'チャット', // Japanese
            '주요 채팅', // Korean
            'chat destacado', 'principal', // Spanish
            'Principais mensagens', // Portuguese
            'Интересные сообщения', // Russian
            'Top Chat', // General
            'Messaggi principali', // Italian
            'Top-Chat', 'Top-Chat-Meldungen', // German

            // --- Replay Chat Keywords ---
            'top replay chat', 'top messages replay', // English
            '重播熱門聊天室訊息', // Traditional Chinese
            '重播热门聊天室消息', '重播重要消息', // Simplified Chinese
            '上位のチャットのリプレイ', // Japanese
            '주요 다시보기 채팅', '다시보기 주요 메시지', // Korean
            'chat destacado de repetición', 'mensajes destacados de repetición', // Spanish
            "revoir l'essentiel du chat", // French
            'top-chat-wiedergabe', 'top-chat-nachrichten-wiedergabe', // German
            'chat di replay', 'messaggi principali di replay', // Italian
            'только интересные сообщения', // Russian
            'principais mensagens de reprise', 'chat de reprise', // Portuguese
        ],

        // 選單中「顯示所有訊息」的選項文字 (Menu option text for "All Messages")
        menu: [
            // --- Live Chat Keywords ---
            'all messages', 'live chat', // English
            '所有訊息', '即時聊天', '聊天室', // Traditional Chinese
            '所有消息', '实时聊天', // Simplified Chinese
            'すべてのチャット', 'チャット', // Japanese
            '실시간 채팅', // Korean
            'Chat en directo', 'todos los mensajes', // Spanish
            'Chat em direto', // Portuguese
            'все сообщения', 'Чат', // Russian
            'Chat en direct', // French
            'alle chats', // Dutch
            'Chat live', // General
            'Alle Nachrichten', // German
            'tutti i messaggi', // Italian

            // --- Replay Chat Keywords ---
            'live chat replay', 'all messages replay', // English
            '聊天重播', '你可以查看所有訊息', // Traditional Chinese
            '你可以查看所有消息', // Simplified Chinese
            'チャットのリプレイ', // Japanese
            '실시간 채팅 다시보기', '모든 메시지 다시보기', // Korean
            'chat en directo de repetición', 'todos los mensajes de repetición', // Spanish
            'replay du chat en direct', // French
            'alle nachrichten wiedergabe', 'live-chat-wiedergabe', // German
            'tutti i messaggi di replay', // Italian
            'запись чата', // Russian
            'todas as mensagens de reprise', 'chat ao vivo de reprise', // Portuguese
        ],

        // 排除關鍵字(避免誤點選單中相同的關鍵字) (Exclude keywords to avoid misclicks)
        exclude: [
            // --- Live Chat Exclude ---
            'top chat', // English
            '重點', '重要消息', // Traditional/Simplified Chinese
            'chat destacado', // Spanish
            'лучший чат', 'Интересные сообщения', // Russian
            'トップチャット', // Japanese
            'Top Chat', // General
            'Principais mensagens', // Portuguese
            'Messaggi principali', // Italian
            '주요 채팅', // Korean
            'Top-Chat', 'Top-Chat-Meldungen', // German

            // --- Replay Chat Exclude ---
            'top replay chat', 'top messages replay', // English
            '重播熱門聊天室訊息', // Traditional Chinese
            '重播重要消息', // Simplified Chinese
            '上位のチャットのリプレイ', // Japanese
            '주요 다시보기 채팅', // Korean
            'chat destacado de repetición', // Spanish
            "revoir l'essentiel du chat", // French
            'top-chat-wiedergabe', // German
            'messaggi principali di replay', // Italian
            'только интересные сообщения', // Russian
            'principais mensagens de reprise', // Portuguese
        ]
    };

    // - 正規化工具 (Normalization utility)
    const normalize = (str) => {
        if (!str) return '';
        // 統一處理大小寫、變音符號、各種空白字元、引號和破折號
        return String(str)
            .toLowerCase()
            .normalize('NFKD') // 處理變音符號
            .replace(/[\u0300-\u036f]/g, '') // 移除重音 (例如法語 l'essentiel)
            .replace(/[\s\u00A0\u200B\u2009\u202F\uFEFF]+/g, '') // 移除各類空白字元
            .replace(/[’'`"“”«»\-–—‑]+/g, ''); // 移除常見引號與各類破折號
    };

    // - 預先正規化的關鍵字 (Pre-normalized keywords for performance)
    const KEYWORDS = {
        button_norm: _KEYWORDS_RAW.button.map(normalize),
        menu_norm: _KEYWORDS_RAW.menu.map(normalize),
        exclude_norm: _KEYWORDS_RAW.exclude.map(normalize)
    };

    // ==================== 狀態管理 / State Management ====================

    class StateManager {
        constructor() {
            this.currentNavId = Date.now();
            this.processedFrames = new Map();
            this.processedDocs = new Map();
            this.activeObservers = new Set();
            this.activeTimers = new Set();
        }

        reset() {
            this.activeObservers.forEach(obs => {
                try {
                    obs.disconnect();
                } catch (e) {}
            });
            this.activeObservers.clear();

            this.activeTimers.forEach(timer => {
                try {
                    clearTimeout(timer);
                } catch (e) {}
            });
            this.activeTimers.clear();

            this.processedFrames.clear();
            this.processedDocs.clear();

            this.currentNavId = Date.now();
        }

        addObserver(observer) {
            this.activeObservers.add(observer);
            return observer;
        }

        addTimer(callback, delay) {
            const timer = setTimeout(() => {
                this.activeTimers.delete(timer);
                try {
                    callback();
                } catch (e) {
                    console.error('[YT Chat] Timer callback error:', e);
                }
            }, delay);
            this.activeTimers.add(timer);
            return timer;
        }

        clearTimer(timer) {
            if (this.activeTimers.has(timer)) {
                this.activeTimers.delete(timer);
                clearTimeout(timer);
            }
        }

        isProcessed(element, isFrame = false) {
            if (!element) return true;
            const map = isFrame ? this.processedFrames : this.processedDocs;
            return map.has(element);
        }

        markProcessed(element, isFrame = false) {
            if (!element) return;
            const map = isFrame ? this.processedFrames : this.processedDocs;
            map.set(element, this.currentNavId);
        }

        isValidNavId(navId) {
            return navId === this.currentNavId;
        }
    }

    const state = new StateManager();

    // ==================== 日誌系統 / Logging System ====================
    const LOG_PREFIX = '[YT Chat]';
    const LOG_STYLE = {
        init: 'color: #4CAF50; font-weight: bold',
        success: 'color: #1E88E5',
        warn: 'color: #FF9800',
        error: 'color: #F44336'
    };

    function log(type, msgFn) {
        if (!CONFIG.DEBUG && type !== 'error') return;
        const style = LOG_STYLE[type] || '';
        const message = typeof msgFn === 'function' ? msgFn() : msgFn;
        console.log(`%c${LOG_PREFIX}`, style, message);
    }

    // ==================== 核心函數 / Core Functions ====================

    // - 模擬點擊事件 (Simulate a click event robustly)
    function simulateClick(element) {
        if (!element) return false;

        // 使用元素所在 document 的 window 作為 view
        const view = element.ownerDocument?.defaultView || window;

        // 1. 嘗試原生 click()
        try {
            if (typeof element.click === 'function') {
                element.click();
                return true;
            }
        } catch (e) {
            // Fallback below
        }

        // 2. 嘗試 MouseEvent 模擬
        try {
            const evt = new view.MouseEvent('click', { bubbles: true, cancelable: true });
            return element.dispatchEvent(evt);
        } catch (e) {
            return false;
        }
    }

    // - 等待元素出現 (Wait for an element to appear)
    function waitForElement(doc, selector, timeout, navId) {
        return new Promise(resolve => {
            if (!state.isValidNavId(navId)) {
                resolve(null);
                return;
            }

            const existing = doc.querySelector(selector);
            if (existing) {
                log('init', () => `✓ Found element: ${selector}`);
                resolve(existing);
                return;
            }

            log('init', () => `⏳ Waiting for element: ${selector}...`);

            let resolved = false;

            const observer = new MutationObserver(() => {
                if (resolved) return;

                if (!state.isValidNavId(navId)) {
                    resolved = true;
                    observer.disconnect();
                    log('warn', () => `⚠ Navigation changed while waiting for: ${selector}`);
                    resolve(null);
                    return;
                }

                const el = doc.querySelector(selector);
                if (el) {
                    resolved = true;
                    state.clearTimer(timerId);
                    observer.disconnect();
                    log('init', () => `✓ Found element: ${selector}`);
                    resolve(el);
                }
            });

            const timerId = state.addTimer(() => {
                if (!resolved) {
                    resolved = true;
                    observer.disconnect();
                    log('warn', () => `⚠ Timeout waiting for element: ${selector}`);
                    resolve(null);
                }
            }, timeout);

            // - doc.body 重試機制 (Retry mechanism for doc.body)
            function attemptObserve() {
                if (!state.isValidNavId(navId) || resolved) return;

                if (doc.body) {
                    state.addObserver(observer);
                    observer.observe(doc.body, { childList: true, subtree: true });
                } else {
                    log('warn', '⚠ Document body not yet available, retrying observer attachment...');
                    state.addTimer(attemptObserve, 100); // Short retry
                }
            }

            attemptObserve();
        });
    }

    // - 根據關鍵字點擊按鈕 (Click a button based on keywords)
    function clickByKeywords(container, normalizedKeywords, type) {
        if (!container) {
            log('error', `✗ Container is null for ${type} click.`);
            return false;
        }

        const elements = container.querySelectorAll(
            'button, tp-yt-paper-button, [role="button"]'
        );

        for (const el of elements) {
            const textSources = [
                el.innerText,
                el.textContent,
                el.getAttribute('aria-label'),
                el.title,
                el.dataset.tooltip,
                el.getAttribute('data-tooltip-text')
            ];

            const text = textSources.find(t => t && String(t).trim())?.toString() || '';
            const normalizedText = normalize(text);

            if (normalizedText && normalizedKeywords.some(k => normalizedText.includes(k))) {
                log('init', () => `✓ Clicking ${type} button: "${text.trim()}"`);

                if (simulateClick(el)) {
                    return true;
                } else {
                    log('error', `✗ Failed to click ${type} button via simulation.`);
                    return false;
                }
            }
        }

        log('warn', () => `⚠ ${type} button not found.`);
        return false;
    }

    // - 監聽並點擊選單項目 (Observe and click a menu item)
    // ** v1.3.7 優化:使用 MutationObserver 替換輪詢 **
    function pollMenuItems(container, normalizedKeywords, normalizedExcludeKeywords, timeout, navId) {
        return new Promise(resolve => {
            if (!container) {
                log('error', '✗ Menu container is null.');
                resolve(false);
                return;
            }

            let resolved = false;
            let observer; // 宣告在外部,以便在 checkAndClick 中斷開
            let timerId; // 宣告在外部,以便在 checkAndClick 中清除

            // --- 核心檢查及點擊邏輯 (Core Check and Click Logic) ---
            const checkAndClick = () => {
                if (resolved) return;

                // 1. SPA 健壯性檢查
                if (!state.isValidNavId(navId)) {
                    resolved = true;
                    if (observer) observer.disconnect();
                    state.clearTimer(timerId);
                    log('warn', () => `⚠ Observer aborted: navigation changed.`);
                    resolve(false);
                    return;
                }

                // 2. 搜尋所有可能的選單項目
                const items = container.querySelectorAll(
                    'tp-yt-paper-item, yt-live-chat-menu-item-renderer, [role="menuitem"]'
                );

                for (const item of items) {
                    const textSources = [
                        item.innerText,
                        item.textContent,
                        item.getAttribute('aria-label'),
                        item.title,
                        item.getAttribute('data-tooltip-text')
                    ];
                    const text = textSources.find(t => t && String(t).trim())?.toString() || '';
                    const normalizedText = normalize(text);

                    if (!normalizedText) continue;

                    // 排除檢查 (Exclude check)
                    if (normalizedExcludeKeywords.some(k => normalizedText.includes(k))) {
                        continue;
                    }

                    // 目標檢查 (Target check)
                    if (normalizedKeywords.some(k => normalizedText.includes(k))) {
                        const isSelected =
                            item.hasAttribute('selected') ||
                            item.getAttribute('aria-selected') === 'true' ||
                            item.getAttribute('aria-checked') === 'true' ||
                            item.classList.contains('iron-selected') ||
                            item.classList.contains('selected');

                        if (isSelected) {
                            log('success', () => `✓ Already selected: "${text.trim()}"`);
                        } else {
                            // 點擊目標 (Click target)
                            log('success', () => `✓ Switched to: "${text.trim()}"`);
                            simulateClick(item);
                        }

                        // **清理與解決 (Cleanup and Resolve)**:無論是否已選中,任務都已完成。
                        resolved = true;
                        if (observer) observer.disconnect();
                        state.clearTimer(timerId);
                        resolve(true);
                        return;
                    }
                }
            };
            // --- 核心檢查及點擊邏輯結束 ---

            // 3. 設定超時計時器 (Set timeout timer)
            timerId = state.addTimer(() => {
                if (!resolved) {
                    resolved = true;
                    if (observer) observer.disconnect(); // 超時時停止觀察者
                    log('error', '✗ Menu item not found (Observer Timeout).');
                    resolve(false);
                }
            }, timeout);

            // 4. 設定 MutationObserver (Set MutationObserver)
            observer = state.addObserver(new MutationObserver(checkAndClick));

            // 5. 開始監聽 (Start observing)
            observer.observe(container, { childList: true, subtree: true });

            // 6. 立即執行一次,以防選單項目在監聽開始前已經存在
            checkAndClick();
        });
    }

    // - 執行聊天切換 (Perform the chat switch)
    async function switchChat(doc, navId) {
        if (!state.isValidNavId(navId)) {
            log('warn', () => `⚠ switchChat aborted: navigation changed.`);
            return;
        }

        const isIframe = (doc !== document);

        if (state.isProcessed(doc, false)) {
            log('init', () => isIframe ? '→ iframe already processed.' : '→ Document already processed.');
            return;
        }

        state.markProcessed(doc, false);

        log('init', () => isIframe ?
            `▶ Starting chat switch in iframe.` :
            `▶ Starting chat switch in main page.`);

        const header = await waitForElement(
            doc,
            'yt-live-chat-header-renderer',
            CONFIG.OBSERVER_TIMEOUT,
            navId
        );

        if (!header) {
            log('error', '✗ Chat header not found.');
            return;
        }

        // 點擊開關按鈕 (Click switch button)
        if (!clickByKeywords(header, KEYWORDS.button_norm, 'switch')) {
            log('error', '✗ Could not click switch button.');
            return;
        }

        // 等待選單容器
        const menu = await waitForElement(
            doc,
            '[role="menu"], [role="listbox"]',
            CONFIG.OBSERVER_TIMEOUT,
            navId
        );

        if (!menu) {
            log('error', '✗ Menu failed to appear.');
            return;
        }

        // 監聽並點擊選單項目 (Observe and click menu item)
        await pollMenuItems(
            menu,
            KEYWORDS.menu_norm,
            KEYWORDS.exclude_norm,
            CONFIG.OBSERVER_TIMEOUT, // 使用 OBSERVER_TIMEOUT 作為選單出現的超時時間
            navId
        );
    }

    // - 初始化 iframe (Initialize an iframe)
    function initIframe(iframe, navId) {
        if (!state.isValidNavId(navId)) return;
        if (state.isProcessed(iframe, true)) return;

        state.markProcessed(iframe, true);
        log('init', () => `→ Initializing iframe.`);

        let retries = 0;

        function tryAccess() {
            if (!state.isValidNavId(navId)) {
                log('warn', () => `⚠ tryAccess aborted: navigation changed.`);
                return;
            }

            if (retries++ >= CONFIG.MAX_RETRIES) {
                log('error', () => `✗ Max retries (${CONFIG.MAX_RETRIES}) reached for iframe.`);
                return;
            }

            let doc;
            try {
                doc = iframe.contentDocument || iframe.contentWindow?.document;
            } catch (e) {
                if (e.name === 'SecurityError') {
                    log('error', `✗ iframe blocked by Same-Origin Policy. Script cannot access content.`);
                    // SecurityError is fatal; do not retry.
                    return;
                } else {
                    log('warn', () => `⚠ iframe access error (retry ${retries}/${CONFIG.MAX_RETRIES}): ${e.message}`);
                    state.addTimer(tryAccess, CONFIG.RETRY_INTERVAL);
                }
                return;
            }

            if (!doc || doc.readyState === 'loading') {
                log('warn', () => `⚠ iframe not ready (retry ${retries}/${CONFIG.MAX_RETRIES}).`);
                state.addTimer(tryAccess, CONFIG.RETRY_INTERVAL);
                return;
            }

            switchChat(doc, navId);
        }

        tryAccess();
    }

    // ==================== 啟動腳本 / Script Initialization (修改處) ====================

    // - 處理頁面加載/導航 (Handle page load/navigation)
    function startProcessing() {
        const navId = state.currentNavId;

        // 腳本啟動日誌 (Script startup log)
        log('init', '═══════════════════════════════════════');
        log('init', 'YouTube Auto Switch Live Chat v1.3.8 (Optimized)'); // <-- 日誌版本更新
        log('init', '═══════════════════════════════════════');

        const isLiveChatPage = location.pathname.startsWith('/live_chat');
        // 檢查當前是否在影片觀看頁面 (這是 Live Chat Iframe 的容器)
        const isVideoPage = document.querySelector('ytd-watch-flexy, ytd-watch-grid');

        // --- 【效能優化:快速退出檢查 (Quick Exit Check)】---
        // 如果既不是 live_chat 頁面,也不是影片觀看頁面 (如首頁、訂閱頁),則立即退出
        if (!isLiveChatPage && !isVideoPage) {
            log('warn', () => 'Quick Exit: Not on a video page or live chat page. Aborting processing to save performance.');
            return;
        }
        // --- 【快速退出檢查結束】---

        // 處理內嵌聊天室頁面 (Handle embedded chat page)
        if (isLiveChatPage) {
            log('init', () => `→ Embedded Chat Page detected. Running switchChat on document.`);
            switchChat(document, navId);
            return;
        }

        // 處理主要觀看頁面 (Handle main video page) - 只有在 isVideoPage 為 true 時才會執行到這裡
        log('init', () => `→ Main Video Page detected. Starting MutationObserver.`);

        const observer = state.addObserver(new MutationObserver(mutations => {
            if (!state.isValidNavId(navId)) {
                observer.disconnect();
                return;
            }

            // 監聽新加入的 iframe (Observe for newly added iframes)
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeName === 'IFRAME') {
                        const src = node.src || '';
                        const srcdoc = node.srcdoc || '';
                        if (src.includes('live_chat') || srcdoc.includes('live_chat')) {
                            log('init', () => '→ New chat iframe detected.');
                            initIframe(node, navId);
                        }
                    } else if (node.querySelectorAll) {
                        // 檢查新增節點的子樹中是否包含 iframe
                        node.querySelectorAll('iframe[src*="live_chat"], iframe[srcdoc*="live_chat"]')
                            .forEach(iframe => {
                                log('init', () => '→ Chat iframe detected in added node.');
                                initIframe(iframe, navId);
                            });
                    }
                });
            });
        }));

        // 決定觀察的目標 (Determine observe target)
        // 優先監聽聊天容器,如果找不到則監聽 body
        const chatContainer = document.querySelector('#chat, #chat-container, #chatframe');
        const observeTarget = chatContainer || document.body;

        if (observeTarget && state.isValidNavId(navId)) {
            // 監聽聊天容器或 body,以捕捉 iframe 的出現
            observer.observe(observeTarget, { childList: true, subtree: true });
        }

        // 初始掃描 (Initial scan for existing iframes)
        state.addTimer(() => {
            if (!state.isValidNavId(navId)) return;
            log('init', () => `→ Initial scan for existing iframes.`);
            document.querySelectorAll('iframe[src*="live_chat"], iframe[srcdoc*="live_chat"]')
                .forEach(iframe => initIframe(iframe, navId));
        }, CONFIG.SPA_DELAY);
    }

    // - 腳本初始化 (Script Initialization)
    function init() {
        // 初始執行 (Initial execution)
        startProcessing();

        // 監聽 SPA 導航 (Listen for SPA navigation)
        // 使用旗標避免重複綁定 (Use flag to prevent duplicate binding)
        if (!window._ytAutoChatBound) {
            window._ytAutoChatBound = true;
            window.addEventListener('yt-navigate-finish', () => {
                log('init', () => '→ SPA navigation detected.');
                state.reset(); // 重置狀態
                startProcessing(); // 立即重新執行
            });
        }
    }

    init();

})();