나중에 보지 마세요!

"나중에 보기"에 저장된 동영상은 홈 화면에서 캐러셀로 표시됩니다

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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());
        }
    });
})();