YouTube to Gemini 自动总结与字幕

在YouTube视频中添加按钮,点击后跳转到Gemini并自动输入提示词总结视频或生成字幕。长视频字幕首段自动处理,后续手动。首页缩略图自适应布局。

// ==UserScript==
// @name         YouTube to Gemini 自动总结与字幕
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  在YouTube视频中添加按钮,点击后跳转到Gemini并自动输入提示词总结视频或生成字幕。长视频字幕首段自动处理,后续手动。首页缩略图自适应布局。
// @author       hengyu (Optimized by Assistant)
// @match        *://www.youtube.com/*
// @match        *://gemini.google.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 ---
    const CHECK_INTERVAL_MS = 300; // Fallback polling interval increased to 300ms
    const YOUTUBE_ELEMENT_TIMEOUT_MS = 15000; // 等待YouTube元素的最大时间(毫秒)
    const GEMINI_ELEMENT_TIMEOUT_MS = 15000; // 等待Gemini元素的最大时间(毫秒)
    const GEMINI_PROMPT_EXPIRY_MS = 300000; // 提示词传输有效期5分钟
    const SUBTITLE_SEGMENT_DURATION_SECONDS = 1200; // 字幕分段时长,20分钟 = 1200秒
    // Key selectors for ensuring page/video context is ready
    const YOUTUBE_PLAYER_METADATA_SELECTOR = 'ytd-watch-metadata, #above-the-fold .title, #info-contents';
    const YOUTUBE_VIDEO_TITLE_SELECTOR = 'h1.ytd-watch-metadata, #video-title, #title h1, .title';
    // Thumbnail specific selectors
    const THUMBNAIL_CHECK_INTERVAL_MS = 1500; // 缩略图检查间隔

    // --- GM存储键 ---
    const PROMPT_KEY = 'geminiPrompt';
    const TITLE_KEY = 'videoTitle';
    const ORIGINAL_TITLE_KEY = 'geminiOriginalVideoTitle';
    const TIMESTAMP_KEY = 'timestamp';
    const ACTION_TYPE_KEY = 'geminiActionType';
    const VIDEO_TOTAL_DURATION_KEY = 'geminiVideoTotalDuration';
    const FIRST_SEGMENT_END_TIME_KEY = 'geminiFirstSegmentEndTime';

    // --- 调试日志 ---
    const DEBUG = true;
    function debugLog(message) {
        if (DEBUG) {
            console.log(`[YT->Gemini v1.2] ${message}`);
        }
    }

    // --- 辅助函数 ---
    function formatTimeHHMMSS(totalSeconds) {
        if (isNaN(totalSeconds) || totalSeconds < 0) {
            return '00:00:00';
        }
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = Math.floor(totalSeconds % 60);
        const pad = (num) => String(num).padStart(2, '0');
        return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
    }

    function parseISO8601DurationToSeconds(durationString) {
        if (!durationString || typeof durationString !== 'string' || !durationString.startsWith('PT')) return 0;
        let totalSeconds = 0;
        const timePart = durationString.substring(2);
        const hourMatch = timePart.match(/(\d+)H/);
        if (hourMatch) totalSeconds += parseInt(hourMatch[1]) * 3600;
        const minuteMatch = timePart.match(/(\d+)M/);
        if (minuteMatch) totalSeconds += parseInt(minuteMatch[1]) * 60;
        const secondMatch = timePart.match(/(\d+)S/);
        if (secondMatch) totalSeconds += parseInt(secondMatch[1]);
        return totalSeconds;
    }

    function waitForElement(selectors, timeoutMs, parent = document) {
        const selectorArray = Array.isArray(selectors) ? selectors : [selectors];
        const combinedSelector = selectorArray.join(', ');

        return new Promise((resolve, reject) => {
            const initialElement = findVisibleElement(combinedSelector, parent);
            if (initialElement) {
                debugLog(`Element found immediately: ${combinedSelector}`);
                return resolve(initialElement);
            }

            let observer = null;
            let timeoutId = null;

            const cleanup = () => {
                if (observer) {
                    observer.disconnect();
                    observer = null;
                    debugLog(`MutationObserver disconnected for: ${combinedSelector}`);
                }
                if (timeoutId) {
                    clearTimeout(timeoutId);
                    timeoutId = null;
                }
            };

            const onTimeout = () => {
                cleanup();
                debugLog(`Element not found or not visible after ${timeoutMs}ms: ${combinedSelector}`);
                reject(new Error(`Element not found or not visible: ${combinedSelector}`));
            };

            const checkNode = (node) => {
                if (node && node.nodeType === Node.ELEMENT_NODE) {
                    if (node.matches && node.matches(combinedSelector) && isElementVisible(node)) {
                         debugLog(`Element found via MutationObserver (direct match): ${combinedSelector}`);
                         cleanup();
                         resolve(node);
                         return true;
                    }
                    const foundDescendant = findVisibleElement(combinedSelector, node);
                    if (foundDescendant) {
                         debugLog(`Element found via MutationObserver (descendant): ${combinedSelector}`);
                         cleanup();
                         resolve(foundDescendant);
                         return true;
                    }
                }
                return false;
            };

            timeoutId = setTimeout(onTimeout, timeoutMs);

            observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.type === 'childList') {
                        for (const node of mutation.addedNodes) {
                            if (checkNode(node)) return;
                        }
                    } else if (mutation.type === 'attributes') {
                        if (checkNode(mutation.target)) return;
                    }
                }
                const element = findVisibleElement(combinedSelector, parent);
                if (element) {
                    debugLog(`Element found via MutationObserver (fallback check): ${combinedSelector}`);
                    cleanup();
                    resolve(element);
                }
            });

            observer.observe(parent === document ? document.documentElement : parent, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['style', 'class', 'disabled']
            });
            debugLog(`MutationObserver started for: ${combinedSelector}`);
        });
    }

    function findVisibleElement(selector, parent) {
        try {
            const elements = parent.querySelectorAll(selector);
            for (const el of elements) {
                if (isElementVisible(el)) {
                    if (selector.includes('button') && el.disabled) {
                       continue;
                    }
                    return el;
                }
            }
        } catch (e) {
            debugLog(`Error finding element with selector "${selector}": ${e}`);
        }
        return null;
    }

    function isElementVisible(el) {
        if (!el) return false;
        return (el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0);
    }

    function copyToClipboard(text) {
        navigator.clipboard.writeText(text).then(() => {
            debugLog("Text copied to clipboard via modern API.");
        }).catch(err => {
            debugLog(`Clipboard API failed: ${err}, using legacy method.`);
            legacyClipboardCopy(text);
        });
    }

    function legacyClipboardCopy(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.top = '-9999px';
        textarea.style.left = '-9999px';
        document.body.appendChild(textarea);
        textarea.select();
        try {
            const successful = document.execCommand('copy');
            debugLog(`Legacy copy attempt: ${successful ? 'Success' : 'Fail'}`);
        } catch (err) {
            debugLog('Failed to copy to clipboard using legacy execCommand: ' + err);
        }
        document.body.removeChild(textarea);
    }

    function showNotification(elementId, message, styles, duration = 15000) {
        let existingNotification = document.getElementById(elementId);
        if (existingNotification) {
            const existingTimeoutId = existingNotification.dataset.timeoutId;
            if (existingTimeoutId) {
                clearTimeout(parseInt(existingTimeoutId));
            }
            existingNotification.remove();
        }

        const notification = document.createElement('div');
        notification.id = elementId;
        notification.textContent = message;
        Object.assign(notification.style, styles);

        document.body.appendChild(notification);

        const closeButton = document.createElement('button');
        closeButton.textContent = '✕';
        Object.assign(closeButton.style, {
            position: 'absolute', top: '5px', right: '10px', background: 'transparent',
            border: 'none', color: 'inherit', fontSize: '16px', cursor: 'pointer', padding: '0', lineHeight: '1'
        });
        closeButton.onclick = () => notification.remove();
        notification.appendChild(closeButton);

        const timeoutId = setTimeout(() => notification.remove(), duration);
        notification.dataset.timeoutId = timeoutId.toString();
    }

    // --- YouTube Related ---
    const YOUTUBE_NOTIFICATION_ID = 'gemini-yt-notification';
    const YOUTUBE_NOTIFICATION_STYLE = {
        position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)',
        backgroundColor: 'rgba(0,0,0,0.85)', color: 'white', padding: '15px 35px 15px 20px',
        borderRadius: '8px', zIndex: '9999', maxWidth: 'calc(100% - 40px)', textAlign: 'left',
        boxSizing: 'border-box', whiteSpace: 'pre-wrap',
        boxShadow: '0 4px 12px rgba(0,0,0,0.3)'
    };
    const SUMMARY_BUTTON_ID = 'gemini-summarize-btn';
    const SUBTITLE_BUTTON_ID = 'gemini-subtitle-btn';
    const THUMBNAIL_BUTTON_CLASS = 'gemini-thumbnail-btn';
    const THUMBNAIL_PROCESSED_FLAG = 'data-gemini-processed';

    // --- Injected Styles ---
    GM_addStyle(`
        .${THUMBNAIL_BUTTON_CLASS} {
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            cursor: pointer;
            z-index: 100;
            display: flex;
            align-items: center;
            opacity: 0;
            transition: opacity 0.2s ease;
        }

        #dismissible:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-grid-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-rich-item-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-compact-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-playlist-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
        ytd-reel-item-renderer:hover .${THUMBNAIL_BUTTON_CLASS} {
            opacity: 1;
        }

        .${THUMBNAIL_BUTTON_CLASS}:hover {
            background-color: rgba(0, 0, 0, 0.9);
        }
    `);

    GM_addStyle(`
        /* Responsive Grid for YouTube Homepage Thumbnails */
        ytd-rich-grid-renderer > div#contents { /* Simplified selector */
            display: grid !important;
            /* Default: 2 columns for smaller screens or if no other media query matches */
            grid-template-columns: repeat(2, 1fr) !important;
            gap: 24px 16px !important; /* row-gap column-gap */
            padding: 0 !important; /* Reset padding */
            margin: 0 auto !important; /* Center contents if it\'s narrower than parent, or use \'0 !important\' if not needed */
            width: 100% !important; /* Ensure it takes full available width for grid calculation */

            /* Attempt to override YouTube\'s own column variables */
            --ytd-rich-grid-items-per-row: 2 !important;
            --ytd-rich-grid-posts-per-row: 2 !important; /* For posts grid if applicable */
            --ytd-rich-grid-game-cards-per-row: 2 !important; /* For game cards if applicable */
            --ytd-rich-grid-max-width: none !important; /* Remove max width constraint on contents if any */
        }

        /* Ensure grid items (video thumbnails) don\'t have conflicting fixed widths or margins from YT */
        ytd-rich-grid-renderer > div#contents > ytd-rich-item-renderer, /* Simplified selector */
        ytd-rich-grid-renderer > div#contents > ytd-grid-video-renderer, /* For grid video items */
        ytd-rich-grid-renderer > div#contents > .ytd-rich-grid-row > .ytd-rich-grid-media, /* Common pattern for items in rows */
        ytd-rich-grid-renderer > div#contents > * > ytd-thumbnail { /* Target thumbnails more broadly if direct items change */
            margin: 0 !important; /* Override default margins if they conflict with grid gap */
            width: auto !important; /* Let the grid cell define the width. Needed to override YT\'s inline styles or specific class widths. */
            max-width: none !important; /* Ensure items can fill grid cells */
            min-width: 0 !important; /* Allow items to shrink if needed */
        }
        /* Additional rule for the items themselves if they are direct children */
        ytd-rich-grid-renderer > div#contents > ytd-rich-item-renderer,
        ytd-rich-grid-renderer > div#contents > ytd-grid-video-renderer {
            box-sizing: border-box !important; /* Ensure padding/border don\'t expand item beyond grid cell */
        }


        /* Breakpoints for responsive columns */
        /* 3 columns */
        @media (min-width: 1000px) { /* Adjust breakpoint as needed */
            ytd-rich-grid-renderer > div#contents { /* Simplified selector */
                grid-template-columns: repeat(3, 1fr) !important;
                --ytd-rich-grid-items-per-row: 3 !important;
                --ytd-rich-grid-posts-per-row: 3 !important;
                --ytd-rich-grid-game-cards-per-row: 3 !important;
            }
        }

        /* 4 columns */
        @media (min-width: 1400px) { /* Adjust breakpoint as needed */
            ytd-rich-grid-renderer > div#contents { /* Simplified selector */
                grid-template-columns: repeat(4, 1fr) !important;
                --ytd-rich-grid-items-per-row: 4 !important;
                --ytd-rich-grid-posts-per-row: 4 !important;
                --ytd-rich-grid-game-cards-per-row: 4 !important;
            }
        }

        /* 5 columns */
        @media (min-width: 1700px) { /* Adjust breakpoint as needed */
            ytd-rich-grid-renderer > div#contents { /* Simplified selector */
                grid-template-columns: repeat(5, 1fr) !important;
                --ytd-rich-grid-items-per-row: 5 !important;
                --ytd-rich-grid-posts-per-row: 5 !important;
                --ytd-rich-grid-game-cards-per-row: 5 !important;
            }
        }

        /* Ensure shelves (like Shorts, Breaking News etc.) span full width */
        ytd-rich-grid-renderer > div#contents > ytd-rich-section-renderer, /* Simplified selector */
        ytd-rich-grid-renderer > div#contents > ytd-reel-shelf-renderer {
            grid-column: 1 / -1 !important; /* Make shelves span all columns */
            width: 100% !important; /* Ensure shelves also take full width */
            margin-top: 16px !important; /* Add some spacing for shelves */
            margin-bottom: 16px !important;
        }
    `);

    GM_addStyle(`
        #gemini-popup {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            z-index: 9999;
            width: 300px;
            display: none;
        }
        #gemini-popup .button {
            width: 100%;
            padding: 10px;
            margin: 5px 0;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        #gemini-popup .button:hover {
            background-color: #45a049;
        }
        #gemini-popup .status {
            margin-top: 10px;
            padding: 10px;
            border-radius: 4px;
            display: none;
        }
        #gemini-popup .success {
            background-color: #dff0d8;
            color: #3c763d;
        }
        #gemini-popup .error {
            background-color: #f2dede;
            color: #a94442;
        }
    `);

    function isVideoPage() {
        return window.location.pathname === '/watch' && new URLSearchParams(window.location.search).has('v');
    }

    // ---- 缩略图按钮逻辑 ----
    function getVideoInfoFromElement(element) {
        try {
            // 检查元素是否已经处理过
            if (element.hasAttribute(THUMBNAIL_PROCESSED_FLAG) && element.getAttribute(THUMBNAIL_PROCESSED_FLAG) === 'true') {
                return null;
            }

            let videoId = '';
            const linkElement = element.querySelector('a[href*="/watch?v="]');
            if (linkElement) {
                const href = linkElement.getAttribute('href');
                const match = href.match(/\/watch\?v=([^&]+)/);
                if (match && match[1]) {
                    videoId = match[1];
                }
            }

            let videoTitle = '';
            const titleElement = element.querySelector('#video-title, .title, [title]');
            if (titleElement) {
                videoTitle = titleElement.textContent?.trim() || titleElement.getAttribute('title')?.trim() || '';
            }

            if (!videoId || !videoTitle) {
                return null;
            }

            return {
                id: videoId,
                title: videoTitle,
                url: `https://www.youtube.com/watch?v=${videoId}`
            };
        } catch (error) {
            debugLog('获取视频信息时出错:' + error.message);
            return null;
        }
    }

    function handleThumbnailButtonClick(event, videoInfo) {
        event.preventDefault();
        event.stopPropagation();

        try {
            if (!videoInfo || !videoInfo.url || !videoInfo.title) {
                throw new Error('视频信息不完整');
            }

            const prompt = `请分析这个YouTube视频: ${videoInfo.url}\n\n提供一个全面的摘要,包括主要观点、关键见解和视频中讨论的重要细节,以结构化的方式分解内容,并包括任何重要的结论或要点。`;
            debugLog(`从缩略图生成提示词: ${videoInfo.title}`);

            GM_setValue(PROMPT_KEY, prompt);
            GM_setValue(TITLE_KEY, videoInfo.title);
            GM_setValue(ORIGINAL_TITLE_KEY, videoInfo.title);
            GM_setValue(TIMESTAMP_KEY, Date.now());
            GM_setValue(ACTION_TYPE_KEY, 'summary');
            window.open('https://gemini.google.com/', '_blank');
            debugLog("从缩略图打开Gemini标签页。");

            const notificationMessage = `
已跳转到 Gemini!
系统将尝试自动输入提示词并发送请求。

视频: "${videoInfo.title}"

(如果自动操作失败,提示词已复制到剪贴板,请手动粘贴)
            `.trim();
            showNotification(YOUTUBE_NOTIFICATION_ID, notificationMessage, YOUTUBE_NOTIFICATION_STYLE, 10000);
            copyToClipboard(prompt);

        } catch (error) {
            console.error("[YT->Gemini] 处理缩略图按钮点击时出错:", error);
            showNotification(YOUTUBE_NOTIFICATION_ID, `创建摘要时出错: ${error.message}`, { ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025', color: 'white' }, 10000);
        }
    }

    // 改进的缩略图按钮添加函数,更强大的处理逻辑
    function addThumbnailButtons() {
        // 为提高性能,如果是视频页面则跳过
        if (isVideoPage()) {
            return;
        }

        // 新的选择器列表,更全面的覆盖范围
        const videoElementSelectors = [
            'ytd-rich-item-renderer',
            'ytd-grid-video-renderer',
            'ytd-video-renderer',
            'ytd-compact-video-renderer',
            'ytd-playlist-video-renderer',
            'ytd-reel-item-renderer',
            '.ytd-video-preview',
            '.video-card',
            '.ytd-compact-playlist-renderer',
            'ytd-grid-playlist-renderer'
        ];

        try {
            const videoElements = document.querySelectorAll(videoElementSelectors.join(','));
            let addedButtons = 0;

            videoElements.forEach(element => {
                // 如果已经处理过则跳过
                if (element.hasAttribute(THUMBNAIL_PROCESSED_FLAG) && element.getAttribute(THUMBNAIL_PROCESSED_FLAG) === 'true') {
                    return;
                }

                // 如果已经有按钮则跳过且标记为已处理
                if (element.querySelector(`.${THUMBNAIL_BUTTON_CLASS}`)) {
                    element.setAttribute(THUMBNAIL_PROCESSED_FLAG, 'true');
                    return;
                }

                const thumbnailContainer = element.querySelector('#thumbnail, .thumbnail, a[href*="/watch"]');
                if (!thumbnailContainer) {
                    return;
                }

                const videoInfo = getVideoInfoFromElement(element);
                if (!videoInfo) {
                    return;
                }

                const button = document.createElement('button');
                button.className = THUMBNAIL_BUTTON_CLASS;
                button.textContent = '📝 总结';
                button.title = '使用Gemini总结此视频';
                button.addEventListener('click', (e) => handleThumbnailButtonClick(e, videoInfo));

                thumbnailContainer.style.position = 'relative';
                thumbnailContainer.appendChild(button);

                // 标记为已处理
                element.setAttribute(THUMBNAIL_PROCESSED_FLAG, 'true');
                addedButtons++;
            });

            if (addedButtons > 0) {
                debugLog(`成功添加了 ${addedButtons} 个缩略图按钮。`);
            }
        } catch (e) {
            debugLog('addThumbnailButtons出错: ' + e.message);
        }
    }

    // 设置更强大的缩略图观察系统
    function setupThumbnailButtonSystem() {
        // 首先,立即运行检查
        addThumbnailButtons();

        // DOM变化观察器 - 独立观察缩略图变化
        const thumbnailObserver = new MutationObserver((mutations) => {
            let shouldAddButtons = false;

            for (const mutation of mutations) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName && (
                                node.tagName.toLowerCase().includes('ytd-') ||
                                node.querySelector('ytd-rich-item-renderer, ytd-grid-video-renderer, ytd-video-renderer')
                            )) {
                                shouldAddButtons = true;
                                break;
                            }
                        }
                    }
                }
                if (shouldAddButtons) break;
            }

            if (shouldAddButtons) {
                clearTimeout(window.thumbnailButtonTimeout);
                window.thumbnailButtonTimeout = setTimeout(addThumbnailButtons, 300);
            }
        });

        // 设置观察器来监视整个页面的变化
        thumbnailObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 页面导航和其他事件处理
        function handleNavigation() {
            if (!isVideoPage()) {
                debugLog("检测到页面导航或滚动,添加缩略图按钮");
                clearTimeout(window.thumbnailButtonTimeout);
                window.thumbnailButtonTimeout = setTimeout(addThumbnailButtons, 500);
            }
        }

        // YouTube特有的导航事件
        window.addEventListener('yt-navigate-finish', handleNavigation);

        // 滚动事件处理(用于延迟加载的内容)
        let scrollTimeout;
        window.addEventListener('scroll', () => {
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(handleNavigation, 500);
        });

        // 定期检查缩略图按钮
        setInterval(() => {
            if (!isVideoPage()) {
                addThumbnailButtons();
            }
        }, THUMBNAIL_CHECK_INTERVAL_MS);

        // 页面加载完成后再次检查
        window.addEventListener('load', () => {
            setTimeout(addThumbnailButtons, 1000);
        });
    }

    async function addYouTubeActionButtons() {
        if (!isVideoPage()) {
            debugLog("addYouTubeActionButtons called on non-video page, ensuring removal.");
            removeYouTubeActionButtonsIfExists();
            return;
        }

        if (document.getElementById(SUMMARY_BUTTON_ID) || document.getElementById(SUBTITLE_BUTTON_ID)) {
            debugLog("Action buttons already exist.");
            return;
        }

        debugLog("Video page detected. Attempting to add action buttons...");

        // 扩展容器选择器,添加更多备选项
        const containerSelectors = [
            '#top-row.ytd-watch-metadata > #subscribe-button',
            '#meta-contents #subscribe-button',
            '#owner #subscribe-button',
            '#meta-contents #top-row',
            '#above-the-fold #title',
            'ytd-watch-metadata #actions',
            '#masthead #end',
            // 添加更通用的备选选择器
            'ytd-watch-metadata',
            '#above-the-fold',
            '#meta',
            '#info-contents',
            '#primary-inner',
            '#top-level-buttons-computed',
            '#menu-container',
            '#description'
        ];

        try {
            let anchorElement = null;

            try {
                // 尝试使用waitForElement查找元素
                anchorElement = await waitForElement(containerSelectors, YOUTUBE_ELEMENT_TIMEOUT_MS);
                debugLog(`Found anchor element using selector matching: ${anchorElement.tagName}[id="${anchorElement.id}"][class="${anchorElement.className}"]`);
            } catch (error) {
                debugLog("Wait for element failed, trying direct query fallback: " + error);

                // 如果waitForElement失败,直接查询DOM
                for (const selector of containerSelectors) {
                    const element = document.querySelector(selector);
                    if (element && isElementVisible(element)) {
                        anchorElement = element;
                        debugLog(`Found anchor via direct query: ${selector}`);
                        break;
                    }
                }

                // 如果仍未找到,尝试更通用的容器
                if (!anchorElement) {
                    const fallbackContainers = [
                        document.querySelector('#primary-inner'),
                        document.querySelector('#primary'),
                        document.querySelector('ytd-watch-flexy'),
                        document.querySelector('#player-container'),
                        document.querySelector('#container.ytd-masthead')
                    ];

                    for (const container of fallbackContainers) {
                        if (container && isElementVisible(container)) {
                            anchorElement = container;
                            debugLog(`Using fallback container: ${container.tagName}[id="${container.id}"]`);
                            break;
                        }
                    }
                }

                // 如果仍然没有找到任何容器,使用body作为最后的备选
                if (!anchorElement) {
                    anchorElement = document.body;
                    debugLog("Using document.body as last resort container");
                }
            }

            if (document.getElementById(SUMMARY_BUTTON_ID) || document.getElementById(SUBTITLE_BUTTON_ID)) {
                debugLog("Buttons were added concurrently, skipping.");
                return;
            }

            const summaryButton = document.createElement('button');
            summaryButton.id = SUMMARY_BUTTON_ID;
            summaryButton.textContent = '📝 Gemini摘要';
            Object.assign(summaryButton.style, {
                backgroundColor: '#1a73e8', color: 'white', border: 'none', borderRadius: '18px',
                padding: '0 16px', margin: '0 8px', cursor: 'pointer', fontWeight: '500',
                height: '36px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: '14px', zIndex: '100', whiteSpace: 'nowrap', transition: 'background-color 0.3s ease'
            });
            summaryButton.onmouseover = () => summaryButton.style.backgroundColor = '#185abc';
            summaryButton.onmouseout = () => summaryButton.style.backgroundColor = '#1a73e8';
            summaryButton.addEventListener('click', handleSummarizeClick);

            const subtitleButton = document.createElement('button');
            subtitleButton.id = SUBTITLE_BUTTON_ID;
            subtitleButton.textContent = '🎯 生成字幕';
            Object.assign(subtitleButton.style, {
                backgroundColor: '#28a745',
                color: 'white', border: 'none', borderRadius: '18px',
                padding: '0 16px', margin: '0 8px 0 0', cursor: 'pointer', fontWeight: '500',
                height: '36px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: '14px', zIndex: '100', whiteSpace: 'nowrap', transition: 'background-color 0.3s ease'
            });
            subtitleButton.onmouseover = () => subtitleButton.style.backgroundColor = '#218838';
            subtitleButton.onmouseout = () => subtitleButton.style.backgroundColor = '#28a745';
            subtitleButton.addEventListener('click', handleGenerateSubtitlesClick);

            // 改进的按钮插入逻辑,具有多种回退策略
            try {
                if (anchorElement.id?.includes('subscribe-button') || anchorElement.tagName === 'BUTTON') {
                    anchorElement.parentNode.insertBefore(summaryButton, anchorElement);
                    anchorElement.parentNode.insertBefore(subtitleButton, summaryButton);
                    debugLog(`Buttons inserted before anchor: ${anchorElement.id || anchorElement.tagName}`);
                } else if (anchorElement.id === 'actions' || anchorElement.id === 'end' || anchorElement.id === 'top-row') {
                    anchorElement.insertBefore(summaryButton, anchorElement.firstChild);
                    anchorElement.insertBefore(subtitleButton, summaryButton);
                    debugLog(`Buttons inserted as first children of container: ${anchorElement.id || anchorElement.tagName}`);
                } else {
                    anchorElement.appendChild(subtitleButton);
                    anchorElement.appendChild(summaryButton);
                    debugLog(`Buttons appended to container: ${anchorElement.id || anchorElement.tagName}`);
                }
            } catch (error) {
                // 如果上述所有尝试都失败,创建固定位置按钮
                debugLog(`Error inserting buttons normally: ${error}, creating fixed position buttons`);
                document.body.appendChild(subtitleButton);
                document.body.appendChild(summaryButton);

                // 更新样式为固定定位
                Object.assign(summaryButton.style, {
                    position: 'fixed',
                    top: '70px',
                    right: '20px',
                    zIndex: '9999'
                });

                Object.assign(subtitleButton.style, {
                    position: 'fixed',
                    top: '120px',
                    right: '20px',
                    zIndex: '9999'
                });
            }

            debugLog("Action buttons successfully added!");

        } catch (error) {
            console.error('[YT->Gemini] Failed to add action buttons:', error);
            removeYouTubeActionButtonsIfExists();

            // 最后的备选方案:在页面上添加固定位置的按钮
            if (isVideoPage()) {
                try {
                    debugLog("Attempting last resort fixed position buttons");
                    createFixedPositionButtons();
                } catch (e) {
                    console.error("Even fixed buttons failed:", e);
                }
            }
        }
    }

    // 创建固定位置按钮的备选函数
    function createFixedPositionButtons() {
        if (document.getElementById(SUMMARY_BUTTON_ID) || document.getElementById(SUBTITLE_BUTTON_ID)) {
            return;
        }

        const summaryButton = document.createElement('button');
        summaryButton.id = SUMMARY_BUTTON_ID;
        summaryButton.textContent = '📝 Gemini摘要';
        Object.assign(summaryButton.style, {
            position: 'fixed',
            top: '70px',
            right: '20px',
            backgroundColor: '#1a73e8',
            color: 'white',
            border: 'none',
            borderRadius: '18px',
            padding: '0 16px',
            cursor: 'pointer',
            fontWeight: '500',
            height: '36px',
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: '14px',
            zIndex: '99999',
            whiteSpace: 'nowrap'
        });
        summaryButton.addEventListener('click', handleSummarizeClick);

        const subtitleButton = document.createElement('button');
        subtitleButton.id = SUBTITLE_BUTTON_ID;
        subtitleButton.textContent = '🎯 生成字幕';
        Object.assign(subtitleButton.style, {
            position: 'fixed',
            top: '120px',
            right: '20px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '18px',
            padding: '0 16px',
            cursor: 'pointer',
            fontWeight: '500',
            height: '36px',
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: '14px',
            zIndex: '99999',
            whiteSpace: 'nowrap'
        });
        subtitleButton.addEventListener('click', handleGenerateSubtitlesClick);

        document.body.appendChild(summaryButton);
        document.body.appendChild(subtitleButton);
        debugLog("Created fixed position buttons as last resort");
    }

    function handleSummarizeClick() {
        try {
            const youtubeUrl = window.location.href;
            const titleElement = document.querySelector(YOUTUBE_VIDEO_TITLE_SELECTOR);
            const videoTitle = titleElement?.textContent?.trim() || document.title.replace(/ - YouTube$/, '').trim() || 'Unknown Video';
            const prompt = `请分析这个YouTube视频: ${youtubeUrl}\n\n提供一个全面的摘要,包括主要观点、关键见解和视频中讨论的重要细节,以结构化的方式分解内容,并包括任何重要的结论或要点。`;
            debugLog(`Summarize prompt: ${prompt}`);
            GM_setValue(PROMPT_KEY, prompt);
            GM_setValue(TITLE_KEY, videoTitle);
            GM_setValue(ORIGINAL_TITLE_KEY, videoTitle);
            GM_setValue(TIMESTAMP_KEY, Date.now());
            GM_setValue(ACTION_TYPE_KEY, 'summary');
            window.open('https://gemini.google.com/', '_blank');
            showNotification(YOUTUBE_NOTIFICATION_ID, `已跳转到 Gemini 进行视频总结...\n"${videoTitle}"`, YOUTUBE_NOTIFICATION_STYLE);
            copyToClipboard(prompt);
        } catch (error) {
            console.error("[YT->Gemini] Error during summarize button click:", error);
            showNotification(YOUTUBE_NOTIFICATION_ID, `创建摘要时出错: ${error.message}`, { ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025', color: 'white' }, 10000);
        }
    }

    async function handleGenerateSubtitlesClick() {
        try {
            await waitForElement(YOUTUBE_PLAYER_METADATA_SELECTOR, YOUTUBE_ELEMENT_TIMEOUT_MS);
            debugLog("Player metadata element found, proceeding to get video details for subtitles.");

            const youtubeUrl = window.location.href;
            const titleElement = document.querySelector(YOUTUBE_VIDEO_TITLE_SELECTOR);
            const videoTitle = titleElement?.textContent?.trim() || document.title.replace(/ - YouTube$/, '').trim() || 'Unknown Video';

            let videoDurationInSeconds = 0;
            try {
                const durationMetaElement = document.querySelector('meta[itemprop="duration"]');
                if (durationMetaElement && durationMetaElement.content) {
                    videoDurationInSeconds = parseISO8601DurationToSeconds(durationMetaElement.content);
                    debugLog(`Video duration from meta: ${durationMetaElement.content} -> ${videoDurationInSeconds}s`);
                } else {
                    debugLog("Duration meta tag not found or has no content.");
                }
            } catch (e) { debugLog("Failed to get video duration: " + e); }

            if (videoDurationInSeconds <= 0) {
                 showNotification(YOUTUBE_NOTIFICATION_ID, "无法获取当前视频时长,无法启动字幕任务。请尝试刷新页面。", { ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' }, 15000);
                 return;
            }

            const startTime = 0;
            const firstSegmentActualEndTimeSeconds = Math.min(videoDurationInSeconds, SUBTITLE_SEGMENT_DURATION_SECONDS);
            const startTimeFormatted = formatTimeHHMMSS(startTime);
            const endTimeFormatted = formatTimeHHMMSS(firstSegmentActualEndTimeSeconds);

            const prompt = `${youtubeUrl}
1.不要添加自己的语言
2.变成简体中文,流畅版本。

YouTube
请提取此视频从${startTimeFormatted}到${endTimeFormatted}的完整字幕文本。`;

            GM_setValue(PROMPT_KEY, prompt);
            const titleForGeminiNotificationDisplay = `${videoTitle} (字幕 ${startTimeFormatted}-${endTimeFormatted})`;
            GM_setValue(TITLE_KEY, titleForGeminiNotificationDisplay);
            GM_setValue(ORIGINAL_TITLE_KEY, videoTitle);
            GM_setValue(TIMESTAMP_KEY, Date.now());
            GM_setValue(ACTION_TYPE_KEY, 'subtitle');
            GM_setValue(VIDEO_TOTAL_DURATION_KEY, videoDurationInSeconds);
            GM_setValue(FIRST_SEGMENT_END_TIME_KEY, firstSegmentActualEndTimeSeconds);

            const youtubeNotificationMessage = `已跳转到 Gemini 生成字幕: ${startTimeFormatted} - ${endTimeFormatted}...\n"${videoTitle}"`;
            showNotification(YOUTUBE_NOTIFICATION_ID, youtubeNotificationMessage, YOUTUBE_NOTIFICATION_STYLE, 15000);

            window.open('https://gemini.google.com/', '_blank');
            copyToClipboard(prompt);

        } catch (error) {
            console.error("[YT->Gemini] Error during subtitle click:", error);
            showNotification(YOUTUBE_NOTIFICATION_ID, `生成字幕时出错: ${error.message}`, { ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' }, 15000);
        }
    }

    function removeYouTubeActionButtonsIfExists() {
        const summaryButton = document.getElementById(SUMMARY_BUTTON_ID);
        if (summaryButton) {
            summaryButton.remove();
            debugLog("Removed existing summary button.");
        }
        const subtitleButton = document.getElementById(SUBTITLE_BUTTON_ID);
        if (subtitleButton) {
            subtitleButton.remove();
            debugLog("Removed existing subtitle button.");
        }
    }

    // --- Gemini Related ---
    const GEMINI_NOTIFICATION_ID = 'gemini-auto-notification';
    const GEMINI_NOTIFICATION_STYLES = {
        info: { backgroundColor: '#e8f4fd', color: '#1967d2', border: '1px solid #a8c7fa' },
        warning: { backgroundColor: '#fef7e0', color: '#a56300', border: '1px solid #fdd663' },
        error: { backgroundColor: '#fce8e6', color: '#c5221f', border: '1px solid #f7a7a5' }
    };
    const BASE_GEMINI_NOTIFICATION_STYLE = {
        position: 'fixed', bottom: '230px', right: '20px', padding: '15px 35px 15px 20px',
        borderRadius: '8px', zIndex: '9999', maxWidth: '350px', textAlign: 'left',
        boxSizing: 'border-box', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', whiteSpace: 'pre-wrap'
    };

    function showGeminiNotification(message, type = "info", duration = 12000) {
        const style = { ...BASE_GEMINI_NOTIFICATION_STYLE, ...(GEMINI_NOTIFICATION_STYLES[type] || GEMINI_NOTIFICATION_STYLES.info) };
        if (message.length > 150) {
             style.maxWidth = '500px';
        }
        showNotification(GEMINI_NOTIFICATION_ID, message, style, duration);
    }

    async function handleGeminiPage() {
        debugLog("Gemini page loaded. Checking for pending action...");

        const prompt = GM_getValue(PROMPT_KEY, null);
        const timestamp = GM_getValue(TIMESTAMP_KEY, 0);
        const notificationTitleForSimpleMessage = GM_getValue(TITLE_KEY, 'N/A');
        const originalVideoTitle = GM_getValue(ORIGINAL_TITLE_KEY, 'N/A');
        const actionType = GM_getValue(ACTION_TYPE_KEY, null);
        const videoTotalDurationSeconds = GM_getValue(VIDEO_TOTAL_DURATION_KEY, 0);

        if(actionType) GM_deleteValue(ACTION_TYPE_KEY);

        debugLog(`Retrieved from GM: actionType=${actionType}, promptExists=${!!prompt}, notificationTitleForSimpleMessage=${notificationTitleForSimpleMessage}, originalVideoTitle=${originalVideoTitle}, timestamp=${timestamp}, videoTotalDuration=${videoTotalDurationSeconds}`);

        const clearAllGmValues = () => {
            debugLog("Clearing all GM values.");
            GM_deleteValue(PROMPT_KEY);
            GM_deleteValue(TITLE_KEY);
            GM_deleteValue(ORIGINAL_TITLE_KEY);
            GM_deleteValue(TIMESTAMP_KEY);
            GM_deleteValue(VIDEO_TOTAL_DURATION_KEY);
            GM_deleteValue(FIRST_SEGMENT_END_TIME_KEY);
        };

        if (!prompt || !actionType || Date.now() - timestamp > GEMINI_PROMPT_EXPIRY_MS) {
            debugLog("No valid prompt, actionType, or prompt expired.");
            clearAllGmValues();
            return;
        }

        debugLog(`Valid action (${actionType}) found. Proceeding to interact with Gemini page.`);
        const initialNotificationMessageIntro = actionType === 'summary' ? '总结' : '字幕';
        showGeminiNotification(`检测到来自 YouTube 的 "${initialNotificationMessageIntro}" 请求...\n视频: "${originalVideoTitle}"`, "info", 10000);

        const textareaSelectors = ['div.input-area > div.input-box > div[contenteditable="true"]', 'div[role="textbox"][contenteditable="true"]', 'textarea[aria-label*="Prompt"]', 'div[contenteditable="true"]', 'textarea'];
        const sendButtonSelectors = ['button[aria-label*="Send message"], button[aria-label*="发送消息"]', 'button:has(span[class*="send-icon"])', 'button.send-button', 'button:has(mat-icon[data-mat-icon-name="send"])', 'button[aria-label="Run"], button[aria-label="Submit"]'];
        const geminiSuccessNotificationDuration = 15000;

        try {
            debugLog("Waiting for textarea...");
            const textarea = await waitForElement(textareaSelectors, GEMINI_ELEMENT_TIMEOUT_MS);
            debugLog("Textarea found. Focusing and inputting prompt.");
            textarea.focus();
            if (textarea.isContentEditable) textarea.textContent = prompt;
            else if (textarea.tagName === 'TEXTAREA') textarea.value = prompt;
            else {
                debugLog("Cannot input text into the found element.");
                throw new Error("Could not determine how to input text into the found element.");
            }
            textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
            textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
            debugLog("Prompt inserted and events dispatched.");
            await new Promise(resolve => setTimeout(resolve, 250));

            debugLog("Waiting for send button...");
            const sendButton = await waitForElement(sendButtonSelectors, GEMINI_ELEMENT_TIMEOUT_MS);
            debugLog("Send button found. Checking if enabled.");
            if (sendButton.disabled || sendButton.getAttribute('aria-disabled') === 'true') {
                debugLog("Send button is disabled. Waiting a bit longer...");
                await new Promise(resolve => setTimeout(resolve, 600));
                if (sendButton.disabled || sendButton.getAttribute('aria-disabled') === 'true') {
                    debugLog("Send button remained disabled.");
                    const errorMessage = "发送按钮仍然禁用。提示词已复制,请手动粘贴并发送。";
                    console.warn(`[YT->Gemini] ${errorMessage}`);
                    showGeminiNotification(errorMessage, "warning", geminiSuccessNotificationDuration);
                    copyToClipboard(prompt);
                    clearAllGmValues();
                    return;
                }
                debugLog("Send button became enabled after waiting.");
            }
            debugLog("Clicking send button...");
            sendButton.click();
            debugLog("Prompt sent to Gemini successfully.");

            let finalNotificationMessage;
            const notificationMessageIntro = actionType === 'summary' ? '总结' : '字幕';

            if (actionType === 'subtitle' && videoTotalDurationSeconds > SUBTITLE_SEGMENT_DURATION_SECONDS) {
                const firstSegmentDisplayEndTime = formatTimeHHMMSS(SUBTITLE_SEGMENT_DURATION_SECONDS);
                const suggestedNextStartTimeFormatted = firstSegmentDisplayEndTime;
                const suggestedNextSegmentEndTimeSeconds = Math.min(videoTotalDurationSeconds, SUBTITLE_SEGMENT_DURATION_SECONDS * 2);
                const suggestedNextEndTimeFormatted = formatTimeHHMMSS(suggestedNextSegmentEndTimeSeconds);

                finalNotificationMessage = `提示... 如果您需要提取后续部分的字幕 (例如从 ${suggestedNextStartTimeFormatted} 到 ${suggestedNextEndTimeFormatted}):
1. 需要手动修改时间范围(最好不超过20分钟)
2. 若Gemini模型不是2.5 Pro,建议暂停切换
3. 若Gemini不生成或繁体字,让他"重做"
`.trim();
            } else {
                finalNotificationMessage = `"${notificationMessageIntro}" 请求已发送! (视频: "${notificationTitleForSimpleMessage}")`;
            }
            showGeminiNotification(finalNotificationMessage, "info", geminiSuccessNotificationDuration);
            clearAllGmValues();

        } catch (error) {
            console.error('[YT->Gemini] Error on Gemini page:', error);
            showGeminiNotification(`自动操作失败: ${error.message}\n提示词已复制,请手动粘贴。`, "error", geminiSuccessNotificationDuration);
            copyToClipboard(prompt);
            clearAllGmValues();
        }
    }

    // --- 添加视频页面观察器确保按钮存在 ---
    function createVideoPageObserver() {
        const observer = new MutationObserver((mutations) => {
            if (isVideoPage() && !document.getElementById(SUMMARY_BUTTON_ID)) {
                debugLog("视频页面结构变化,尝试重新添加按钮");
                setTimeout(addYouTubeActionButtons, 500);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class', 'style']
        });

        return observer;
    }

    // --- Main Execution Logic ---
    debugLog("脚本启动...");

    if (window.location.hostname.includes('www.youtube.com')) {
        debugLog("YouTube域名。");

        // 缩略图按钮相关初始化应先于其他功能
        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            setupThumbnailButtonSystem();
        } else {
            document.addEventListener('DOMContentLoaded', setupThumbnailButtonSystem, { once: true });
        }

        // 视频页面按钮逻辑
        const runYouTubeLogic = async () => {
            if (isVideoPage()) {
                debugLog("当前是视频页面。处理按钮逻辑。");
                try {
                    await waitForElement(YOUTUBE_PLAYER_METADATA_SELECTOR, YOUTUBE_ELEMENT_TIMEOUT_MS);
                    debugLog("YouTube关键视频元素已准备好执行逻辑。");
                    addYouTubeActionButtons();

                    // 创建观察器确保按钮不消失
                    createVideoPageObserver();
                } catch (error) {
                    debugLog("无法找到关键YouTube视频元素或视频页面上的其他错误: " + error);
                    removeYouTubeActionButtonsIfExists();

                    // 即使waitForElement失败,也尝试添加固定位置按钮
                    setTimeout(createFixedPositionButtons, 2000);
                }
            } else {
                debugLog("非视频页面。确保移除操作按钮。");
                removeYouTubeActionButtonsIfExists();

                // 在非视频页面特别触发缩略图添加
                setTimeout(addThumbnailButtons, 300);
            }
        };

        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            runYouTubeLogic();
        } else {
            document.addEventListener('DOMContentLoaded', runYouTubeLogic, { once: true });
        }

        // 页面导航事件监听
        window.addEventListener('yt-navigate-finish', () => {
            debugLog("检测到yt-navigate-finish事件。");
            requestAnimationFrame(runYouTubeLogic);
            // 导航完成后专门触发缩略图更新
            setTimeout(addThumbnailButtons, 500);
        });

        window.addEventListener('popstate', () => {
            debugLog("检测到popstate事件。");
            requestAnimationFrame(runYouTubeLogic);
            // 历史导航后专门触发缩略图更新
            setTimeout(addThumbnailButtons, 500);
        });

        // 额外的加载检测以确保按钮存在
        window.addEventListener('load', () => {
            debugLog("检测到window加载事件。");
            setTimeout(runYouTubeLogic, 1000);
            // 页面完全加载后额外检查
            setTimeout(addThumbnailButtons, 1500);
        });

    } else if (window.location.hostname.includes('gemini.google.com')) {
        debugLog("Gemini域名。");
        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            handleGeminiPage();
        } else {
            window.addEventListener('DOMContentLoaded', handleGeminiPage, { once: true });
        }
    } else {
        debugLog(`脚本加载在未识别的域名上: ${window.location.hostname}`);
    }

    function createPopup() {
        const popup = document.createElement('div');
        popup.id = 'gemini-popup';
        popup.innerHTML = `
            <button id="gemini-start-summary" class="button">开始总结当前视频</button>
            <div id="gemini-status" class="status"></div>
        `;
        document.body.appendChild(popup);
        return popup;
    }

    function showPopup() {
        const popup = document.getElementById('gemini-popup') || createPopup();
        popup.style.display = 'block';
        const startButton = document.getElementById('gemini-start-summary');
        const statusDiv = document.getElementById('gemini-status');

        startButton.onclick = () => {
            try {
                if (!isVideoPage()) {
                    showStatus('请在YouTube视频页面使用此功能', 'error');
                    return;
                }
                handleSummarizeClick();
                showStatus('开始总结视频...', 'success');
                popup.style.display = 'none';
            } catch (error) {
                showStatus('发生错误:' + error.message, 'error');
            }
        };

        function showStatus(message, type) {
            statusDiv.textContent = message;
            statusDiv.className = 'status ' + type;
            statusDiv.style.display = 'block';
        }
    }

    function addExtensionIconClickListener() {
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.addedNodes) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            const extensionIcon = node.querySelector('ytd-masthead #buttons ytd-button-renderer');
                            if (extensionIcon) {
                                extensionIcon.addEventListener('click', (e) => {
                                    e.preventDefault();
                                    e.stopPropagation();
                                    showPopup();
                                });
                            }
                        }
                    }
                }
            }
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        addExtensionIconClickListener();
    } else {
        window.addEventListener('DOMContentLoaded', addExtensionIconClickListener, {once: true});
    }
})();