YouTube独轮车
// ==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";
}
})();