Replace "1 year ago" with exact dates. Three-tier color badges: Green (this week), Purple (this year - toggleable), Yellow (old years)
// ==UserScript==
// @name YouTube Full Dates (v2)
// @namespace YouTube Full Dates
// @version 2
// @description Replace "1 year ago" with exact dates. Three-tier color badges: Green (this week), Purple (this year - toggleable), Yellow (old years)
// @author Solomon (improved from InMirrors)
// @match https://www.youtube.com/*
// @icon https://www.youtube.com/s/desktop/814d40a6/img/favicon_144x144.png
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
// Previous Features (v1):
// - Exact upload dates replacing relative timestamps
// - 11 language support
// - Custom date format tokens
// - Three-tier color badges (green/purple/yellow)
// - Smart year hiding for current year
// - Settings panel with toggles
// - Works on all YouTube pages
// New in v2:
// - Added toggle to disable purple "this year" badges (shows plain text instead)
// - User request: "can we please get a toggle for the purple badges? I'd rather see plain text"
(function() {
'use strict';
const LANGUAGES = {
en: { name: 'English', monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], monthsFull: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], agoKeywords: ['ago', 'Streamed'], dateKeywords: ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] },
es: { name: 'Español', monthsShort: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], monthsFull: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], agoKeywords: ['hace'], dateKeywords: ['segundo', 'minuto', 'hora', 'día', 'semana', 'mes', 'año'] },
fr: { name: 'Français', monthsShort: ['janv', 'févr', 'mars', 'avr', 'mai', 'juin', 'juil', 'août', 'sept', 'oct', 'nov', 'déc'], monthsFull: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'], agoKeywords: ['il y a'], dateKeywords: ['seconde', 'minute', 'heure', 'jour', 'semaine', 'mois', 'an', 'année'] },
de: { name: 'Deutsch', monthsShort: ['Jan', 'Feb', 'März', 'Apr', 'Mai', 'Juni', 'Juli', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], monthsFull: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], agoKeywords: ['vor'], dateKeywords: ['Sekunde', 'Minute', 'Stunde', 'Tag', 'Woche', 'Monat', 'Jahr'] },
pt: { name: 'Português', monthsShort: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], monthsFull: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], agoKeywords: ['há'], dateKeywords: ['segundo', 'minuto', 'hora', 'dia', 'semana', 'mês', 'ano'] },
it: { name: 'Italiano', monthsShort: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], monthsFull: ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'], agoKeywords: ['fa'], dateKeywords: ['secondo', 'minuto', 'ora', 'giorno', 'settimana', 'mese', 'anno'] },
ru: { name: 'Русский', monthsShort: ['янв', 'февр', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сент', 'окт', 'нояб', 'дек'], monthsFull: ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], agoKeywords: ['назад'], dateKeywords: ['секунд', 'минут', 'час', 'день', 'дней', 'недел', 'месяц', 'год', 'лет'] },
zh: { name: '中文', monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], monthsFull: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], agoKeywords: ['前'], dateKeywords: ['秒', '分', '时', '時', '天', '日', '周', '週', '月', '年'] },
ja: { name: '日本語', monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], monthsFull: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], agoKeywords: ['前'], dateKeywords: ['秒', '分', '時間', '日', '週間', 'か月', '年'] },
ko: { name: '한국어', monthsShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], monthsFull: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], agoKeywords: ['전'], dateKeywords: ['초', '분', '시간', '일', '주', '개월', '년'] },
ar: { name: 'العربية', monthsShort: ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], monthsFull: ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], agoKeywords: ['قبل', 'منذ'], dateKeywords: ['ثانية', 'دقيقة', 'ساعة', 'يوم', 'أسبوع', 'شهر', 'سنة'] }
};
// v2: Added thisYearBadge setting (default true for backward compatibility)
const DEFAULT_CONFIG = { dateFormat: 'MMMM dd yy', language: 'en', smartYear: true, highlightOldVideos: true, thisWeekEmoji: true, thisWeekBadge: '🆕', thisYearBadge: true, debugMode: false };
const savedSettings = GM_getValue('settings', {});
const SETTINGS = { ...DEFAULT_CONFIG, ...savedSettings };
const PROCESSED_ATTR = 'data-ytfd-done';
const dateCache = new Map();
let isProcessing = false, pendingRequests = 0, lastUrl = window.location.href;
const MAX_CONCURRENT = 8, requestQueue = [];
const log = (...args) => SETTINGS.debugMode && console.log('📅 [v2]', ...args);
function isWithinLastWeek(date) {
const diffDays = (new Date() - new Date(date)) / (1000 * 60 * 60 * 24);
return diffDays >= 0 && diffDays < 7;
}
function formatDate(date) {
const d = new Date(date);
if (isNaN(d.getTime())) return { text: '', tier: 'none' };
const lang = LANGUAGES[SETTINGS.language] || LANGUAGES.en;
const pad = (n) => String(n).padStart(2, '0');
const now = new Date(), currentYear = now.getFullYear(), videoYear = d.getFullYear();
const isThisWeek = isWithinLastWeek(d);
const isThisYear = currentYear === videoYear && !isThisWeek;
const isOldYear = videoYear < currentYear;
// Tier 1: This week - green with emoji
if (SETTINGS.thisWeekEmoji && isThisWeek) {
return {
text: SETTINGS.thisWeekBadge + ' ' + lang.monthsShort[d.getMonth()] + ' ' + d.getDate(),
tier: 'thisWeek'
};
}
// Build date string
const tokens = {
yyyy: d.getFullYear(),
yy: String(d.getFullYear()).slice(-2),
MMMM: lang.monthsFull[d.getMonth()],
MMM: lang.monthsShort[d.getMonth()],
MM: pad(d.getMonth() + 1),
dd: pad(d.getDate())
};
let result = SETTINGS.dateFormat;
// Tier 2: This year - no year shown
if (SETTINGS.smartYear && currentYear === videoYear) {
result = result.replace(/\s*yyyy\s*/g, ' ').replace(/\s*yy\s*/g, ' ');
}
// Replace tokens and clean up
result = result.replace(/yyyy|yy|MMMM|MMM|MM|dd/g, m => tokens[m]);
result = result.replace(/,/g, '').replace(/\s+/g, ' ').trim();
if (isThisYear) return { text: result, tier: 'thisYear' };
if (isOldYear) return { text: result, tier: 'oldYear' };
return { text: result, tier: 'none' };
}
function getVideoId(url) {
if (!url) return null;
let m = url.match(/\/shorts\/([^/?&]+)/) || url.match(/[?&]v=([^&]+)/) || url.match(/\/embed\/([^/?&]+)/);
return m ? m[1] : null;
}
function hasRelativeDate(text) {
if (!text) return false;
const agoKw = Object.values(LANGUAGES).flatMap(l => l.agoKeywords);
const dateKw = Object.values(LANGUAGES).flatMap(l => l.dateKeywords);
const t = text.toLowerCase();
return agoKw.some(k => t.includes(k.toLowerCase())) && dateKw.some(k => t.includes(k.toLowerCase()));
}
function hasYouTubeFullDate(text) {
if (!text) return false;
return /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|January|February|March|April|June|July|August|September|October|November|December)[a-z]*\.?\s+\d{1,2}/i.test(text);
}
function extractDateFromText(text) {
if (!text) return null;
const monthMap = { jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11, january: 0, february: 1, march: 2, april: 3, june: 5, july: 6, august: 7, september: 8, october: 9, november: 10, december: 11 };
let m = text.match(/\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|January|February|March|April|May|June|July|August|September|October|November|December)[a-z]*\.?\s+(\d{1,2}),?\s*(\d{4})?\b/i);
if (m) {
const mo = monthMap[m[1].toLowerCase().replace(/[^a-z]/g, '').substring(0, 3)];
if (mo !== undefined) {
const year = m[3] ? parseInt(m[3]) : new Date().getFullYear();
return new Date(year, mo, parseInt(m[2]));
}
}
return null;
}
function needsProcessing(el) {
if (el.hasAttribute(PROCESSED_ATTR)) return false;
const text = el.textContent;
return text && (hasRelativeDate(text) || hasYouTubeFullDate(text));
}
async function fetchUploadDate(videoId) {
if (dateCache.has(videoId)) return dateCache.get(videoId);
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('Network error');
const data = await res.json(), info = data?.microformat?.playerMicroformatRenderer;
let uploadDate = info?.liveBroadcastDetails?.isLiveNow ? info.liveBroadcastDetails.startTimestamp : (info?.publishDate || info?.uploadDate);
if (uploadDate) dateCache.set(videoId, uploadDate);
return uploadDate;
} catch (e) { log('❌ Fetch error:', e); return null; }
}
async function processQueue() {
while (requestQueue.length > 0 && pendingRequests < MAX_CONCURRENT) {
const task = requestQueue.shift();
pendingRequests++;
try { await task(); } catch (e) { log('❌ Task error:', e); }
pendingRequests--;
}
}
function applyDateToElement(element, result) {
element.textContent = result.text;
element.setAttribute(PROCESSED_ATTR, 'true');
element.classList.remove('ytfd-old-video', 'ytfd-this-week', 'ytfd-this-year');
if (result.tier === 'thisWeek') {
element.classList.add('ytfd-this-week');
} else if (result.tier === 'oldYear' && SETTINGS.highlightOldVideos) {
element.classList.add('ytfd-old-video');
} else if (result.tier === 'thisYear' && SETTINGS.thisYearBadge) {
// v2: Only apply purple badge if thisYearBadge is enabled
element.classList.add('ytfd-this-year');
}
// v2: If thisYearBadge is OFF, "thisYear" dates get no badge class = plain text
log('✅', result.tier, result.text);
}
function queueDateUpdate(videoId, element, originalText) {
element.setAttribute(PROCESSED_ATTR, 'pending');
requestQueue.push(async () => {
let uploadDate = null;
if (hasYouTubeFullDate(originalText)) {
const extracted = extractDateFromText(originalText);
if (extracted) {
uploadDate = extracted.toISOString();
log('📅 Extracted:', originalText, '→', extracted);
}
}
if (!uploadDate) {
uploadDate = await fetchUploadDate(videoId);
}
if (!uploadDate) {
element.removeAttribute(PROCESSED_ATTR);
return;
}
const result = formatDate(uploadDate);
if (!result.text) {
element.removeAttribute(PROCESSED_ATTR);
return;
}
applyDateToElement(element, result);
});
processQueue();
}
function findDateElement(container) {
const selectors = ['#metadata-line > span:first-child', '#metadata-line > span', '.inline-metadata-item', '.yt-core-attributed-string--link-inherit-color', 'span.yt-formatted-string'];
for (const selector of selectors) {
const elements = container.querySelectorAll(selector);
for (const el of elements) {
if (needsProcessing(el)) return el;
}
}
return null;
}
function findVideoLink(container) {
const selectors = ['a#thumbnail', 'a#video-title-link', 'h3 > a', '.yt-lockup-view-model__content-image', 'a[href*="watch"]', 'a[href*="shorts"]'];
for (const selector of selectors) {
const el = container.querySelector(selector);
if (el?.href) return el.href;
}
return null;
}
function processAllVideos() {
const containerSelectors = ['ytd-rich-item-renderer', 'ytd-video-renderer', 'ytd-compact-video-renderer', 'ytd-playlist-video-renderer', 'ytd-grid-video-renderer', 'ytd-rich-grid-media', 'yt-lockup-view-model'];
containerSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(container => {
const dateEl = findDateElement(container);
if (!dateEl) return;
const href = findVideoLink(container);
const videoId = getVideoId(href);
if (!videoId) return;
const originalText = dateEl.textContent.trim();
queueDateUpdate(videoId, dateEl, originalText);
});
});
}
function clearAllMarkers() {
log('🔄 Clearing all markers...');
document.querySelectorAll('[' + PROCESSED_ATTR + ']').forEach(el => {
el.removeAttribute(PROCESSED_ATTR);
el.classList.remove('ytfd-old-video', 'ytfd-this-week', 'ytfd-this-year');
});
}
function runProcessors() {
if (isProcessing) return;
isProcessing = true;
try { processAllVideos(); } catch (e) { log('❌ Error:', e); }
isProcessing = false;
}
GM_addStyle(`
.ytfd-old-video { background-color: #ffeb3b !important; padding: 2px 6px !important; border-radius: 4px !important; color: #000 !important; font-weight: 600 !important; display: inline !important; line-height: 1.2 !important; }
html[dark] .ytfd-old-video, [dark] .ytfd-old-video { background-color: #ffd600 !important; }
.ytfd-this-week { background-color: #81c784 !important; padding: 2px 6px !important; border-radius: 4px !important; color: #000 !important; font-weight: 600 !important; display: inline !important; line-height: 1.2 !important; }
html[dark] .ytfd-this-week, [dark] .ytfd-this-week { background-color: #66bb6a !important; }
.ytfd-this-year { background-color: #b39ddb !important; padding: 2px 6px !important; border-radius: 4px !important; color: #000 !important; font-weight: 600 !important; display: inline !important; line-height: 1.2 !important; }
html[dark] .ytfd-this-year, [dark] .ytfd-this-year { background-color: #9575cd !important; color: #fff !important; }
.ytfd-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); z-index: 99999; width: 360px; max-height: 80vh; overflow: hidden; opacity: 0; visibility: hidden; transition: all 0.2s ease; }
html[dark] .ytfd-panel, [dark] .ytfd-panel { background: #212121; color: #fff; }
.ytfd-panel.visible { opacity: 1; visibility: visible; }
.ytfd-header { background: #cc0000; color: #fff; padding: 14px; display: flex; justify-content: space-between; align-items: center; }
.ytfd-header h2 { margin: 0; font-size: 15px; }
.ytfd-close { background: rgba(255,255,255,0.2); border: none; color: #fff; width: 26px; height: 26px; border-radius: 50%; cursor: pointer; font-size: 14px; }
.ytfd-content { padding: 14px; max-height: 50vh; overflow-y: auto; }
.ytfd-section { margin-bottom: 14px; }
.ytfd-section-title { font-size: 11px; font-weight: 600; color: #666; margin-bottom: 6px; text-transform: uppercase; }
html[dark] .ytfd-section-title { color: #aaa; }
.ytfd-input-group { margin-bottom: 8px; }
.ytfd-input-group label { display: block; font-size: 12px; margin-bottom: 3px; color: #333; }
html[dark] .ytfd-input-group label { color: #ddd; }
.ytfd-input-group input, .ytfd-input-group select { width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box; }
html[dark] .ytfd-input-group input, html[dark] .ytfd-input-group select { background: #333; border-color: #444; color: #fff; }
.ytfd-toggle-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; }
.ytfd-toggle-label { font-size: 12px; color: #333; }
html[dark] .ytfd-toggle-label { color: #ddd; }
.ytfd-toggle { width: 36px; height: 18px; background: #ccc; border-radius: 9px; position: relative; cursor: pointer; }
.ytfd-toggle::after { content: ''; position: absolute; width: 14px; height: 14px; background: #fff; border-radius: 50%; top: 2px; left: 2px; transition: transform 0.2s; }
.ytfd-toggle.on { background: #cc0000; }
.ytfd-toggle.on::after { transform: translateX(18px); }
.ytfd-footer { padding: 10px 14px; background: #f5f5f5; display: flex; justify-content: flex-end; gap: 6px; }
html[dark] .ytfd-footer { background: #1a1a1a; }
.ytfd-btn { padding: 6px 12px; border: none; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
.ytfd-btn-primary { background: #cc0000; color: #fff; }
.ytfd-btn-secondary { background: #ddd; color: #333; }
html[dark] .ytfd-btn-secondary { background: #444; color: #fff; }
.ytfd-help { font-size: 10px; color: #888; line-height: 1.4; }
`);
function createSettingsPanel() {
const panel = document.createElement('div');
panel.className = 'ytfd-panel';
const langOpts = Object.entries(LANGUAGES).map(([c, l]) => '<option value="' + c + '"' + (c === SETTINGS.language ? ' selected' : '') + '>' + l.name + '</option>').join('');
panel.innerHTML = '<div class="ytfd-header"><h2>📅 YouTube Full Dates v2</h2><button class="ytfd-close">✕</button></div><div class="ytfd-content"><div class="ytfd-section"><div class="ytfd-section-title">Language & Format</div><div class="ytfd-input-group"><label>Language</label><select id="ytfd-language">' + langOpts + '</select></div><div class="ytfd-input-group"><label>Date Format</label><input type="text" id="ytfd-format" value="' + SETTINGS.dateFormat + '"></div><div class="ytfd-help">MMMM=January, MMM=Jan, dd=05, yy=25, yyyy=2025</div></div><div class="ytfd-section"><div class="ytfd-section-title">Display Options</div><div class="ytfd-toggle-row"><span class="ytfd-toggle-label">Smart Year (hide if current)</span><div class="ytfd-toggle' + (SETTINGS.smartYear ? ' on' : '') + '" data-key="smartYear"></div></div><div class="ytfd-toggle-row"><span class="ytfd-toggle-label">🟡 Yellow for old years</span><div class="ytfd-toggle' + (SETTINGS.highlightOldVideos ? ' on' : '') + '" data-key="highlightOldVideos"></div></div><div class="ytfd-toggle-row"><span class="ytfd-toggle-label">🆕 Green for this week</span><div class="ytfd-toggle' + (SETTINGS.thisWeekEmoji ? ' on' : '') + '" data-key="thisWeekEmoji"></div></div><div class="ytfd-toggle-row"><span class="ytfd-toggle-label">🟣 Purple for this year</span><div class="ytfd-toggle' + (SETTINGS.thisYearBadge ? ' on' : '') + '" data-key="thisYearBadge"></div></div></div><div class="ytfd-section"><div class="ytfd-section-title">Color Guide</div><div class="ytfd-help">🟢 Green = This week (<7 days)<br>🟣 Purple = This year (7+ days) — toggle off for plain text<br>🟡 Yellow = Previous years</div></div></div><div class="ytfd-footer"><button class="ytfd-btn ytfd-btn-secondary" id="ytfd-reset">Reset</button><button class="ytfd-btn ytfd-btn-primary" id="ytfd-save">Save</button></div>';
document.body.appendChild(panel);
panel.querySelector('.ytfd-close').addEventListener('click', () => panel.classList.remove('visible'));
panel.querySelectorAll('.ytfd-toggle').forEach(t => t.addEventListener('click', () => t.classList.toggle('on')));
panel.querySelector('#ytfd-save').addEventListener('click', () => {
GM_setValue('settings', {
dateFormat: panel.querySelector('#ytfd-format').value,
language: panel.querySelector('#ytfd-language').value,
smartYear: panel.querySelector('[data-key="smartYear"]').classList.contains('on'),
highlightOldVideos: panel.querySelector('[data-key="highlightOldVideos"]').classList.contains('on'),
thisWeekEmoji: panel.querySelector('[data-key="thisWeekEmoji"]').classList.contains('on'),
thisYearBadge: panel.querySelector('[data-key="thisYearBadge"]').classList.contains('on'),
thisWeekBadge: SETTINGS.thisWeekBadge,
debugMode: SETTINGS.debugMode
});
alert('Saved! Refresh to apply.');
panel.classList.remove('visible');
});
panel.querySelector('#ytfd-reset').addEventListener('click', () => { GM_setValue('settings', {}); alert('Reset! Refresh to apply.'); panel.classList.remove('visible'); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') panel.classList.remove('visible'); });
return panel;
}
const settingsPanel = createSettingsPanel();
GM_registerMenuCommand('⚙️ Settings', () => settingsPanel.classList.add('visible'));
let debounceTimer = null;
function debouncedRun(delay = 200) { clearTimeout(debounceTimer); debounceTimer = setTimeout(runProcessors, delay); }
document.addEventListener('click', e => {
const chip = e.target.closest('yt-chip-cloud-chip-renderer, yt-formatted-string[class*="chip"], [role="tab"], iron-selector yt-formatted-string, tp-yt-paper-item, ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer');
if (chip) {
log('🎯 Tab/filter clicked');
setTimeout(() => { clearAllMarkers(); runProcessors(); }, 500);
setTimeout(() => { clearAllMarkers(); runProcessors(); }, 1000);
setTimeout(() => { clearAllMarkers(); runProcessors(); }, 2000);
}
}, true);
const observer = new MutationObserver((mutations) => {
let shouldRun = false;
for (const m of mutations) {
if (m.addedNodes.length > 0) {
for (const n of m.addedNodes) {
if (n.nodeType === 1 && (n.matches?.('ytd-rich-item-renderer, ytd-video-renderer, ytd-compact-video-renderer, #contents') || n.querySelector?.('ytd-rich-item-renderer, ytd-video-renderer'))) {
shouldRun = true;
break;
}
}
}
}
if (shouldRun) debouncedRun(100);
});
observer.observe(document.body, { childList: true, subtree: true });
setInterval(() => { if (window.location.href !== lastUrl) { log('🔄 URL changed'); lastUrl = window.location.href; clearAllMarkers(); debouncedRun(300); } }, 500);
window.addEventListener('yt-navigate-finish', () => { log('🧭 Navigation finished'); dateCache.clear(); lastUrl = window.location.href; clearAllMarkers(); debouncedRun(300); });
window.addEventListener('scroll', () => debouncedRun(150), { passive: true });
setInterval(runProcessors, 3000);
setTimeout(runProcessors, 300);
setTimeout(runProcessors, 1000);
setTimeout(runProcessors, 2000);
console.log('📅 YouTube Full Dates v2 loaded!');
})();