YouTube 浏览助手

可在当前页面查看YouTube的缩略图,支持字幕下载

// ==UserScript==
// @name         YouTube 浏览助手
// @namespace    https://www.runningcheese.com/userscripts
// @description  可在当前页面查看YouTube的缩略图,支持字幕下载
// @author       RunningCheese
// @version      1.0
// @match        http*://www.youtube.com/*
// @match        http*://m.youtube.com/*
// @icon         https://t1.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=32&url=https://www.youtube.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 简化的元素创建工具
    const elements = {
        createAs(nodeType, config, appendTo) {
            const element = document.createElement(nodeType);
            if (config) {
                Object.entries(config).forEach(([key, value]) => {
                    element[key] = value;
                });
            }
            if (appendTo) appendTo.appendChild(element);
            return element;
        },
        getAs(selector) {
            return document.body.querySelector(selector);
        }
    };

    // 创建预览图片元素
    const preview = elements.createAs("img", {
        id: "preview",
        style: `
            position: absolute;
            z-index: 2000;
            max-width: 60vw;
            max-height: 60vh;
            border: 1px solid #fff;
            border-radius: 4px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            display: none;
            z-index: 1000000000;
        `
    }, document.body);

    // 添加CSS样式
    const style = elements.createAs('style', {
        textContent: `
            .yt-icon-btn {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                width: 16px;
                height: 16px;
                border-radius: 4px;
                cursor: pointer;
                margin-left: 8px;
                transition: background-color 0.3s;
                padding: 2px;
            }

            .yt-icon-btn svg {
                width: 14px;
                height: 14px;
                fill: currentColor;
            }

            .yt-subtitle-btn {
                color: white;
                background-color: #FF0000;
            }

            .yt-subtitle-btn:hover {
                background-color: #CC0000;
                color: white;
            }

            .yt-thumbnail-btn {
                color: white;
                background-color: #FF0000;
            }

            .yt-thumbnail-btn:hover {
                background-color: #CC0000;
                color: white;
            }
        `
    }, document.head);

    // YouTube缩略图和字幕查看器主体
    const youtubeViewer = {
        videoId: null,
        buttonAdded: false,
        buttonCheckInterval: null,

        // 获取当前视频ID
        getVideoId() {
            const urlParams = new URLSearchParams(window.location.search);
            return urlParams.get('v');
        },

        // 检查是否在视频页面
        isVideoPage() {
            return window.location.pathname.includes('/watch');
        },

        // 获取YouTube缩略图URL
        getThumbnailUrl() {
            const videoId = this.getVideoId();
            if (!videoId) return null;

            // 返回最高质量的缩略图
            return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
        },

        // 打开downsub.com下载字幕
        openDownsub() {
            const currentUrl = window.location.href;
            const downsubUrl = `https://downsub.com/?url=${encodeURIComponent(currentUrl)}`;
            window.open(downsubUrl, '_blank');
        },

        // 显示提示消息
        showToast(message) {
            const toast = elements.createAs("div", {
                style: `
                    position: fixed;
                    top: 12%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background-color: #FF0000;
                    color: white;
                    padding: 15px 20px;
                    border-radius: 4px;
                    font-size: 14px;
                    z-index: 1000000000;
                `,
                textContent: message
            }, document.body);

            setTimeout(() => {
                if (document.body.contains(toast)) {
                    document.body.removeChild(toast);
                }
            }, 3000);
        },

        // 添加字幕和缩略图按钮到YouTube logo右边
        addButtons() {
            if (elements.getAs('#yt-subtitle-btn') && elements.getAs('#yt-thumbnail-btn')) {
                return;
            }

            // 查找YouTube logo元素
            const logoElement = elements.getAs('#logo') ||
                               elements.getAs('ytd-topbar-logo-renderer') ||
                               elements.getAs('#masthead-logo') ||
                               elements.getAs('a[href="/"]');

            if (!logoElement) {
                console.log('找不到YouTube logo元素');
                return;
            }

            // 创建按钮容器
            let buttonContainer = elements.getAs('#yt-helper-buttons');
            if (!buttonContainer) {
                buttonContainer = elements.createAs('div', {
                    id: 'yt-helper-buttons',
                    style: `
                        display: flex;
                        align-items: center;
                        margin-left: 0px;
                    `
                });

                // 将按钮容器插入到logo的父元素中
                const logoParent = logoElement.parentElement;
                if (logoParent) {
                    logoParent.style.display = 'flex';
                    logoParent.style.alignItems = 'center';
                    logoParent.insertBefore(buttonContainer, logoElement.nextSibling);
                }
            }

            // 创建字幕按钮 - 只在视频页面显示
            if (!elements.getAs('#yt-subtitle-btn') && this.isVideoPage() && this.getVideoId()) {
                const subtitleBtn = elements.createAs('a', {
                    id: 'yt-subtitle-btn',
                    className: 'yt-icon-btn yt-subtitle-btn',
                    title: '下载视频字幕 (downsub.com)',
                    innerHTML: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.5a1 1 0 0 0-.8.4l-1.9 2.533a1 1 0 0 1-1.6 0L5.3 12.4a1 1 0 0 0-.8-.4H2a2 2 0 0 1-2-2V2zm7.194 2.766a1.688 1.688 0 0 0-.227-.272 1.467 1.467 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 5.734 4C4.776 4 4 4.746 4 5.667c0 .92.776 1.666 1.734 1.666.343 0 .662-.095.931-.26-.137.389-.39.804-.81 1.22a.405.405 0 0 0 .011.59c.173.16.447.155.614-.01 1.334-1.329 1.37-2.758.941-3.706a2.461 2.461 0 0 0-.227-.4zM11 7.073c-.136.389-.39.804-.81 1.22a.405.405 0 0 0 .012.59c.172.16.446.155.613-.01 1.334-1.329 1.37-2.758.942-3.706a2.466 2.466 0 0 0-.228-.4 1.686 1.686 0 0 0-.227-.273 1.466 1.466 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 10.07 4c-.957 0-1.734.746-1.734 1.667 0 .92.777 1.666 1.734 1.666.343 0 .662-.095.931-.26z"/></svg>',
                    onclick: () => this.openDownsub()
                }, buttonContainer);
            }

            // 创建缩略图按钮 - 只在视频页面显示
            if (!elements.getAs('#yt-thumbnail-btn') && this.isVideoPage() && this.getVideoId()) {
                const thumbnailBtn = elements.createAs('a', {
                    id: 'yt-thumbnail-btn',
                    className: 'yt-icon-btn yt-thumbnail-btn',
                    title: '查看视频缩略图',
                    innerHTML: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/><path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z"/></svg>',
                    onmouseenter: (e) => this.showThumbnailPreview(e),
                    onmouseleave: () => this.hideThumbnailPreview(),
                    onclick: () => this.openThumbnailInNewTab()
                }, buttonContainer);
            }

            this.buttonAdded = true;
            console.log('YouTube浏览助手按钮检查完成');
        },

        // 显示缩略图预览
        showThumbnailPreview(event) {
            const thumbnailUrl = this.getThumbnailUrl();
            if (thumbnailUrl) {
                preview.src = thumbnailUrl;
                const rect = event.currentTarget.getBoundingClientRect();
                preview.style.left = (rect.right + 10) + 'px';
                preview.style.top = rect.top + 'px';
                preview.style.width = 'auto';
                preview.style.height = 'auto';

                preview.onload = () => {
                    const screenWidth = window.innerWidth * 0.6;
                    const screenHeight = window.innerHeight * 0.6;

                    if (preview.naturalWidth > screenWidth || preview.naturalHeight > screenHeight) {
                        const widthRatio = screenWidth / preview.naturalWidth;
                        const heightRatio = screenHeight / preview.naturalHeight;
                        const ratio = Math.min(widthRatio, heightRatio);
                        preview.style.width = (preview.naturalWidth * ratio) + 'px';
                        preview.style.height = (preview.naturalHeight * ratio) + 'px';
                    }

                    const previewRect = preview.getBoundingClientRect();
                    if (previewRect.right > window.innerWidth) {
                        preview.style.left = (rect.left - previewRect.width - 10) + 'px';
                    }
                    if (previewRect.bottom > window.innerHeight) {
                        preview.style.top = (window.innerHeight - previewRect.height - 10) + 'px';
                    }

                    preview.style.display = 'block';
                };
            }
        },

        // 隐藏缩略图预览
        hideThumbnailPreview() {
            preview.style.display = 'none';
        },

        // 在新标签页打开缩略图
        openThumbnailInNewTab() {
            const thumbnailUrl = this.getThumbnailUrl();
            if (thumbnailUrl) {
                window.open(thumbnailUrl, '_blank');
            } else {
                this.showToast('无法获取视频缩略图');
            }
        },

        // 清理按钮
        cleanupButtons() {
            const subtitleBtn = elements.getAs('#yt-subtitle-btn');
            const thumbnailBtn = elements.getAs('#yt-thumbnail-btn');

            if (subtitleBtn) {
                subtitleBtn.remove();
            }
            if (thumbnailBtn) {
                thumbnailBtn.remove();
            }
        },

        // 重置状态
        reset() {
            this.buttonAdded = false;
            this.videoId = null;
            if (this.buttonCheckInterval) {
                clearInterval(this.buttonCheckInterval);
                this.buttonCheckInterval = null;
            }
        },

        // 启动按钮检查
        startButtonCheck() {
            if (this.buttonCheckInterval) {
                clearInterval(this.buttonCheckInterval);
            }

            this.buttonCheckInterval = setInterval(() => {
                // 在视频页面检查按钮是否存在
                if (this.isVideoPage() && this.getVideoId()) {
                    if (!elements.getAs('#yt-subtitle-btn') || !elements.getAs('#yt-thumbnail-btn')) {
                        console.log('视频页面按钮已消失,重新添加');
                        this.addButtons();
                    }
                } else {
                    // 不在视频页面时清理按钮
                    this.cleanupButtons();
                }
            }, 2000);
        },

        // 初始化
        init() {
            // 无论在哪个页面都启动检查
            this.addButtons();
            this.startButtonCheck();
            console.log('YouTube浏览助手初始化成功');

            // 监听页面变化
            let lastUrl = location.href;
            new MutationObserver(() => {
                if (lastUrl !== location.href) {
                    lastUrl = location.href;
                    console.log('页面URL变化:', lastUrl);

                    // 延迟处理,等待页面元素加载
                    setTimeout(() => {
                        this.videoId = this.getVideoId();
                        this.addButtons();
                    }, 1000);
                }
            }).observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    };

    // 等待页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setTimeout(() => youtubeViewer.init(), 2000);
        });
    } else {
        setTimeout(() => youtubeViewer.init(), 2000);
    }
})();