Universal YouTube progress tracker with playlist support
当前为
// ==UserScript==
// @name YouTube Universal Progress Tracker
// @namespace http://tampermonkey.net/
// @version 3.0
// @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',
right: '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);
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址