讓 YouTube 的推薦影片變回4個一行

這個腳本會將YouTube的推薦影片從三個一排,變回四個一排

// ==UserScript==
// @name         讓 YouTube 的推薦影片變回4個一行
// @name:en      Change YouTube's recommended videos back to 4 rows
// @namespace    http://tampermonkey.net/
// @version      2025-05-11 1.10
// @description  這個腳本會將YouTube的推薦影片從三個一排,變回四個一排
// @description:en This script will change YouTube's recommended videos from three in a row to four in a row
// @author       bahamutID:ra45388791
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wayne-blog.com
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    let IronIndex = 0
    let VideoCount = 0
    const rowBtnCount = 4
    let lastPath = ""
    // let settime

    new MutationObserver((e) => {

        if (location.pathname !== "/") {
            //切換頁面行為重置影片數量觸發機制
            if (document.VideoCount !== 0) {
                document.VideoCount = 0
            }
            return;
        }
        let cnt = document.querySelector("#contents");

        //狀態檢查
        //是否渲染過
        if (cnt.dataset.isActiveScript === "true") {
            //是否變更過推薦類別
            // if (IronIndex === activeIronIndex) {
            //     console.log("推薦中止")
            //     return;
            // }
            //影片數量是否不同
            if (cnt.children.length === document.VideoCount) {
                //第8位是否是非影片按鈕
                if (cnt.children[8].tagName === "YTD-RICH-SECTION-RENDERER") {
                    return;
                }
            }
        }
        //設定狀態
        cnt.dataset.isActiveScript = "true"
        // IronIndex = activeIronIndex
        cnt.style = `--ytd-rich-grid-items-per-row: ${rowBtnCount};`

        if (document.settime !== undefined) {
            clearInterval(document.settime)
        }
        //確保頁面渲染完成
        document.settime = setInterval(() => {
            // const lastVideoSrc = cnt.children[0].querySelector("#thumbnail yt-image img")
            // console.log("1:" + lastVideoSrc)
            // console.log("2:" + document.lastVideo)
            // if (document.lastVideo !== lastVideoSrc) {
            //     document.VideoCount = 0
            //     document.lastVideo = lastVideoSrc
            // }


            checkStructure(cnt)
            clearInterval(document.settime)
        }, 500);

    }).observe(document, {
        childList: true,
        // attributes: true,
        subtree: true
    });

})();


function checkStructure(cnt) {
    let cntBtns = cnt.children

    //第8位沒有 isSet 判斷結構改變
    if (cnt.children.length > 8 && cnt.children[8].dataset.isSet !== "true") {
        const tagCount = Array.from(cnt.children).filter(e => { return e.tagName === "YTD-RICH-SECTION-RENDERER" }).length
        if (tagCount !== 0) {
            document.VideoCount = 0
        }
    }
    //影片數量是否不同
    if (cnt.children.length === document.VideoCount) {
        return;
    }

    if (cntBtns.length > 4) {
        let count = cnt.children.length

        //影片數量變更時重新定位
        for (let btn of cntBtns) {
            if (btn.dataset.isSet === "true") {
                btn.dataset.isSet = ""
            }
        }
        document.VideoCount = count
        //移動節點
        moveElement()
    }
}



//取得目前推薦類別序號
function getIronIndex() {
    const irons = document.querySelectorAll("#chips > yt-chip-cloud-chip-renderer")

    for (let i = 0; i < irons.length; i++) {
        const haveClass = irons[i].classList.contains("iron-selected")
        if (haveClass) {
            return i + 1
        }
    }
    return 0
}

function moveElement() {
    let cnt = document.querySelector("#contents").children;
    let count = 0;   //待移動節點數量
    if (cnt.length < 9) { return }

    //把節點移到最後
    for (let i = 0; i < cnt.length; i++) {
        const e = cnt[i];
        if (e.tagName === "YTD-RICH-SECTION-RENDERER") {
            if (e.dataset.isSet !== "true") {       //已定位不允許再移動
                e.parentNode.insertBefore(e, cnt[cnt.length - 1])
                count++
            }
        }
    }

    //移動節點
    let setIndex = 0
    for (let i = 0; i < count; i++) {
        let index
        //紀錄需要移動的節點
        for (let j = setIndex; j < cnt.length; j++) {
            const e = cnt[j];
            if (e.tagName === "YTD-RICH-SECTION-RENDERER") {
                if (e.dataset.isSet !== "true") {   //已定位不允許再移動
                    index = j
                    break;
                }
            }
        }

        //以8個為一組
        setIndex += 8 + i

        const targetIndex = cnt[index]
        //將 targetIndex 移動到 setIndex 之前
        cnt[0].parentNode.insertBefore(targetIndex, cnt[setIndex])
        //該節點狀態設為已定位
        targetIndex.dataset.isSet = "true"
    }
}