您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在后台将 YouTube 视频添加到"稍后观看"列表,按钮贴边半隐藏,可上下拖动,添加设置按钮和自定义快捷键功能
// ==UserScript== // @name YouTube(油管)添加到稍后观看加强版 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在后台将 YouTube 视频添加到"稍后观看"列表,按钮贴边半隐藏,可上下拖动,添加设置按钮和自定义快捷键功能 // @author 特比欧炸 // @match https://www.youtube.com/* // @grant none // @charset UTF-8 // @license 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 una 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 dragStartTime = 0; let dragThreshold = 200; // 拖动阈值时间(ms) let panelOpen = false; let offsetY = 0; let watchLaterKey = 'w'; // 默认快捷键是"W" let playlistKeys = {}; // 存储每个收藏夹对应的快捷键 let isPreloading = false; let buttonContainer = null; // 存储按钮容器引用 let savedPosition = null; // 保存的位置信息 // 添加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 }); } // 显示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 = ''; // 恢复默认显示 }); } // 保存位置到localStorage function savePosition(top) { try { localStorage.setItem('youtube_button_position', top.toString()); } catch (e) { console.error('保存位置失败:', e); } } // 从localStorage获取位置 function getSavedPosition() { try { const saved = localStorage.getItem('youtube_button_position'); return saved ? parseInt(saved) : null; } catch (e) { console.error('获取保存位置失败:', e); return null; } } // 创建悬浮按钮 function createFloatingButton() { if (window.location.pathname !== '/watch') return; // 仅在 /watch 页面时创建按钮 // 如果已存在按钮,先移除 if (buttonContainer) { buttonContainer.remove(); } const container = document.createElement('div'); container.style.position = 'fixed'; container.style.zIndex = 9999; container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.transition = 'right 0.3s'; container.style.right = '0'; // 设置初始位置 const savedTop = getSavedPosition(); if (savedTop !== null) { container.style.top = `${savedTop}px`; } else { container.style.top = '50%'; container.style.transform = 'translateY(-50%)'; } // 稍后观看按钮 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.onmouseenter = function() { if (!isDragging) { watchLaterButton.style.right = '0'; settingsButton.style.right = '0'; } }; watchLaterButton.onmouseleave = function() { if (!isDragging && !panelOpen) { watchLaterButton.style.right = '-60px'; settingsButton.style.right = '-80px'; } }; // 设置按钮 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.onmouseenter = function() { if (!isDragging) { settingsButton.style.right = '0'; watchLaterButton.style.right = '0'; } }; settingsButton.onmouseleave = function() { if (!isDragging && !panelOpen) { settingsButton.style.right = '-80px'; watchLaterButton.style.right = '-60px'; } }; // 拖动相关变量 let dragStartX = 0; let dragStartY = 0; let hasMovedSignificantly = false; // 拖动逻辑 function startDragging(e) { dragStartTime = Date.now(); dragStartX = e.clientX; dragStartY = e.clientY; hasMovedSignificantly = false; isDragging = true; // 移除transform以使用像素坐标 if (container.style.transform) { const rect = container.getBoundingClientRect(); container.style.top = `${rect.top}px`; container.style.transform = ''; } offsetY = e.clientY - container.getBoundingClientRect().top; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDragging); // 防止文字选择 e.preventDefault(); } function drag(e) { const deltaX = Math.abs(e.clientX - dragStartX); const deltaY = Math.abs(e.clientY - dragStartY); // 如果移动距离超过阈值,标记为显著移动 if (deltaX > 5 || deltaY > 5) { hasMovedSignificantly = true; } if (hasMovedSignificantly) { const containerHeight = container.offsetHeight; const windowHeight = window.innerHeight; let newY = e.clientY - offsetY; // 限制拖动范围 newY = Math.max(0, Math.min(newY, windowHeight - containerHeight)); container.style.top = `${newY}px`; // 如果面板打开,也跟着移动 if (panelOpen && settingsPanel) { settingsPanel.style.top = `${newY}px`; } } } function stopDragging(e) { document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDragging); const dragDuration = Date.now() - dragStartTime; // 如果拖动时间短且移动距离小,视为点击 if (dragDuration < dragThreshold && !hasMovedSignificantly) { // 不阻止点击事件 isDragging = false; return; } // 保存位置 if (hasMovedSignificantly) { const rect = container.getBoundingClientRect(); savePosition(rect.top); } // 延迟重置拖动状态,防止立即触发点击 setTimeout(() => { isDragging = false; }, 50); } // 为容器添加拖动事件 container.addEventListener('mousedown', startDragging); // 稍后观看按钮点击事件 watchLaterButton.addEventListener('click', function(e) { // 如果正在拖动或刚完成拖动,不处理点击 if (isDragging || hasMovedSignificantly) { e.preventDefault(); e.stopPropagation(); return; } if (isCoolingDown) { return; } isCustomButtonClicked = true; isCoolingDown = true; watchLaterButton.style.backgroundColor = '#AAAAAA'; setTimeout(() => { isCoolingDown = false; watchLaterButton.style.backgroundColor = '#FF0000'; }, 3500); const videoId = getVideoId(); if (videoId) { addWatchLater(); } }); // 设置按钮点击事件 settingsButton.addEventListener('click', function(e) { if (isDragging || hasMovedSignificantly) { e.preventDefault(); e.stopPropagation(); return; } toggleSettingsPanel(); }); container.appendChild(watchLaterButton); container.appendChild(settingsButton); document.body.appendChild(container); // 保存容器引用 buttonContainer = container; // 创建并附加设置面板 const settingsPanel = document.createElement('div'); settingsPanel.id = 'customSettingsPanel'; settingsPanel.style.position = 'fixed'; settingsPanel.style.backgroundColor = '#FFFFFF'; settingsPanel.style.border = '1px solid #888'; settingsPanel.style.borderRadius = '5px'; settingsPanel.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)'; settingsPanel.style.zIndex = 10000; settingsPanel.style.padding = '15px'; settingsPanel.style.width = '320px'; settingsPanel.style.maxHeight = '400px'; settingsPanel.style.overflowY = 'auto'; settingsPanel.style.display = 'none'; settingsPanel.style.fontSize = '14px'; // 初始位置,与按钮对齐 const rect = container.getBoundingClientRect(); settingsPanel.style.top = `${rect.top}px`; settingsPanel.style.right = '90px'; // 面板标题和关闭按钮 const panelHeader = document.createElement('div'); panelHeader.style.display = 'flex'; panelHeader.style.justifyContent = 'space-between'; panelHeader.style.alignItems = 'center'; panelHeader.style.marginBottom = '15px'; panelHeader.style.fontWeight = 'bold'; panelHeader.style.fontSize = '16px'; panelHeader.innerText = '快捷键设置'; const closeBtn = document.createElement('button'); closeBtn.innerText = '×'; closeBtn.style.background = 'none'; closeBtn.style.border = 'none'; closeBtn.style.fontSize = '20px'; closeBtn.style.cursor = 'pointer'; closeBtn.style.padding = '0'; closeBtn.style.width = '20px'; closeBtn.style.height = '20px'; closeBtn.onclick = function() { settingsPanel.style.display = 'none'; panelOpen = false; if (!isDragging) { watchLaterButton.style.right = '-60px'; settingsButton.style.right = '-80px'; } }; panelHeader.appendChild(closeBtn); settingsPanel.appendChild(panelHeader); // 稍后观看快捷键设置区域 const watchLaterSection = document.createElement('div'); watchLaterSection.style.marginBottom = '20px'; watchLaterSection.style.padding = '10px'; watchLaterSection.style.border = '1px solid #ddd'; watchLaterSection.style.borderRadius = '5px'; watchLaterSection.style.backgroundColor = '#f9f9f9'; const watchLaterTitle = document.createElement('div'); watchLaterTitle.innerText = '稍后观看快捷键'; watchLaterTitle.style.fontWeight = 'bold'; watchLaterTitle.style.marginBottom = '10px'; watchLaterSection.appendChild(watchLaterTitle); const watchLaterKeyDiv = document.createElement('div'); watchLaterKeyDiv.style.display = 'flex'; watchLaterKeyDiv.style.alignItems = 'center'; watchLaterKeyDiv.style.gap = '10px'; const watchLaterKeyInput = document.createElement('input'); watchLaterKeyInput.type = 'text'; watchLaterKeyInput.style.width = '150px'; watchLaterKeyInput.style.padding = '5px'; watchLaterKeyInput.style.border = '1px solid #ccc'; watchLaterKeyInput.style.borderRadius = '3px'; watchLaterKeyInput.placeholder = '按下组合键...'; watchLaterKeyInput.readOnly = true; watchLaterKeyInput.value = formatShortcut(getStoredWatchLaterKey()); const watchLaterResetBtn = document.createElement('button'); watchLaterResetBtn.innerText = '重置'; watchLaterResetBtn.style.padding = '5px 10px'; watchLaterResetBtn.style.border = '1px solid #ccc'; watchLaterResetBtn.style.borderRadius = '3px'; watchLaterResetBtn.style.cursor = 'pointer'; watchLaterResetBtn.onclick = function() { watchLaterKey = { ctrl: false, alt: false, shift: false, key: 'w' }; watchLaterKeyInput.value = formatShortcut(watchLaterKey); saveWatchLaterKey(); }; watchLaterKeyDiv.appendChild(watchLaterKeyInput); watchLaterKeyDiv.appendChild(watchLaterResetBtn); watchLaterSection.appendChild(watchLaterKeyDiv); // 为稍后观看快捷键输入框添加键盘事件监听 watchLaterKeyInput.addEventListener('keydown', function(e) { e.preventDefault(); if (e.key === 'Escape') { watchLaterKeyInput.blur(); return; } if (e.key === 'Tab' || e.key === 'Enter') return; const newKey = { ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey, key: e.key.toLowerCase() }; // 至少需要一个修饰键 if (!newKey.ctrl && !newKey.alt && !newKey.shift) { alert('请使用组合键(Ctrl/Alt/Shift + 字母/数字)'); return; } watchLaterKey = newKey; watchLaterKeyInput.value = formatShortcut(watchLaterKey); saveWatchLaterKey(); }); settingsPanel.appendChild(watchLaterSection); // 收藏夹快捷键设置区域 const playlistSection = document.createElement('div'); playlistSection.style.marginBottom = '15px'; const playlistTitle = document.createElement('div'); playlistTitle.innerText = '收藏夹快捷键'; playlistTitle.style.fontWeight = 'bold'; playlistTitle.style.marginBottom = '10px'; playlistSection.appendChild(playlistTitle); // 刷新收藏夹按钮 const refreshBtn = document.createElement('button'); refreshBtn.innerText = '刷新收藏夹列表'; refreshBtn.style.padding = '5px 10px'; refreshBtn.style.border = '1px solid #ccc'; refreshBtn.style.borderRadius = '3px'; refreshBtn.style.cursor = 'pointer'; refreshBtn.style.marginBottom = '10px'; refreshBtn.onclick = function() { updatePlaylistList(); }; playlistSection.appendChild(refreshBtn); // 列表容器 const panelList = document.createElement('div'); panelList.style.maxHeight = '200px'; panelList.style.overflowY = 'auto'; panelList.style.border = '1px solid #ddd'; panelList.style.borderRadius = '5px'; panelList.style.padding = '5px'; playlistSection.appendChild(panelList); settingsPanel.appendChild(playlistSection); // 添加至所选按钮 const addSelectedBtn = document.createElement('button'); addSelectedBtn.innerText = '添加到选中收藏夹'; addSelectedBtn.style.padding = '8px'; addSelectedBtn.style.width = '100%'; addSelectedBtn.style.border = 'none'; addSelectedBtn.style.backgroundColor = '#FF0000'; addSelectedBtn.style.color = '#FFFFFF'; addSelectedBtn.style.borderRadius = '5px'; addSelectedBtn.style.cursor = 'pointer'; addSelectedBtn.onclick = function() { addToSelectedPlaylists(); }; settingsPanel.appendChild(addSelectedBtn); document.body.appendChild(settingsPanel); // 切换设置面板显示隐藏 function toggleSettingsPanel() { if (settingsPanel.style.display === 'none') { updatePlaylistList(); settingsPanel.style.display = 'block'; panelOpen = true; // Position panel near container const rect = container.getBoundingClientRect(); settingsPanel.style.top = `${rect.top}px`; settingsPanel.style.right = '90px'; // 保持按钮可见 watchLaterButton.style.right = '0'; settingsButton.style.right = '0'; } else { settingsPanel.style.display = 'none'; panelOpen = false; // 隐藏按钮 if (!isDragging) { watchLaterButton.style.right = '-60px'; settingsButton.style.right = '-80px'; } } } // 更新收藏夹列表 function updatePlaylistList() { // 清空旧列表 panelList.replaceChildren(); // 获取存储的收藏夹数据 const storedPlaylists = getStoredPlaylists(); // 获取当前页面的收藏夹 const currentPlaylistElems = document.querySelectorAll('#playlists yt-formatted-string[title]'); const currentPlaylists = new Set(); currentPlaylistElems.forEach(elem => { const name = elem.textContent || elem.getAttribute('title'); if (name && !name.match(/稍后观看|Watch later|历史记录|History/)) { currentPlaylists.add(name); } }); // 合并存储的和当前的收藏夹 const allPlaylists = new Set([...Object.keys(storedPlaylists), ...currentPlaylists]); // 为每个收藏夹创建列表项 Array.from(allPlaylists).sort().forEach(name => { const itemDiv = document.createElement('div'); itemDiv.style.display = 'flex'; itemDiv.style.alignItems = 'center'; itemDiv.style.marginBottom = '8px'; itemDiv.style.padding = '5px'; itemDiv.style.border = '1px solid #eee'; itemDiv.style.borderRadius = '3px'; itemDiv.style.backgroundColor = '#fff'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.marginRight = '8px'; itemDiv.appendChild(checkbox); const label = document.createElement('span'); label.className = 'playlistName'; label.innerText = name; label.style.flexGrow = '1'; label.style.fontSize = '12px'; // 如果不在当前页面,显示为灰色 if (!currentPlaylists.has(name)) { label.style.color = '#888'; label.title = '此收藏夹不在当前页面'; } itemDiv.appendChild(label); const keyInput = document.createElement('input'); keyInput.type = 'text'; keyInput.className = 'playlistKey'; keyInput.style.width = '100px'; keyInput.style.padding = '2px 5px'; keyInput.style.border = '1px solid #ccc'; keyInput.style.borderRadius = '3px'; keyInput.style.fontSize = '12px'; keyInput.placeholder = '组合键...'; keyInput.readOnly = true; // 如果已有设置的快捷键,显示 if (storedPlaylists[name]) { keyInput.value = formatShortcut(storedPlaylists[name]); } // 为快捷键输入框添加键盘事件监听 keyInput.addEventListener('keydown', function(e) { e.preventDefault(); if (e.key === 'Escape') { keyInput.blur(); return; } if (e.key === 'Tab' || e.key === 'Enter') return; const newKey = { ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey, key: e.key.toLowerCase() }; // 至少需要一个修饰键 if (!newKey.ctrl && !newKey.alt && !newKey.shift) { alert('请使用组合键(Ctrl/Alt/Shift + 字母/数字)'); return; } // 检查是否与稍后观看快捷键冲突 if (isShortcutEqual(newKey, watchLaterKey)) { alert('快捷键与稍后观看快捷键冲突!'); return; } // 检查是否与其他收藏夹快捷键冲突 let conflict = false; for (const [pl, key] of Object.entries(storedPlaylists)) { if (pl !== name && isShortcutEqual(key, newKey)) { conflict = true; break; } } if (conflict) { alert('快捷键与其他收藏夹冲突!'); return; } // 保存快捷键 const playlists = getStoredPlaylists(); playlists[name] = newKey; savePlaylists(playlists); keyInput.value = formatShortcut(newKey); playlistKeys[name] = newKey; }); // 重置按钮 const resetBtn = document.createElement('button'); resetBtn.innerText = '×'; resetBtn.style.marginLeft = '5px'; resetBtn.style.padding = '2px 5px'; resetBtn.style.border = '1px solid #ccc'; resetBtn.style.borderRadius = '3px'; resetBtn.style.cursor = 'pointer'; resetBtn.style.fontSize = '12px'; resetBtn.onclick = function() { const playlists = getStoredPlaylists(); delete playlists[name]; delete playlistKeys[name]; savePlaylists(playlists); keyInput.value = ''; }; itemDiv.appendChild(keyInput); itemDiv.appendChild(resetBtn); panelList.appendChild(itemDiv); }); } // 将当前视频添加到所选收藏夹 function addToSelectedPlaylists() { const checkedPlaylists = []; panelList.querySelectorAll('div').forEach(itemDiv => { const chk = itemDiv.querySelector('input[type="checkbox"]'); const nameSpan = itemDiv.querySelector('.playlistName'); if (chk.checked && nameSpan) { checkedPlaylists.push(nameSpan.innerText); } }); if (checkedPlaylists.length === 0) { alert('未选择任何收藏夹'); return; } // 关闭面板 settingsPanel.style.display = 'none'; panelOpen = false; if (!isDragging) { watchLaterButton.style.right = '-60px'; settingsButton.style.right = '-80px'; } // 点击保存按钮,预加载对话框 const saveButton = document.querySelector('button[aria-label*="保存"], button[aria-label*="Save"]'); if (saveButton) { isPreloading = true; document.body.classList.add('preloading'); saveButton.click(); // 等待对话框加载后点击每个选中的收藏夹 setTimeout(() => { checkedPlaylists.forEach(name => { const item = Array.from(document.querySelectorAll('yt-formatted-string')).find(el => el.textContent === name); if (item) { item.click(); } }); finishPreloadAndHideDialog(); }, 500); } } } // 数据持久化相关函数 function getStoredPlaylists() { try { const stored = localStorage.getItem('youtube_playlists_shortcuts'); return stored ? JSON.parse(stored) : {}; } catch (e) { console.error('获取收藏夹数据失败:', e); return {}; } } function savePlaylists(playlists) { try { localStorage.setItem('youtube_playlists_shortcuts', JSON.stringify(playlists)); } catch (e) { console.error('保存收藏夹数据失败:', e); } } function getStoredWatchLaterKey() { try { const stored = localStorage.getItem('youtube_watch_later_key'); return stored ? JSON.parse(stored) : { ctrl: false, alt: false, shift: false, key: 'w' }; } catch (e) { console.error('获取稍后观看快捷键失败:', e); return { ctrl: false, alt: false, shift: false, key: 'w' }; } } function saveWatchLaterKey() { try { localStorage.setItem('youtube_watch_later_key', JSON.stringify(watchLaterKey)); } catch (e) { console.error('保存稍后观看快捷键失败:', e); } } // 快捷键相关工具函数 function formatShortcut(shortcut) { if (!shortcut) return ''; const parts = []; if (shortcut.ctrl) parts.push('Ctrl'); if (shortcut.alt) parts.push('Alt'); if (shortcut.shift) parts.push('Shift'); if (shortcut.key) parts.push(shortcut.key.toUpperCase()); return parts.join(' + '); } function isShortcutEqual(key1, key2) { if (!key1 || !key2) return false; return key1.ctrl === key2.ctrl && key1.alt === key2.alt && key1.shift === key2.shift && key1.key === key2.key; } // 初始化快捷键数据 watchLaterKey = getStoredWatchLaterKey(); playlistKeys = getStoredPlaylists(); // 保留其他逻辑 function getVideoId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('v'); } // 添加视频到稍后观看 function addWatchLater() { const saveButton = document.querySelector('button[aria-label*="保存"], button[aria-label*="Save"]'); if (saveButton) { isPreloading = true; document.body.classList.add('preloading'); 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(); } } // 在预加载完成后移除 'preloading' 类并隐藏对话框 function finishPreloadAndHideDialog() { isPreloading = false; document.body.classList.remove('preloading'); hideDialogElements(); } 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.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { return; } const currentKey = { ctrl: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, key: event.key.toLowerCase() }; // 检查稍后观看快捷键 if (isShortcutEqual(currentKey, watchLaterKey)) { event.preventDefault(); const button = document.getElementById('customWatchLaterButton'); if (button) { button.click(); } return; } // 检查收藏夹快捷键 for (const [playlistName, shortcut] of Object.entries(playlistKeys)) { if (isShortcutEqual(currentKey, shortcut)) { event.preventDefault(); const saveButton = document.querySelector('button[aria-label*="保存"], button[aria-label*="Save"]'); if (saveButton) { isPreloading = true; document.body.classList.add('preloading'); saveButton.click(); const observer = new MutationObserver(() => { const item = Array.from(document.querySelectorAll('yt-formatted-string')).find(el => el.textContent === playlistName); if (item) { observer.disconnect(); item.click(); console.log(`视频已添加到收藏夹: ${playlistName}`); finishPreloadAndHideDialog(); } }); observer.observe(document.body, { childList: true, subtree: true }); } return; } } }); // 隐藏按钮在主页、迷你播放器和全屏时 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() { // 延迟创建按钮,等待页面完全加载 setTimeout(() => { createFloatingButton(); setupOriginalSaveButtonListener(); checkVisibility(); }, 500); } // 监听预加载完成事件 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 }); })();