YouTube(油管)添加到稍后观看

在后台将 YouTube 视频添加到"稍后观看"列表,按钮贴边半隐藏,可上下拖动,添加设置按钮和自定义快捷键功能

// ==UserScript==
// @name         YouTube(油管)添加到稍后观看
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在后台将 YouTube 视频添加到"稍后观看"列表,按钮贴边半隐藏,可上下拖动,添加设置按钮和自定义快捷键功能
// @author       特比欧炸
// @match        https://www.youtube.com/watch*
// @match        https://www.youtube.com/   // 添加匹配主页的URL
// @grant        none
// @charset      UTF-8
// @license      MIT
// MIT License
// https://opensource.org/licenses/MIT
// ==/UserScript==

(function() {
    'use strict';

    const i18n = {
        en: {
            watchLater: 'Watch Later',
            settings: 'Settings',
            promptShortcut: 'Enter a new shortcut key (current is "{key}")',
            invalidShortcut: 'Invalid shortcut, please enter a single character.',
            updatedShortcut: 'Shortcut updated to "{key}".'
        },
        'en-GB': {
            watchLater: 'Watch Later',
            settings: 'Settings',
            promptShortcut: 'Enter a new shortcut key (current is "{key}")',
            invalidShortcut: 'Invalid shortcut, please enter a single character.',
            updatedShortcut: 'Shortcut updated to "{key}".'
        },
        zh: {
            watchLater: '稍后观看',
            settings: '设置',
            promptShortcut: '请输入一个新的快捷键(当前快捷键为 "{key}")',
            invalidShortcut: '无效的快捷键,请输入单个字符。',
            updatedShortcut: '快捷键已更新为 "{key}"'
        },
        'zh-TW': {
            watchLater: '稍後觀看',
            settings: '設定',
            promptShortcut: '請輸入一個新的快捷鍵(當前快捷鍵為 "{key}")',
            invalidShortcut: '無效的快捷鍵,請輸入單個字符。',
            updatedShortcut: '快捷鍵已更新為 "{key}"'
        },
        'zh-HK': {
            watchLater: '稍後觀看',
            settings: '設定',
            promptShortcut: '請輸入一個新的快捷鍵(當前快捷鍵為 "{key}")',
            invalidShortcut: '無效的快捷鍵,請輸入單個字符。',
            updatedShortcut: '快捷鍵已更新為 "{key}"'
        },
         th: {
            watchLater: 'ดูภายหลัง',
            settings: 'การตั้งค่า',
            promptShortcut: 'กรุณาป้อนปุ่มลัดใหม่ (ปัจจุบันคือ "{key}")',
            invalidShortcut: 'ปุ่มลัดไม่ถูกต้อง โปรดป้อนตัวอักษรตัวเดียว',
            updatedShortcut: 'อัปเดตปุ่มลัดเป็น "{key}".'
        },
        ur: {
            watchLater: 'بعد میں دیکھیں',
            settings: 'ترتیبات',
            promptShortcut: 'نیا شارٹ کٹ کلید درج کریں (موجودہ "{key}" ہے)',
            invalidShortcut: 'غلط شارٹ کٹ، براہ کرم ایک حرف درج کریں۔',
            updatedShortcut: 'شارٹ کٹ کو "{key}" میں اپ ڈیٹ کیا گیا ہے۔'
        },
        fa: {
            watchLater: 'تماشا بعداً',
            settings: 'تنظیمات',
            promptShortcut: 'کلید میانبر جدید را وارد کنید (کلید فعلی "{key}" است)',
            invalidShortcut: 'کلید میانبر نامعتبر است، لطفاً یک حرف وارد کنید.',
            updatedShortcut: 'میانبر به "{key}" به روز شد.'
        },
        ja: {
            watchLater: '後で見る',
            settings: '設定',
            promptShortcut: '新しいショートカットキーを入力してください(現在のキーは "{key}")',
            invalidShortcut: '無効なショートカットです。1文字を入力してください。',
            updatedShortcut: 'ショートカットが "{key}" に更新されました。'
        },
        ko: {
            watchLater: '나중에 보기',
            settings: '설정',
            promptShortcut: '새로운 단축키를 입력하세요 (현재 단축키: "{key}")',
            invalidShortcut: '잘못된 단축키입니다. 한 글자를 입력해주세요.',
            updatedShortcut: '단축키가 "{key}"로 업데이트되었습니다.'
        },
        ru: {
            watchLater: 'Смотреть позже',
            settings: 'Настройки',
            promptShortcut: 'Введите новую клавишу быстрого доступа (текущая: "{key}")',
            invalidShortcut: 'Недопустимая клавиша быстрого доступа, введите один символ.',
            updatedShortcut: 'Клавиша быстрого доступа обновлена на "{key}".'
        },
        hi: {
            watchLater: 'बाद में देखें',
            settings: 'सेटिंग्स',
            promptShortcut: 'नया शॉर्टकट कुंजी दर्ज करें (वर्तमान "{key}")',
            invalidShortcut: 'अमान्य शॉर्टकट, कृपया एक अक्षर दर्ज करें।',
            updatedShortcut: 'शॉर्टकट "{key}" पर अपडेट किया गया है।'
        },
        es: {
            watchLater: 'Ver más tarde',
            settings: 'Configuraciones',
            promptShortcut: 'Ingrese una nueva tecla de acceso rápido (actualmente "{key}")',
            invalidShortcut: 'Acceso rápido no válido, ingrese un solo carácter.',
            updatedShortcut: 'Tecla de acceso rápido actualizada a "{key}".'
        },
        pt: {
            watchLater: 'Assistir mais tarde',
            settings: 'Configurações',
            promptShortcut: 'Insira uma nova tecla de atalho (atual: "{key}")',
            invalidShortcut: 'Tecla de atalho inválida, insira um único caractere.',
            updatedShortcut: 'Tecla de atalho atualizada para "{key}".'
        }
    };

    // 获取用户语言,处理不同地区的语言代码
    let language = (navigator.language || navigator.userLanguage).toLowerCase();
    if (language.includes('-')) {
        const [baseLang] = language.split('-');
        language = i18n[baseLang] ? baseLang : language;
    }
    const translations = i18n[language] || i18n['en'];  // 默认使用英文

    let isCustomButtonClicked = false;
    let isCoolingDown = false;
    let isDragging = false;
    let offsetY = 0;
    let customShortcutKey = 'w';  // 默认快捷键是"W"
    let isPreloading = false;

    // 添加CSS来隐藏.opened元素,隐藏弹出窗口
    const style = document.createElement('style');
    style.textContent = `
        .opened {
            display: none !important;
        }
    `;
    document.head.appendChild(style);

    // 隐藏tp-yt-paper-dialog元素的函数
    function hideDialogElements() {
        const dialogElements = document.querySelectorAll('tp-yt-paper-dialog.ytd-popup-container.style-scope > .ytd-popup-container.style-scope');
        dialogElements.forEach(element => {
            element.style.display = 'none'; // 直接设置display为none
        });
        console.log("隐藏了tp-yt-paper-dialog元素");
    }

    // 显示tp-yt-paper-dialog元素的函数
    function showDialogElements() {
        const dialogElements = document.querySelectorAll('tp-yt-paper-dialog.ytd-popup-container.style-scope > .ytd-popup-container.style-scope');
        dialogElements.forEach(element => {
            element.style.display = ''; // 恢复默认显示
        });
        console.log("显示了tp-yt-paper-dialog元素");
    }

    // 创建悬浮按钮
    function createFloatingButton() {
        if (window.location.pathname !== '/watch') return;  // 仅在 /watch 页面时创建按钮

        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.top = '50%';
        container.style.right = '0';
        container.style.zIndex = 9999;
        container.style.display = 'flex';
        container.style.flexDirection = 'column';
        container.style.transform = 'translateY(-50%)';
        container.style.transition = 'right 0.3s';

        // 稍后观看按钮
        const watchLaterButton = document.createElement('button');
        watchLaterButton.innerText = translations.watchLater;
        watchLaterButton.style.backgroundColor = '#FF0000';
        watchLaterButton.style.color = '#FFFFFF';
        watchLaterButton.style.padding = '10px';
        watchLaterButton.style.border = 'none';
        watchLaterButton.style.borderRadius = '5px';
        watchLaterButton.style.cursor = 'pointer';
        watchLaterButton.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
        watchLaterButton.style.width = '80px';
        watchLaterButton.style.height = '40px';
        watchLaterButton.style.position = 'relative';
        watchLaterButton.style.right = '-60px';  // 半隐藏
        watchLaterButton.style.transition = 'right 0.3s';
        watchLaterButton.setAttribute('id', 'customWatchLaterButton');

        watchLaterButton.onmouseover = function() {
            watchLaterButton.style.right = '0';
            settingsButton.style.right = '0'; // 显示设置按钮
        };

        watchLaterButton.onmouseout = function() {
            if (!isDragging) {
                watchLaterButton.style.right = '-60px';  // 回到半隐藏状态
                settingsButton.style.right = '-80px';  // 隐藏设置按钮
            }
        };

        // 分离拖动逻辑
        function startDragging(e) {
            isDragging = true;
            offsetY = e.clientY - container.getBoundingClientRect().top;
            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', stopDragging);
        }

        function drag(e) {
            const containerHeight = container.offsetHeight;
            const windowHeight = window.innerHeight;
            let newY = e.clientY - offsetY;

            // 限制拖动范围
            newY = Math.max(containerHeight / 2, Math.min(newY, windowHeight - containerHeight / 2));

            container.style.top = `${newY}px`;
        }

        function stopDragging() {
            isDragging = false;
            document.removeEventListener('mousemove', drag);
            document.removeEventListener('mouseup', stopDragging);
        }

        // 添加拖动事件监听器
        container.addEventListener('mousedown', startDragging);

        // 独立的点击事件
        watchLaterButton.onclick = function(e) {
            // 防止拖动时触发点击事件
            if (isDragging) {
                e.stopPropagation();
                return;
            }

            if (isCoolingDown) {
                console.log('按钮正在冷却,请稍后再试。');
                return;
            }

            isCustomButtonClicked = true;
            isCoolingDown = true;
            watchLaterButton.style.backgroundColor = '#AAAAAA';

            setTimeout(() => {
                isCoolingDown = false;
                watchLaterButton.style.backgroundColor = '#FF0000';
            }, 3500);

            const videoId = getVideoId();
            if (videoId) {
                preloadWatchLater();
            } else {
                console.log('无法获取视频ID');
            }
        };

        // 设置按钮
        const settingsButton = document.createElement('button');
        settingsButton.innerText = translations.settings;
        settingsButton.style.backgroundColor = '#000000';
        settingsButton.style.color = '#FFFFFF';
        settingsButton.style.padding = '8px';
        settingsButton.style.border = 'none';
        settingsButton.style.borderRadius = '5px';
        settingsButton.style.cursor = 'pointer';
        settingsButton.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
        settingsButton.style.width = '80px';
        settingsButton.style.height = '35px';
        settingsButton.style.position = 'relative';
        settingsButton.style.right = '-80px';  // 完全隐藏
        settingsButton.style.transition = 'right 0.3s';

        settingsButton.onmouseover = function() {
            settingsButton.style.right = '0';  // 不会马上缩回
        };

        settingsButton.onmouseout = function() {
            watchLaterButton.onmouseout();  // 跟随主按钮的状态
        };

        settingsButton.onclick = function() {
            const newShortcut = prompt(translations.promptShortcut.replace('{key}', customShortcutKey.toUpperCase()));
            if (newShortcut && newShortcut.length === 1) {
                customShortcutKey = newShortcut.toLowerCase();
                alert(translations.updatedShortcut.replace('{key}', customShortcutKey.toUpperCase()));
            } else {
                alert(translations.invalidShortcut);
            }
        };

        container.appendChild(watchLaterButton);
        container.appendChild(settingsButton);
        document.body.appendChild(container);
    }

    // 保留其他逻辑
    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v');
    }

    // 在预加载完成后移除 'preloading' 类并隐藏对话框
    function finishPreloadAndHideDialog() {
        isPreloading = false;
        document.body.classList.remove('preloading');
        hideDialogElements(); // 立即隐藏对话框
    }

    // 预加载稍后观看列表
    function preloadWatchLater() {
        isPreloading = true;
        document.body.classList.add('preloading');

        const saveButton = document.querySelector('button[aria-label*="保存"], button[aria-label*="Save"]');
        if (saveButton) {
            saveButton.click();

            const observer = new MutationObserver(() => {
                const watchLaterButton = document.querySelector('yt-formatted-string[title="稍后观看"], yt-formatted-string[title="Watch later"]');
                if (watchLaterButton) {
                    observer.disconnect();
                    watchLaterButton.click();
                    console.log('视频已添加到稍后观看');

                    // 完成预加载并隐藏对话框
                    finishPreloadAndHideDialog();
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            console.log('未找到保存按钮');
            finishPreloadAndHideDialog(); // 如果没有找到保存按钮,也完成预加载
        }
    }

    function setupOriginalSaveButtonListener() {
        const observer = new MutationObserver(() => {
            const originalSaveButton = document.querySelector('ytd-menu-renderer');
            if (originalSaveButton) {
                originalSaveButton.addEventListener('click', () => {
                    isCustomButtonClicked = false;
                    showDialogElements();
                });
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // 监听键盘事件
    document.addEventListener('keydown', (event) => {
        if (event.key.toLowerCase() === customShortcutKey) {
            const button = document.getElementById('customWatchLaterButton');
            if (button) {
                button.click();
            }
        }
    });

    // 隐藏按钮在主页、迷你播放器和全屏时
    function checkVisibility() {
        const isHomepage = window.location.href === 'https://www.youtube.com/';
        const isMiniPlayer = document.querySelector('.mini-player');
        const isFullscreen = document.fullscreenElement !== null; // 判断是否全屏

        const button = document.getElementById('customWatchLaterButton');
        const settingsButton = document.querySelector('button#customWatchLaterButton + button'); // 获取设置按钮

        if (button) {
            button.style.display = (isHomepage || isMiniPlayer || isFullscreen) ? 'none' : 'block';
            settingsButton.style.display = (isHomepage || isMiniPlayer || isFullscreen) ? 'none' : 'block';
        }
    }

    // 页面加载完成后创建按钮
    function onPreloadFinish() {
        createFloatingButton();
        setupOriginalSaveButtonListener();
        checkVisibility();
    }

    // 监听预加载完成事件
    const preloadObserver = new MutationObserver(() => {
        if (!isPreloading) {
            preloadObserver.disconnect();
            onPreloadFinish();
        }
    });

preloadObserver.observe(document.body, { attributes: true, childList: true, subtree: true });


    // 监控URL变化
    const observer = new MutationObserver(() => {
        checkVisibility();
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址