"나중에 보기"에 저장된 동영상은 홈 화면에서 캐러셀로 표시됩니다
// ==UserScript==
// @name Do not watch later!
// @name:pt-BR Não deixe para assistir mais tarde!
// @name:es ¡No lo veas más tarde!
// @name:zh-CN 别想着以后再看!
// @name:ko 나중에 보지 마세요!
// @name:fr Ne regardez pas plus tard !
// @version v1
// @description Videos from “Watch Later” become a carousel on the home page.
// @description:pt-BR Os vídeos do "Assistir mais tarde" transformam-se num carrossel na página inicial.
// @description:es Los videos de "Ver más tarde" se convierten en un carrusel en la página de inicio.
// @description:zh-CN “稍后观看”中的视频在主页上以轮播形式呈现
// @description:ko "나중에 보기"에 저장된 동영상은 홈 화면에서 캐러셀로 표시됩니다
// @description:fr Les vidéos de la section "À regarder plus tard" apparaissent sous forme de carrousel sur la page d'accueil.
// @author Isac
// @match https://www.youtube.com/*
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_addStyle
// @namespace https://greasyfork.org/users/1570114
// ==/UserScript==
(function() {
'use strict';
/*!
* Copyright (c) 2026, Isac. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
function isHomePage() {
const path = window.location.pathname;
return (
path === "/"
);
}
// ── Primary function ────────────────────
function initCarousel() {
console.log("[Do not watch later] Cleaning carousel");
document.querySelectorAll('.carousel-wrapper, .script-title').forEach(el => el.remove());
// ────────────────────────────────────────────────
// Languages
// ────────────────────────────────────────────────
const translations = {
'pt': {
later: 'Videos do “Assistir mais tarde”',
feed: 'Feed inicial',
noVideos: 'Sem vídeos no "Assistir mais Tarde"',
},
'en': {
later: '"Watch Later" Videos',
feed: 'Home Feed',
noVideos: 'No videos in “Watch Later”',
},
'es': {
later: 'Videos de "Ver más tarde"',
feed: 'Feed principal',
noVideos: 'No hay videos en "Ver más tarde"',
},
'zh': {
later: '稍后观看视频',
feed: '首页动态',
noVideos: '“稍后观看”中没有视频',
},
'ko': {
later: '“나중에 보기” 동영상',
feed: '홈 피드',
noVideos: '“나중에 보기”에 동영상이 없습니다',
},
'fr': {
later: 'Vidéos "À regarder plus tard"',
feed: 'Accueil Flux',
noVideos: 'Aucune vidéo dans "À regarder plus tard"',
},
};
function getYouTubeLanguage() {
let lang = document.documentElement.lang.split('-')[0];
if (!translations[lang]) {
lang = 'en'
}
return lang;
}
// ────────────────────────────────────────────────
// CSS (carousel + cards + arrows)
// ────────────────────────────────────────────────
GM_addStyle(`
.script-title {
margin: 16px 24px 0px;
font-size: 2.2rem;
font-weight: 700;
color: var(--yt-spec-text-primary);
align-self: flex-start;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.script-title svg {
vertical-align: middle;
margin-right: 12px;
width: 28px;
height: 28px;
}
.script-no-video-title {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 2.0rem;
font-weight: 700;
color: var(--yt-spec-text-primary);
padding: 5px 20px;
margin: 0;
}
.carousel-wrapper {
position: relative;
padding: 0 24px;
width: 100%;
margin-top: 24px;
box-sizing: border-box;
}
#laterDiv {
width: 100%;
overflow: hidden;
scroll-behavior: smooth;
display: flex;
flex-direction: row;
gap: 16px;
margin-left: 0 !important;
margin-right: 0 !important;
padding: 0;
box-sizing: border-box;
}
.video-card {
flex: 0 0 auto;
width: 360px;
text-decoration: none !important;
color: inherit;
display: flex;
flex-direction: column;
transition: transform 0.15s ease;
}
.video-card:hover {
transform: scale(0.98);
z-index: 2;
}
.thumb-div {
position: relative;
width: 100%;
border-radius: 12px;
overflow: hidden;
aspect-ratio: 16 / 9;
}
.video-card-thumb {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.video-time-thumb {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0,0,0,0.6);
color: white;
font-size: 1.2rem;
font-weight: 500;
padding: 1px 4px;
border-radius: 4px;
line-height: 1.8rem;
pointer-events: none;
}
.video-card-title {
margin: 12px 0 2px;
font-size: 1.55rem;
line-height: 2.1rem;
font-weight: 500;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(--yt-spec-text-primary);
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
.video-card-infs {
font-size: 1.38rem;
line-height: 1.9rem;
color: var(--yt-spec-text-secondary);
margin-top: 2px;
}
.channel-name-inf {
color: var(--yt-spec-text-secondary);
width: fit-content;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
max-width: 100%;
}
.channel-name-inf:hover:not(.no-link) {
color: var(--yt-spec-text-primary);
}
.video-card-infs .delimiter {
margin: 0 4px;
font-size: 1.1rem;
vertical-align: middle;
color: var(--yt-spec-text-tertiary);
}
/* Arrows */
.carousel-nav {
position: absolute;
top: 50%;
transform: translateY(-50%) scale(1);
background: rgba(15, 15, 15);
box-shadow: 0px 0px 15px 3px black;
color: white;
border: none;
width: 44px;
height: 44px;
border-radius: 50%;
font-size: 1.8rem;
cursor: pointer;
display: flex;
align-self: center;
align-items: center;
justify-content: center;
z-index: 10;
transition: transform 0.2s ease, background-color 0.2s ease, opacity 0.2s ease;
opacity: 0;
pointer-events: none;
}
.carousel-nav svg {
fill: currentColor;
}
.carousel-wrapper:hover .carousel-nav:not(:disabled),
.carousel-wrapper:focus-within .carousel-nav:not(:disabled) {
opacity: 1;
pointer-events: auto;
}
.carousel-nav:hover:not(:disabled):hover {
background: rgba(63, 63, 63);
}
.carousel-nav.left { left: 28px; }
.carousel-nav.right { right: 28px; }
.carousel-nav:disabled {
cursor: not-allowed;
pointer-events: none;
opacity: 0 !important;
transform: translateY(-50%) scale(0.8);
}
`);
// ────────────────────────────────────────────────
// Waiting to load the main feed
// ────────────────────────────────────────────────
const clock = setInterval(async () => {
let candidates = document.querySelectorAll('#contents');
let contents = null;
for (const candidate of candidates) {
const next = candidate.nextElementSibling;
if (next && next.id === "reload-content") {
contents = candidate;
break;
}
}
if (!contents) {
console.log("[Do not watch later] Waiting for content with grid variables...");
return;
}
clearInterval(clock);
console.log("[Do not watch later] #contents found! Starting carousel...");
// ── Carousel title ──────────────────────
function createTitleIcon(pathData) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "24");
svg.setAttribute("height", "24");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("fill", "currentColor");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", pathData);
svg.appendChild(path);
return svg;
}
// ── Titles ──────────────────────
const translatedTexts = translations[getYouTubeLanguage()]
const laterH2 = document.createElement("h2");
laterH2.className = "script-title";
// 1° SVG
const laterIcon = createTitleIcon("M12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1Zm0 2a9 9 0 110 18.001A9 9 0 0112 3Zm0 3a1 1 0 00-1 1v5.565l.485.292 3.33 2a1 1 0 001.03-1.714L13 11.435V7a1 1 0 00-1-1Z");
laterH2.appendChild(laterIcon);
// 2° Text
laterH2.appendChild(document.createTextNode(translatedTexts.later));
contents.before(laterH2);
const feedH2 = document.createElement("h2");
feedH2.className = "script-title feed-title";
// 1° SVG
const feedIcon = createTitleIcon("m11.485 2.143-8 4.8-2 1.2a1 1 0 001.03 1.714L3 9.567V20a2 2 0 002 2h6v-7h2v7h6a2 2 0 002-2V9.567l.485.29a1 1 0 001.03-1.714l-2-1.2-8-4.8a1 1 0 00-1.03 0ZM5 8.366l7-4.2 7 4.2V20h-4v-5.5a1.5 1.5 0 00-1.5-1.5h-3A1.5 1.5 0 009 14.5V20H5V8.366Z");
feedH2.appendChild(feedIcon);
// 2° Text
feedH2.appendChild(document.createTextNode(translatedTexts.feed));
// ── Carousel wrapper (to position arrows) ──
const wrapper = document.createElement("div");
wrapper.className = "carousel-wrapper";
contents.before(wrapper);
// ── Carousel container ───────────────────
const laterDiv = document.createElement("div");
laterDiv.id = "laterDiv";
wrapper.appendChild(laterDiv);
// ── Navigation buttons ──────────────────────
function createChevronSvg(isRight = true) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("height", "24");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "24");
svg.setAttribute("focusable", "false");
svg.setAttribute("aria-hidden", "true");
svg.style.pointerEvents = "none";
svg.style.display = "inherit";
svg.style.width = "100%";
svg.style.height = "100%";
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
if (isRight) {
// Right arrow
path.setAttribute("d", "M8.793 5.293a1 1 0 000 1.414L14.086 12l-5.293 5.293a1 1 0 101.414 1.414L16.914 12l-6.707-6.707a1 1 0 00-1.414 0Z");
} else {
// Left arrow
path.setAttribute("d", "M15.207 5.293a1 1 0 010 1.414L9.914 12l5.293 5.293a1 1 0 01-1.414 1.414l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 0Z");
}
svg.appendChild(path);
return svg;
}
const btnLeft = document.createElement("button");
btnLeft.className = "carousel-nav left";
btnLeft.disabled = true;
btnLeft.appendChild(createChevronSvg(false));
const btnRight = document.createElement("button");
btnRight.className = "carousel-nav right";
btnRight.appendChild(createChevronSvg(true));
wrapper.append(btnLeft, btnRight);
// ── Scroll with arrows ──────────────────────
const scrollAmount = 380; // ≈ width card (360) + gap (16) + margin
btnLeft.addEventListener("click", () => {
laterDiv.scrollBy({ left: -scrollAmount, behavior: "smooth" });
});
btnRight.addEventListener("click", () => {
laterDiv.scrollBy({ left: scrollAmount, behavior: "smooth" });
});
const updateButtons = () => {
btnLeft.disabled = laterDiv.scrollLeft <= 0;
btnRight.disabled = laterDiv.scrollLeft + laterDiv.clientWidth >= laterDiv.scrollWidth - 1;
};
laterDiv.addEventListener("scroll", updateButtons);
window.addEventListener("resize", updateButtons);
// ── Get data from the WL playlist ───────────────
try {
const res = await fetch("https://www.youtube.com/playlist?list=WL");
const text = await res.text();
const start = text.indexOf("var ytInitialData = ") + 20;
const end = text.indexOf(";</script>", start);
const jsonStr = text.slice(start, end);
const data = JSON.parse(jsonStr);
const contentsList = data?.contents
?.twoColumnBrowseResultsRenderer?.tabs?.[0]
?.tabRenderer?.content?.sectionListRenderer?.contents?.[0]
?.itemSectionRenderer?.contents?.[0]
?.playlistVideoListRenderer?.contents;
if (!Array.isArray(contentsList) || contentsList.length === 0) {
console.warn("No videos found in the WL playlist");
btnRight.disabled = true;
// ── Creating "no videos" screen ──────────────────────
const textNoVideos = document.createElement('p');
textNoVideos.textContent = translatedTexts.noVideos;
textNoVideos.className = "script-no-video-title";
laterDiv.appendChild(textNoVideos);
} else {
contentsList.forEach(item => {
const r = item?.playlistVideoRenderer;
if (!r) return;
const video = {
videoId: r.videoId,
title: r.title?.runs?.[0]?.text || "Untitled",
thumb: r.thumbnail?.thumbnails?.at(-1)?.url || r.thumbnail?.thumbnails?.[0]?.url || "",
duration: r.lengthText?.simpleText || "—",
channelName: r.shortBylineText?.runs?.[0]?.text || "Unknown channel",
channelUrl: r.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url || "",
views: r.videoInfo?.runs?.[0]?.text || "",
published: r.videoInfo?.runs?.[2]?.text || "",
};
const a = document.createElement("a");
a.className = "video-card";
a.href = `https://www.youtube.com/watch?v=${video.videoId}`;
const thumbDiv = document.createElement("div");
thumbDiv.className = "thumb-div";
const img = document.createElement("img");
img.className = "video-card-thumb";
img.src = video.thumb;
img.alt = video.title;
img.loading = "lazy";
const time = document.createElement("div");
time.className = "video-time-thumb";
time.textContent = video.duration;
const title = document.createElement("div");
title.className = "video-card-title";
title.textContent = video.title;
const channel = document.createElement("a");
channel.className = "video-card-infs channel-name-inf";
channel.textContent = video.channelName;
if (video.channelUrl) {
channel.href = "https://www.youtube.com" + video.channelUrl;
} else {
channel.className += " no-link"
}
const infs = document.createElement("div");
infs.className = "video-card-infs";
if (video.views) {
const views = document.createElement("span");
views.textContent = video.views;
infs.appendChild(views);
const dot = document.createElement("span");
dot.className = "delimiter";
dot.textContent = "•";
infs.appendChild(dot);
}
if (video.published) {
const date = document.createElement("span");
date.textContent = video.published;
infs.appendChild(date);
}
thumbDiv.append(img, time);
a.append(thumbDiv, title, channel, infs);
laterDiv.appendChild(a);
});
console.log(`[Do not watch later] ${contentsList.length} videos added to the carousel`);
}
setTimeout(updateButtons, 300);
} catch (err) {
console.error("[Do not watch later] Error loading WL:", err);
btnRight.disabled = true;
}
// ── Normal feed title ────────────────────
contents.before(feedH2);
}, 800); };
// ── End of main function ────────────────────
// Listen to YouTube SPA navigations
window.addEventListener('yt-navigate-finish', () => {
console.log("[Do not watch later] Navigation detected - pathname:", window.location.pathname);
if (isHomePage()) {
console.log("[Do not watch later] calling initCarousel");
initCarousel();
} else {
console.log("[Do not watch later] It's not home → cleaning carousel");
document.querySelectorAll('.carousel-wrapper, .script-title').forEach(el => el.remove());
}
});
})();