您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Universal YouTube progress tracker with playlist support
当前为
// ==UserScript== // @name YouTube Universal Progress Tracker // @namespace http://tampermonkey.net/ // @version 3.1 // @description Universal YouTube progress tracker with playlist support // @author ikigaiDH // @match https://www.youtube.com/* // @grant none // @license GPL-3.0-only // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'yt_watch_history'; let watchHistory = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); // Universal video ID extractor const getVideoId = (element) => { try { const link = element.closest('a') || element.querySelector('a'); if (!link) return null; const url = new URL(link.href); // Handle different URL formats return url.searchParams.get('v') || url.pathname.split('/watch/')[1]?.split('?')[0] || url.pathname.split('/')[2]; } catch { return null; } }; // Comprehensive thumbnail selector const getThumbnails = () => { const selectors = [ 'ytd-thumbnail', // Default thumbnails 'ytd-playlist-video-renderer #thumbnail', // Playlist items 'ytd-compact-video-renderer #thumbnail', // Sidebar items 'ytd-rich-item-renderer #thumbnail' // Homepage rich items ]; return document.querySelectorAll(selectors.join(',')); }; // Progress indicator creation const createIndicator = () => { const indicator = document.createElement('div'); Object.assign(indicator.style, { position: 'absolute', bottom: '4px', leftt: '4px', backgroundColor: '#cc0000', color: 'white', padding: '2px 6px', borderRadius: '2px', fontSize: '12px', fontWeight: 'bold', zIndex: '1000', fontFamily: 'Roboto, Arial, sans-serif', textTransform: 'uppercase' }); indicator.className = 'yt-progress-indicator'; return indicator; }; // Main update function const updateAllThumbnails = () => { getThumbnails().forEach(thumbnail => { const videoId = getVideoId(thumbnail); if (!videoId) return; const percentage = watchHistory[videoId] || 0; let indicator = thumbnail.querySelector('.yt-progress-indicator'); if (percentage > 0) { if (!indicator) { indicator = createIndicator(); thumbnail.style.position = 'relative'; thumbnail.appendChild(indicator); } indicator.textContent = percentage >= 100 ? '>100%' : `${Math.round(percentage)}%`; indicator.style.display = 'block'; } else if (indicator) { indicator.style.display = 'none'; } }); }; // Video tracking with debouncing let currentVideoId = null; const trackVideo = () => { const video = document.querySelector('video'); if (!video) return; const newVideoId = new URLSearchParams(window.location.search).get('v'); if (newVideoId === currentVideoId) return; currentVideoId = newVideoId; const saveProgress = () => { if (video.duration > 0) { const percentage = (video.currentTime / video.duration) * 100; if (percentage > (watchHistory[currentVideoId] || 0)) { watchHistory[currentVideoId] = percentage; localStorage.setItem(STORAGE_KEY, JSON.stringify(watchHistory)); updateAllThumbnails(); } } }; video.addEventListener('timeupdate', saveProgress); }; // Enhanced observation const observer = new MutationObserver(mutations => { if (mutations.some(m => m.addedNodes.length)) { updateAllThumbnails(); trackVideo(); } }); // Initialization window.addEventListener('load', () => { observer.observe(document.body, { childList: true, subtree: true, attributes: false }); updateAllThumbnails(); trackVideo(); }); // Handle YouTube navigation document.addEventListener('yt-navigate-finish', updateAllThumbnails); document.addEventListener('yt-page-data-updated', updateAllThumbnails); })();