YoutubeDLC

YouTube独轮车

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         YoutubeDLC
// @description  YouTube独轮车
// @namespace    http://tampermonkey.net/
// @version      2026-07-18
// @author       奶糖花的绿冻兄弟
// @match https://www.youtube.com/watch*
// @match https://www.youtube.com/live_chat*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @grant GM_setValue
// @grant GM_getValue
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';


    if (location.pathname === "/watch") {


        const observer = new MutationObserver(() => {

            if (document.querySelector("ytd-live-chat-frame")) {

                observer.disconnect();

                const panel = createPanel();

                createToggle(panel);

            }

        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

    }

    if (location.pathname === "/live_chat") {
        GM_addValueChangeListener("SM2RR", (name, oldValue, newValue) => {
            sendMessageToRealChatRoom(newValue.msg);
        });
    }

    function createToggle(panel){

        const toggleBtn = document.createElement("button");

        toggleBtn.textContent = "独轮车";
        toggleBtn.style.display = "flex";
        toggleBtn.style.justifyContent = "center"; // 水平居中
        toggleBtn.style.alignItems = "center";// 垂直居中
        toggleBtn.style.position = "fixed";
        toggleBtn.style.right = "0px";
        toggleBtn.style.bottom = "5px";
        toggleBtn.style.width = "60px";
        toggleBtn.style.height = "25px";
        toggleBtn.style.cursor = "pointer";
        toggleBtn.style.backgroundColor = "red";
        toggleBtn.style.zIndex = 999999;
        toggleBtn.style.display = "flex";

        toggleBtn.onclick = () => {

            if (panel.style.display === "none") {
                panel.style.display = "flex"
            }else{
                panel.style.removeProperty("left");
                panel.style.removeProperty("top");

                panel.style.right = "20px";
                panel.style.bottom = "35px";
            }

        };


        document.body.appendChild(toggleBtn);

    }

    function createPanel(){
        const Panel = document.createElement("div");
        Panel.style.position = "fixed";
        Panel.style.right = "20px";
        Panel.style.bottom = "35px";
        Panel.style.width = "280px";
        Panel.style.background = "#222";
        Panel.style.color = "white";
        Panel.style.padding = "0px";
        Panel.style.borderRadius = "0px";
        Panel.style.display = "none";
        Panel.style.flexDirection = "column";
        Panel.style.zIndex = 999999;

        const TopRow = document.createElement("div");
        TopRow.style.display = "flex";
        TopRow.style.width = "100%";

        const title = document.createElement("div");
        title.textContent = "YouTube DLC";
        title.style.cursor = "move";
        title.style.padding = "8px";
        title.style.background = "red";
        title.style.flex = "0.9";

        const btnHide = document.createElement("button");
        btnHide.textContent = "-";
        btnHide.style.flex = "0.1";

        btnHide.onclick = () => {

            Panel.style.display = "none";

        };

        TopRow.append(title, btnHide);

        Panel.prepend(TopRow);

        //panel move

        let dragging = false;
        let offsetX = 0;
        let offsetY = 0;

        const mask = document.createElement("div");
        mask.style.position = "fixed";
        mask.style.left = "0";
        mask.style.top = "0";
        mask.style.width = "100vw";
        mask.style.height = "100vh";
        mask.style.zIndex = "999998";
        mask.style.background = "transparent";

        title.addEventListener("pointerdown", (e) => {

            document.body.appendChild(mask);
            dragging = true;

            const rect = Panel.getBoundingClientRect();

            // 转换为 left/top 定位
            Panel.style.left = rect.left + "px";
            Panel.style.top = rect.top + "px";

            Panel.style.right = "auto";
            Panel.style.bottom = "auto";

            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;

            e.preventDefault();
        });


        document.addEventListener("pointermove", (e) => {

            if (!dragging) return;

            Panel.style.left = (e.clientX - offsetX) + "px";
            Panel.style.top = (e.clientY - offsetY) + "px";

        });


        document.addEventListener("pointerup", () => {

            dragging = false;

            mask.remove();
        });

        //






        const pageDlc= createDlcUI();

        const pageRandomDlc = createRandomDlcUI();

        const menu = createMenuUI(pageDlc,pageRandomDlc);

        Panel.appendChild(menu);

        Panel.appendChild(pageDlc);
        Panel.appendChild(pageRandomDlc);



        document.body.appendChild(Panel);

        return Panel;
    }

    function createMenuUI(page1,page2){
        const menu = document.createElement("div");
        menu.style.padding = "10px 10px 5px 10px";
        menu.style.display = "flex";
        menu.style.borderBottom = "1px solid #555";

        const btnDLC = document.createElement("button");
        btnDLC.textContent = "独轮车";

        const btnAuto = document.createElement("button");
        btnAuto.textContent = "随机独轮车";

        const btnSetting = document.createElement("button");
        btnSetting.textContent = "敬請期待";

        [btnDLC, btnAuto, btnSetting].forEach(btn => {

            btn.style.flex = "1";// 平分宽度
            btn.style.padding = "8px";
            btn.style.cursor = "pointer";

        });

        btnDLC.onclick = () => {
            changeShowHide(page1,page2);
        };

        btnAuto.onclick = () => {
            changeShowHide(page2,page1);
        };

        btnSetting.onclick = () => {
        };


        menu.append(btnDLC, btnAuto, btnSetting);

        return menu;
    }

    function createDlcUI() {
        //panel
        const pageDLC = document.createElement("div");
        pageDLC.style.padding = "5px 10px 10px 10px";
        pageDLC.style.display = "flex";
        pageDLC.style.flexWrap = "wrap";

        const input = document.createElement("input");

        input.type = "text";
        input.placeholder = "输入弹幕";

        input.style.width = "100%";
        input.style.padding = "10px";
        input.style.boxSizing = "border-box";

        pageDLC.appendChild(input);

        const button = document.createElement("button");
        button.textContent = "发送";
        button.style.padding = "10px";
        button.style.cursor = "pointer";
        button.style.width = "100%";
        pageDLC.appendChild(button);
        //
        const inputStyle = document.createElement("style");

        inputStyle.textContent = `input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button {display: none;}`;
        document.head.appendChild(inputStyle);

        const CDinput = document.createElement("input");
        CDinput.type = "number";
        CDinput.style.padding = "10px";
        CDinput.placeholder = "冷却秒数";
        CDinput.min = 0.01;
        CDinput.max = 100000;
        CDinput.style.minWidth = "0";

        const DLCbutton = document.createElement("button");
        DLCbutton.textContent = "独轮车启动";
        DLCbutton.style.padding = "10px";
        DLCbutton.style.cursor = "pointer";

        const row = document.createElement("div");
        row.style.display = "flex";
        row.style.width = "100%";

        CDinput.style.width = "30%";
        DLCbutton.style.width = "70%";


        row.append(CDinput, DLCbutton);

        pageDLC.appendChild(row);
        //
        button.onclick = () => {
            GM_setValue("SM2RR", { msg: input.value, time: Date.now() });
        };

        let timer = null;

        DLCbutton.onclick = () => {

            const value = CDinput.valueAsNumber;

            if (Number.isNaN(value)) {
                return;
            }

            if (timer) {
                // 已经在运行,不重复启动
                clearInterval(timer);
                timer = null;
                DLCbutton.textContent = "独轮车启动";
                DLCbutton.style.backgroundColor = "white";
                return;
            }


            DLCbutton.textContent = "独轮车启动中";
            DLCbutton.style.backgroundColor = "red";

            GM_setValue("SM2RR", { msg: input.value, time: Date.now() });

            timer = setInterval(() => {

                console.log("T发送");
                GM_setValue("SM2RR", { msg: input.value, time: Date.now() });

            }, CDinput.value*1000); // 每 * 秒发送一次

        };

        return pageDLC;
    }

    function createRandomDlcUI() {

        const groups = [ ["组一随机一", "组一随机二","组一随机三"], ["组二随机一", "组二随机二", "组二随机三"], ["组三随机一", "组三随机二", "组三随机三"] ];


        const page = document.createElement("div");
        page.style.display = "none";
        page.style.flexDirection = "column";
        page.style.gap = "10px";
        page.style.padding = "10px";

        function createRow() {

            const row = document.createElement("div");
            row.style.display = "flex";
            row.style.gap = "8px";
            row.style.alignItems = "center";

            const select = document.createElement("select");
            select.style.flex = "1";
            select.style.flex = "1";
            select.style.minWidth = "0";// 允许缩小,不会被内容撑开
            select.style.width = "0";// 配合 flex 使用
            select.style.textAlign = "left";// 文字靠左

            const addBtn = document.createElement("button");
            addBtn.textContent = "新增";

            const delBtn = document.createElement("button");
            delBtn.textContent = "删除当前";

            row.append(select, addBtn, delBtn);

            return {
                row,
                select,
                addBtn,
                delBtn
            };
        }

        function refreshGroupItems(selectC, group) {

            selectC.options.length = 0;

            group.forEach((item, index) => {

                let text;

                if (item === "") {
                    text = "(空)";
                } else if (item.length > 10) {
                    text = item.slice(0, 10) + "...";
                } else {
                    text = item;
                }

                selectC.add(new Option(text, index));
            });


        }

        function refreshGrops(selectG, selectC, groups) {

            selectG.options.length = 0;
            selectC.options.length = 0;

            // 填充组
            groups.forEach((group, index) => {
                selectG.add(new Option(`组 ${index + 1}`, index));
            });

        }

        function InitGrops(selectG, selectC, groups) {

            selectG.options.length = 0;
            selectC.options.length = 0;

            // 填充组
            groups.forEach((group, index) => {
                selectG.add(new Option(`组 ${index + 1}`, index));
            });

            // 默认显示第一组
            refreshGroupItems(selectC, groups[0]);

        }




        // 第一行
        const row1 = createRow();



        // 第二行
        const row2 = createRow();

        row1.select.onchange = () => {
            refreshGroupItems(row2.select, groups[row1.select.selectedIndex]);
        };

        InitGrops(row1.select, row2.select, groups);

        // 第三行
        const textarea = document.createElement("textarea");
        textarea.placeholder = "请输入内容...";
        textarea.style.width = "100%";
        textarea.style.height = "180px";
        textarea.style.resize = "vertical";
        textarea.style.boxSizing = "border-box";

        page.append(row1.row,row2.row,textarea);

        const CDinput = document.createElement("input");
        CDinput.type = "number";
        CDinput.style.padding = "10px";
        CDinput.placeholder = "冷却秒数";
        CDinput.min = 0.01;
        CDinput.max = 100000;
        CDinput.style.minWidth = "0";

        const DLCbutton = document.createElement("button");
        DLCbutton.textContent = "独轮车启动";
        DLCbutton.style.padding = "10px";
        DLCbutton.style.cursor = "pointer";

        const DLCrow = document.createElement("div");
        DLCrow.style.display = "flex";
        DLCrow.style.width = "100%";

        CDinput.style.width = "30%";
        DLCbutton.style.width = "70%";


        DLCrow.append(CDinput, DLCbutton);

        page.appendChild(DLCrow);



        row1.addBtn.onclick = () => {

            groups.push([""]);

            refreshGrops(row1.select, row2.select, groups);

            row1.select.selectedIndex = groups.length - 1;

            refreshGroupItems(row2.select, groups[row1.select.selectedIndex]);
            // 自动选中新建的组


        };

        row2.addBtn.onclick = () => {

            const groupIndex = row1.select.selectedIndex;
            if (groupIndex < 0) return;

            // 新增一个空字符串
            groups[groupIndex].push("");

            // 刷新第二个下拉框
            refreshGroupItems(row2.select, groups[groupIndex]);

            // 自动选中新建项
            row2.select.selectedIndex = groups[groupIndex].length - 1;

            // 清空编辑框
            textarea.value = "";

            // 聚焦到输入框
            textarea.focus();
        };

        row2.select.onchange = () => {

            const groupIndex = Number(row1.select.value);
            const msgIndex = Number(row2.select.value);

            textarea.value = groups[groupIndex][msgIndex];


        };

        textarea.oninput = () => {

            const groupIndex = Number(row1.select.value);
            const msgIndex = Number(row2.select.value);

            if (groups[groupIndex] && groups[groupIndex][msgIndex] !== undefined) {
                groups[groupIndex][msgIndex] = textarea.value;
            }

            row2.select.options[msgIndex].text = textarea.value.slice(0, 10);
        };

        row1.delBtn.onclick = () => {

            const groupIndex = row1.select.selectedIndex;

            if (groupIndex < 0) return;

            groups.splice(groupIndex, 1);

            refreshGrops(row1.select, row2.select, groups);

            if (groups.length > 0) {
                row1.select.selectedIndex = Math.min(groupIndex, groups.length - 1);
                refreshGroupItems(row2.select, groups[row1.select.selectedIndex]);
            } else {
                row2.select.innerHTML = "";
                textarea.value = "";
            }
        };

        row2.delBtn.onclick = () => {

            const groupIndex = row1.select.selectedIndex;
            const itemIndex = row2.select.selectedIndex;

            if (groupIndex < 0 || itemIndex < 0) return;

            // 从数组删除
            groups[groupIndex].splice(itemIndex, 1);

            // 刷新第二个下拉框
            refreshGroupItems(row2.select, groups[groupIndex]);

            // 清空 textarea(如果已经没有内容)
            if (groups[groupIndex].length === 0) {
                textarea.value = "";
            } else {
                // 重新选中一项
                row2.select.selectedIndex = Math.min(itemIndex, groups[groupIndex].length - 1);
                textarea.value = groups[groupIndex][row2.select.selectedIndex];
            }
        };


        let timer = null;
        DLCbutton.onclick = () => {

            const value = CDinput.valueAsNumber;

            if (Number.isNaN(value)) {
                return;
            }

            if (timer) {
                // 已经在运行,不重复启动
                clearInterval(timer);
                timer = null;
                DLCbutton.textContent = "独轮车启动";
                DLCbutton.style.backgroundColor = "white";
                return;
            }


            DLCbutton.textContent = "独轮车启动中";
            DLCbutton.style.backgroundColor = "red";

            GM_setValue("SM2RR", { msg: groups[row1.select.selectedIndex][Math.floor(Math.random() * groups[row1.select.selectedIndex].length)], time: Date.now() });

            timer = setInterval(() => {

                console.log("DLC发送"+groups[row1.select.selectedIndex][Math.floor(Math.random() * groups[row1.select.selectedIndex].length)]);
                GM_setValue("SM2RR", { msg: groups[row1.select.selectedIndex][Math.floor(Math.random() * groups[row1.select.selectedIndex].length)], time: Date.now() });

            }, CDinput.value*1000); // 每 * 秒发送一次

        };

        return page;
    }

    function sendMessageToRealChatRoom(msg) {

        const chatInput = document.querySelector(
            'yt-live-chat-text-input-field-renderer div[contenteditable]'
        );

        const sendButton = document.querySelector("#send-button button");

        if (!chatInput || !sendButton) {
            console.log("没找到聊天框");
            return;
        }

        chatInput.focus();

        chatInput.textContent = msg;

        chatInput.dispatchEvent(new InputEvent("input", {
            bubbles: true
        }));

        sendButton.click();
    }

    function showPage(pageShow,pageHide1,pageHide2) {

    pageShow.style.display = "block";
    pageHide1.style.display = "none";
    pageHide2.style.display = "none";


}

    function changeShowHide(pageShow,pageHide){
        pageShow.style.display = "flex";
        pageHide.style.display = "none";
    }


})();