🏠 Home 

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 });
})();