在后台将 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 }); })();