您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show full upload dates in DD/MM/YYYY HH:MMam/pm format with smart queuing, retry, dynamic updates, and UI toggle
// ==UserScript== // @name Full Date format for Youtube (Enhanced) // @version 2.0.9 // @description Show full upload dates in DD/MM/YYYY HH:MMam/pm format with smart queuing, retry, dynamic updates, and UI toggle // @author Ignacio Albiol // @namespace https://gf.qytechs.cn/en/users/1304094 // @match https://www.youtube.com/* // @iconURL https://seekvectors.com/files/download/youtube-icon-yellow-01.jpg // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const uploadDateCache = new Map(); const processedVideos = new Map(); const videoQueue = []; const apiRequestCache = new Map(); let isEnabled = true; const RETRY_LIMIT = 3; const RETRY_DELAY = 1000; const MAX_CONCURRENT = 3; let currentRequests = 0; const DEBUG = false; const EXPIRY_MS = 10 * 60 * 1000; function debug(...args) { if (DEBUG) console.log('[YT Date Format]', ...args); } function formatDate(iso) { const date = new Date(iso); if (isNaN(date)) return ''; const d = String(date.getDate()).padStart(2, '0'); const m = String(date.getMonth() + 1).padStart(2, '0'); const y = date.getFullYear(); let h = date.getHours(); const mm = String(date.getMinutes()).padStart(2, '0'); const ampm = h >= 12 ? 'pm' : 'am'; h = h % 12 || 12; return `${d}/${m}/${y} ${h}:${mm}${ampm}`; } function extractVideoId(url) { try { const u = new URL(url, 'https://www.youtube.com'); if (u.pathname.includes('/shorts/')) return u.pathname.split('/')[2]; const id = u.searchParams.get('v'); if (id) return id; const match = u.pathname.match(/\/([a-zA-Z0-9_-]{11})(?:[/?#]|$)/); return match?.[1] || ''; } catch { return ''; } } async function fetchUploadDate(videoId, attempt = 0) { if (uploadDateCache.has(videoId)) return uploadDateCache.get(videoId); if (apiRequestCache.has(videoId)) return apiRequestCache.get(videoId); const fetchPromise = (async () => { try { const res = await fetch('https://www.youtube.com/youtubei/v1/player?prettyPrint=false', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ context: { client: { clientName: 'WEB', clientVersion: '2.20240416.01.00' } }, videoId }) }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const d = data?.microformat?.playerMicroformatRenderer; const raw = d?.publishDate || d?.uploadDate || d?.liveBroadcastDetails?.startTimestamp; if (raw) { uploadDateCache.set(videoId, raw); return raw; } else { throw new Error('No date found'); } } catch (err) { if (attempt < RETRY_LIMIT) { debug(`Retry ${attempt + 1} for ${videoId}`); await new Promise(r => setTimeout(r, RETRY_DELAY * (attempt + 1))); return fetchUploadDate(videoId, attempt + 1); } else { console.error('[YT Date Format] Fetch failed:', err); return null; } } finally { apiRequestCache.delete(videoId); } })(); apiRequestCache.set(videoId, fetchPromise); return fetchPromise; } async function processQueue() { if (currentRequests >= MAX_CONCURRENT || !isEnabled) return; const task = videoQueue.shift(); if (!task) return; currentRequests++; const { el, linkSelector, metaSelector } = task; try { const meta = el.querySelector(metaSelector); if (!meta) return; let holder = [...meta.querySelectorAll('span')].find(s => / views?|^\d[\d.,]*\s/.test(s.textContent) )?.nextElementSibling; if (!holder) { holder = document.createElement('span'); meta.appendChild(holder); } const link = el.querySelector(linkSelector); if (!link) return; const videoId = extractVideoId(link.href); if (!videoId || processedVideos.has(videoId)) return; processedVideos.set(videoId, Date.now()); const uploadDate = await fetchUploadDate(videoId); if (uploadDate) { holder.textContent = formatDate(uploadDate); holder.style.marginLeft = '4px'; } } catch (err) { console.error('[YT Date Format] Error in queue item:', err); } finally { currentRequests--; processQueue(); } } function enqueue(el, linkSelector, metaSelector) { if (!isEnabled) return; videoQueue.push({ el, linkSelector, metaSelector }); processQueue(); } function cleanOld() { const now = Date.now(); for (const [id, ts] of processedVideos.entries()) { if (now - ts > EXPIRY_MS) processedVideos.delete(id); } } function observeVideos() { const selectors = [ { container: 'ytd-video-renderer, ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer', link: 'a#thumbnail, h3 a', meta: '#metadata-line' }, { container: 'ytd-channel-video-player-renderer', link: 'a, yt-formatted-string > a', meta: '#metadata-line' } ]; const observer = new MutationObserver(() => { for (const { container, link, meta } of selectors) { document.querySelectorAll(container).forEach(el => enqueue(el, link, meta)); } cleanOld(); }); observer.observe(document.body, { childList: true, subtree: true }); // Initial trigger setTimeout(() => { selectors.forEach(({ container, link, meta }) => { document.querySelectorAll(container).forEach(el => enqueue(el, link, meta)); }); }, 500); } function injectToggleUI() { const btn = document.createElement('button'); btn.id = 'yt-date-toggle-btn'; btn.textContent = isEnabled ? '📅 Date ON' : '📅 Date OFF'; btn.style.cssText = ` min-width: 120px; padding: 6px 12px; margin-left: 10px; border-radius: 16px; background-color: #ffcc00; color: #000; font-weight: 600; border: 2px solid #000; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; justify-content: center; text-align: center; transition: background-color 0.3s ease; z-index: 9999; `; btn.onmouseenter = () => btn.style.backgroundColor = '#ffe066'; btn.onmouseleave = () => btn.style.backgroundColor = '#ffcc00'; btn.onclick = () => { isEnabled = !isEnabled; btn.textContent = isEnabled ? '📅 Date ON' : '📅 Date OFF'; }; const checkInterval = setInterval(() => { const startBar = document.querySelector('#start'); if (startBar && !document.querySelector('#yt-date-toggle-btn')) { startBar.appendChild(btn); clearInterval(checkInterval); } }, 500); } function hideDefaultDateCSS() { const style = document.createElement('style'); style.textContent = ` #info > span:nth-child(3), #info > span:nth-child(4) { display: none !important; } `; document.head.appendChild(style); } function init() { hideDefaultDateCSS(); observeVideos(); injectToggleUI(); } document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址