YoutubeDLC

YouTube独轮车

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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";
    }


})();