Skip to content

Instantly share code, notes, and snippets.

@kor-bim
Last active February 5, 2026 16:18
Show Gist options
  • Select an option

  • Save kor-bim/b370f378465d487b12216561bda42a51 to your computer and use it in GitHub Desktop.

Select an option

Save kor-bim/b370f378465d487b12216561bda42a51 to your computer and use it in GitHub Desktop.
youtube_upload_date
// ==UserScript==
// @name 유튜브 날짜 표시기
// @name:el YouTube Ημερομηνία βίντεο
// @name:nl YouTube Datumweergave
// @name:nb YouTube Datovisning
// @name:da YouTube Datovisning
// @name:de YouTube Datumsanzeige
// @name:ru YouTube Отображение даты
// @name:ro YouTube Afișare dată
// @name:mr YouTube तारीख दर्शक
// @name:vi YouTube Hiển thị ngày
// @name:be YouTube Адлюстраванне даты
// @name:bg YouTube Показване на дата
// @name:sr YouTube Приказ датума
// @name:sv YouTube Datumvisning
// @name:es YouTube Mostrar fecha
// @name:es-419 YouTube Mostrar fecha
// @name:sk YouTube Zobrazenie dátumu
// @name:ar YouTube عرض التاريخ
// @name:eo YouTube Datmontrado
// @name:en YouTube Date Display
// @name:uk YouTube Відображення дати
// @name:ug YouTube چېسلا كۆرسەتكۈچ
// @name:it YouTube Visualizzazione data
// @name:id YouTube Tampilan tanggal
// @name:ja YouTube 日付表示
// @name:ka YouTube თარიღის ჩვენება
// @name:zh-CN YouTube 日期显示
// @name:zh-TW YouTube 日期顯示
// @name:cs YouTube Zobrazení data
// @name:hr YouTube Prikaz datuma
// @name:th YouTube แสดงวันที่
// @name:tr YouTube Tarih Gösterimi
// @name:pt-BR YouTube Exibição de data
// @name:pl YouTube Wyświetlanie daty
// @name:fr YouTube Affichage de la date
// @name:fr-CA YouTube Affichage de la date
// @name:fi YouTube Päivämäärän näyttö
// @name:ko 유튜브 날짜 표시기
// @name:hu YouTube Dátum megjelenítés
// @name:he YouTube הצגת תאריך
// @name:ckb YouTube پیشاندانی بەروار
// @description 썸네일 날짜 영상본문 추천영상 쇼츠 재생목록에 날짜를 표시합니다
// @description:el Εμφανίζει ημερομηνίες σε μικρογραφίες βίντεο προτάσεις Shorts και λίστες αναπαραγωγής
// @description:nl Toont datums op miniaturen videopagina aanbevelingen Shorts en afspeellijsten
// @description:nb Viser datoer på miniatyrer videosider anbefalinger Shorts og spillelister
// @description:da Viser datoer på miniaturebilleder videosider anbefalinger Shorts og afspilningslister
// @description:de Zeigt Datumsangaben auf Thumbnails Videoseiten Empfehlungen Shorts und Wiedergabelisten an
// @description:ru Отображает даты на превью страницах видео рекомендациях Shorts и плейлистах
// @description:ro Afișează datele pe miniaturi pagini video recomandări Shorts și liste de redare
// @description:mr थंबनेल व्हिडिओ पृष्ठ शिफारसी Shorts आणि प्लेलिस्टवर तारीख दाखवते
// @description:vi Hiển thị ngày trên hình thu nhỏ trang video đề xuất Shorts và danh sách phát
// @description:be Паказвае даты на мініяцюрах старонках відэа рэкамендацыях Shorts і плэйлістах
// @description:bg Показва дати върху миниатюри видео страници препоръки Shorts и плейлисти
// @description:sr Приказује датуме на сличицама видео страницама препорукама Shorts и плејлистама
// @description:sv Visar datum på miniatyrer videosidor rekommendationer Shorts och spellistor
// @description:es Muestra fechas en miniaturas páginas de video recomendaciones Shorts y listas de reproducción
// @description:es-419 Muestra fechas en miniaturas páginas de video recomendaciones Shorts y listas de reproducción
// @description:sk Zobrazuje dátumy na miniatúrach stránkach videí odporúčaniach Shorts a zoznamoch prehrávania
// @description:ar يعرض التواريخ على الصور المصغرة وصفحات الفيديو والمقاطع المقترحة وShorts وقوائم التشغيل
// @description:eo Montras datojn sur bildetoj videosiveloj rekomendoj Shorts kaj ludlistoj
// @description:en Displays dates on thumbnails video pages recommended videos Shorts and playlists
// @description:uk Відображає дати на мініатюрах сторінках відео рекомендаціях Shorts та плейлистах
// @description:ug كىچىك رەسىملەر سىن بەتلىرى تەۋسىيە سىنلار Shorts ۋە قويۇش تىزىملىكىدە چېسلا كۆرسىتىدۇ
// @description:it Mostra le date su miniature pagine video consigliati Shorts e playlist
// @description:id Menampilkan tanggal pada thumbnail halaman video rekomendasi Shorts dan playlist
// @description:ja サムネイル 動画ページ おすすめ Shorts 再生リストに日付を表示します
// @description:ka აჩვენებს თარიღებს მინიატურებზე ვიდეო გვერდებზე რეკომენდაციებზე Shorts და პლეილისტებზე
// @description:zh-CN 在缩略图 视频页面 推荐视频 Shorts 和播放列表中显示日期
// @description:zh-TW 在縮圖 影片頁面 推薦影片 Shorts 與播放清單中顯示日期
// @description:cs Zobrazuje data na miniaturách stránkách videí doporučeních Shorts a seznamech skladeb
// @description:hr Prikazuje datume na sličicama stranicama videozapisa preporukama Shorts i popisima za reprodukciju
// @description:th แสดงวันที่บนภาพขนาดย่อ หน้า วิดีโอ วิดีโอแนะนำ Shorts และเพลย์ลิสต์
// @description:tr Küçük resimlerde video sayfalarında önerilen videolarda Shorts ve oynatma listelerinde tarihleri gösterir
// @description:pt-BR Exibe datas em miniaturas páginas de vídeo recomendações Shorts e playlists
// @description:pl Wyświetla daty na miniaturach stronach wideo poleceniach Shorts i listach odtwarzania
// @description:fr Affiche les dates sur les miniatures pages vidéo recommandations Shorts et playlists
// @description:fr-CA Affiche les dates sur les miniatures pages vidéo recommandations Shorts et playlists
// @description:fi Näyttää päivämäärät pikkukuvissa videosivuilla suosituksissa Shortsissa ja soittolistoissa
// @description:ko 썸네일 날짜 영상본문 추천영상 쇼츠 재생목록 날짜표기
// @description:hu Dátumokat jelenít meg bélyegképeken videóoldalakon ajánlásokban Shorts és lejátszási listákon
// @description:he מציג תאריכים בתמונות ממוזערות בדפי וידאו בהמלצות ב Shorts וברשימות השמעה
// @description:ckb بەروار لە وێنۆچکەکان پەڕەی ڤیدیۆ پێشنیارەکان Shorts و لیستی لێدان پیشان دەدات
// @author kor-bim
// @namespace http://tampermonkey.net/
// @version 1.2.2
// @match https://www.youtube.com/*
// @icon https://www.youtube.com/s/desktop/aaaab8bf/img/favicon_144x144.png
// @grant none
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const cache = new Map(); // videoId -> formatted date string
const inflight = new Map(); // videoId -> Promise<string|null>
let scanTimeout = null;
// ---------- locale-aware formatter (24h) ----------
const fmtCache = new Map();
function getLocales() {
const langs =
Array.isArray(navigator.languages) && navigator.languages.length
? navigator.languages
: [navigator.language || "en-US"];
try {
if (typeof window.ytcfg?.get === "function") {
const hl = window.ytcfg.get("HL");
if (hl) return [hl, ...langs];
}
} catch (_) {}
return langs;
}
function formatDate24h(dateLike) {
const d = new Date(dateLike);
if (Number.isNaN(d.getTime())) return null;
const locales = getLocales();
const options = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
hourCycle: "h23",
};
const key = `${locales.join(",")}::${JSON.stringify(options)}`;
if (!fmtCache.has(key)) fmtCache.set(key, new Intl.DateTimeFormat(locales, options));
return fmtCache.get(key).format(d);
}
// ---------- videoId helpers ----------
function getVideoIdFromHref(href) {
if (!href) return null;
try {
if (href.includes("v=")) return new URL(href, location.origin).searchParams.get("v");
if (href.includes("/shorts/")) return href.match(/shorts\/([a-zA-Z0-9_-]+)/)?.[1] || null;
} catch (_) {}
return null;
}
function getSafeVideoId(container, selector = "a#thumbnail") {
try {
const anchor = container.querySelector(selector) || container.querySelector("a[href]");
if (!anchor || !anchor.href) return null;
// Shorts 포함 지원이므로 제외하지 않음
return getVideoIdFromHref(anchor.href);
} catch (_) {}
return null;
}
function getCurrentShortsIdFromUrl() {
// https://www.youtube.com/shorts/VIDEO_ID
const m = location.pathname.match(/^\/shorts\/([a-zA-Z0-9_-]+)/);
return m?.[1] || null;
}
function getShortsVideoIdFromOverlay(overlayRoot) {
// URL이 /shorts/ID면 그걸 최우선
const fromUrl = getCurrentShortsIdFromUrl();
if (fromUrl) return fromUrl;
// 제공 DOM 기준 fallback: /watch?v= 링크
const a =
overlayRoot.querySelector("yt-reel-multi-format-link-view-model a[href*='watch?v=']") ||
overlayRoot.querySelector("a[href*='watch?v=']");
return a ? getVideoIdFromHref(a.getAttribute("href")) : null;
}
// ---------- fetch date (cache + inflight dedupe) ----------
async function fetchDate(videoId) {
if (!videoId) return null;
if (cache.has(videoId)) return cache.get(videoId);
if (inflight.has(videoId)) return inflight.get(videoId);
const p = (async () => {
try {
const response = await fetch("https://www.youtube.com/youtubei/v1/player", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
context: { client: { clientName: "WEB", clientVersion: "2.20250422.01.00" } },
videoId,
}),
});
const data = await response.json();
const micro = data.microformat?.playerMicroformatRenderer;
const date =
micro?.liveBroadcastDetails?.startTimestamp ||
micro?.publishDate ||
micro?.uploadDate;
const formatted = date ? formatDate24h(date) : null;
if (formatted) cache.set(videoId, formatted);
return formatted;
} catch (_) {
return null;
} finally {
inflight.delete(videoId);
}
})();
inflight.set(videoId, p);
return p;
}
// ---------- overwrite helper (no append) ----------
async function overwriteUI(target, videoId) {
if (!target || !videoId) return;
if (target.dataset.ytudProcessing === "true") return;
target.dataset.ytudProcessing = "true";
try {
const dateStr = await fetchDate(videoId);
if (!dateStr) return;
if (!document.body.contains(target)) return;
if (target.textContent !== dateStr) {
target.textContent = dateStr;
}
} finally {
target.removeAttribute("data-ytud-processing");
}
}
// ---------- Shorts: stable date label under title (no flicker, no child injection into attributed-string) ----------
async function upsertShortsDate(overlayRoot) {
const vId = getShortsVideoIdFromOverlay(overlayRoot);
if (!vId) return;
const titleVM = overlayRoot.querySelector("yt-shorts-video-title-view-model");
if (!titleVM) return;
const titleH2 = titleVM.querySelector("h2");
if (!titleH2) return;
// create/find a sibling label (NOT inside title span)
let label = titleVM.querySelector(".ytud-shorts-date");
if (!label) {
label = document.createElement("div");
label.className = "ytud-shorts-date";
// YouTube 스타일 최대한 상속 (색/폰트/크기 유사)
label.style.cssText = `
margin-top: 4px;
font: inherit;
font-size: 14px;
color: inherit;
opacity: 0.9;
white-space: nowrap;
`;
// title 아래에 붙이기
titleH2.insertAdjacentElement("afterend", label);
}
// 이미 같은 vid 처리면 fetch만(텍스트 업데이트)
if (label.dataset.ytudVid === vId && label.textContent && !label.textContent.includes("…")) {
return;
}
label.dataset.ytudVid = vId;
// placeholder
if (!label.textContent) label.textContent = "…";
const dateStr = await fetchDate(vId);
if (!dateStr) return;
if (!document.body.contains(label)) return;
if (label.dataset.ytudVid !== vId) return;
label.textContent = dateStr;
}
// ---------- scan ----------
function runSmartScan() {
// [A] Watch page main date
const mainContainer = document.querySelector("#info.ytd-watch-info-text");
if (mainContainer) {
const dateTarget = mainContainer.querySelector("span:nth-child(3)");
const vId = new URLSearchParams(window.location.search).get("v");
if (dateTarget && vId) overwriteUI(dateTarget, vId);
}
// [B] Playlist
document.querySelectorAll("ytd-playlist-video-renderer").forEach((e) => {
const vId = getSafeVideoId(e);
if (!vId) return;
const metaContainer = e.querySelector("#video-info") || e.querySelector("#metadata-line");
if (!metaContainer) return;
const spans = metaContainer.querySelectorAll("span");
const target = spans.length > 0 ? spans[spans.length - 1] : null;
if (target) overwriteUI(target, vId);
});
// [C] Cards (home/search/recommend)
const cardSelectors =
"ytd-rich-grid-media, ytd-compact-video-renderer, ytd-video-renderer, ytd-grid-video-renderer, yt-lockup-view-model";
document.querySelectorAll(cardSelectors).forEach((container) => {
const metaLine =
container.querySelector("#metadata-line") ||
container.querySelector("yt-content-metadata-view-model");
if (!metaLine) return;
const spans = metaLine.querySelectorAll("span");
if (!spans.length) return;
let target = spans.length >= 2 ? spans[1] : spans[0];
if (container.tagName.toLowerCase() === "yt-lockup-view-model") {
target = spans[spans.length - 1];
}
const vId = getSafeVideoId(container);
if (target && vId) overwriteUI(target, vId);
});
// [D] Shorts overlay (add date under title)
document
.querySelectorAll(".metadata-container.ytd-reel-player-overlay-renderer")
.forEach((overlay) => {
upsertShortsDate(overlay);
});
}
// ---------- observer ----------
const observer = new MutationObserver(() => {
if (scanTimeout) clearTimeout(scanTimeout);
scanTimeout = setTimeout(runSmartScan, 150);
});
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener("yt-navigate-finish", () => {
setTimeout(runSmartScan, 500);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment