您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一个自定弹幕控制面板,按【P】键可以弹出面板
// ==UserScript== // @name B站自定义弹幕控制面板 // @license MIT // @namespace http://tampermonkey.net/ // @version 1.0 // @description 一个自定弹幕控制面板,按【P】键可以弹出面板 // @author Simon // @match https://live.bilibili.com/* // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function () { 'use strict'; // 添加 资源 链接 const elementCss = document.createElement('link'); elementCss.rel = 'stylesheet'; elementCss.href = 'https://cdn.staticfile.org/element-ui/2.15.13/theme-chalk/index.min.css'; let danmakuElem = null; let msgQueue = []; //消息冷却时间 var msgQueueCooldown = 3; // elementCss.onload = function () { // const elementJS = document.createElement('script'); // elementJS.src = 'https://unpkg.com/[email protected]/lib/index.js'; // document.head.appendChild(elementJS); // }; document.head.appendChild(elementCss); const vueJS = document.createElement('script'); // vueJS.src = 'https://unpkg.com/vue@2/dist/vue.js'; vueJS.src = 'https://cdn.staticfile.org/vue/2.7.14/vue.min.js'; vueJS.onload = function () { const elementJS = document.createElement('script'); elementJS.src = 'https://cdn.staticfile.org/element-ui/2.15.13/index.min.js'; elementJS.onload = function () { createVueInstance(); }; document.head.appendChild(elementJS); }; document.head.appendChild(vueJS); var customStyles = document.createElement('style'); customStyles.type = 'text/css'; // 添加自定义 CSS 样式 customStyles.innerHTML = ` /* 自定义 CSS 样式 */ .row { padding-top: 20px; padding-left: 10px; padding-right: 10px; } .color0{ /*background-color: #2b46ff40;*/ background-color: #109d0040; } .color1{ background-color: #ffd90c40; } .color0mved{ background-color: #2b46ff40; } .color1mved{ background-color: #2b46ff40; } `; /** * 获取元素 * @param selector * @param callback */ function findVueElem(selector, callback) { // 弹幕输入框model所在元素获取任务定时器 var times = 0; var findElemTimer = setInterval(() => { var elem = document.querySelector(selector); if (times > 20 || (elem && elem.__vue__)) { callback(elem); clearInterval(findElemTimer) } else { console.warn("获取弹幕元素失败") } times++ }, 1000) } findVueElem("#control-panel-ctnr-box", (res) => { danmakuElem = res; }); function sendArr(arr, finish = true) { var times = 0; var timer = setInterval(() => { var elem = danmakuElem; if (elem && elem.__vue__) { let data = elem.__vue__.$data; console.log("输出"); console.log(data); if (data && arr.length > 0) { let or_msg = data.chatInput;//记录原来的弹幕内容 data.chatInput = arr.shift().substring(0, 20) elem.__vue__.sendDanmaku() data.chatInput = or_msg;//还原本来的弹幕内容 } if (arr.length === 0 && finish) { clearInterval(timer) } } else if (times < 20) { console.warn("danmakuElem 为空") } else { console.warn("消息队列因获取不到弹幕输入框model终止") clearInterval(timer) } times++ }, msgQueueCooldown * 1000) } // 消息队列数据发送 sendArr(msgQueue, false) // 将 <style> 元素添加到 <head> 元素中 document.head.appendChild(customStyles); var config = GM_getValue("config"); if (!config) { config = []; } function createVueInstance() { const vueDiv = document.createElement('div'); vueDiv.id = "vue_tm"; vueDiv.innerHTML = ` <el-dialog title="编辑面板" :visible.sync="editPanelVisible" width="50%" :before-close="handleCloseEditPane"> <el-form :inline="true" style="display: flex; flex-wrap: wrap;"> <span v-for="(item, index) in elements" :key="index" class="row"> <el-card :class="['color' + index%2 +getValDefaultEmpty(item.mved)]"> <el-form-item> <el-switch v-model="item.input" active-text="附加指令"></el-switch> </el-form-item> <el-form-item> <el-input v-if="item.input" v-model="item.msg" placeholder="附加指令" readonly></el-input> </el-form-item> <el-form-item label="指令"> <el-input v-model="item.fixedMsg" placeholder="指令"></el-input> </el-form-item> <el-form-item> <el-switch v-if="item.input" v-model="item.isClear" active-text="执行后清空"></el-switch> </el-form-item> <el-form-item label="按钮标题"> <el-input v-model="item.title" placeholder="按钮标题"></el-input> </el-form-item> <el-form-item> <el-button type="primary">{{item.title}}</button> </el-form-item> <el-row> <el-button-group> <el-button title="置顶" @click="moveArrayElement(index,0)" icon="el-icon-top"></el-button> <el-button title="上移" @click="moveArrayElement(index,index-1)" icon="el-icon-arrow-up"></el-button> <el-button title="下移" @click="moveArrayElement(index,index+1)" icon="el-icon-arrow-down"></el-button> <el-button title="置底" @click="moveArrayElement(index,elements.length-1)" icon="el-icon-bottom"></el-button> </el-button-group> <el-button type="danger" icon="el-icon-delete" circle @click="removeElement(index)"></el-button> </el-row> </el-card> </span> </el-form> <br /> <el-row class="row"> <el-col :span="4"> <!--<el-switch v-model="hasbtn" active-text="添加按钮"></el-switch>--> <label>功能名称</label> </el-col> <el-col :span="16"> <el-input v-model="title" placeholder="请输入功能名称"></el-input> </el-col> </el-row> <el-row class="row"> <el-col :span="8"> <el-switch v-model="hasinput" active-text="附加指令"></el-switch> </el-col> <el-col :span="8"> <el-switch v-if="hasinput" v-model="isClear" active-text="执行后清空"></el-switch> </el-col> </el-row> <el-row class="row"> <el-col :span="4"> <label>固定指令</label> </el-col> <el-col :span="20"> <el-input v-model="fixedMsg" placeholder="固定指令"></el-input> </el-col> </el-row> <el-row class="row"> <el-button type="primary" icon="el-icon-plus" @click="addElement()">添加</el-button> </el-row> <div class="row"> <el-button type="success" icon="el-icon-check" @click="save()">保存</button> </div> </el-dialog> <el-dialog title="命令面板" :visible.sync="cmdPanelVisible" width="50%" :before-close="handleCloseCmdPanel"> <div style=" flex-wrap: wrap;width:100%"> <div v-for="(item, index) in elements" :key="index" class="row"> <el-card :class="['color' + index%2 + getValDefaultEmpty(item.mved)]"> <el-row> <el-col :span="16"> <el-row> <el-col :span="12">{{item.fixedMsg}}</el-col> <el-col :span="12" v-if="item.input"> <el-input v-model="item.msg" style="min-width:100px" placeholder="附加指令"></el-input> </el-col> </el-row> </el-col> <el-col :span="8"> <div style="padding-left:8px"> <el-button type="primary" @click="sendMsg(item)">{{item.title}}</button> </div> </el-col> </el-row> </el-card> </div> <div> </el-dialog> `; document.body.appendChild(vueDiv); // const bodyParent = document.body.parentNode; // 将挂载点插入到 <body> 元素的父元素中 // bodyParent.insertBefore(vueDiv, document.body.nextSibling); console.log(document.getElementById("vue_tm")); var vueObj = null; vueObj = new Vue({ el: '#vue_tm', // 将 Vue 应用挂载到 id 为 'vue_tm' 的 div 上 data: { editPanelVisible: false, cmdPanelVisible: false, msg: "", title: "", fixedMsg: "", hasinput: false, hasbtn: false, isClear: false, elements: [{ title: "切歌", msg: "信息", input: true, btn: true }, { title: "切歌2", msg: "信息", input: true, btn: true } ] }, mounted: function () { // Vue 初始化完成后执行的代码 this.elements = config; this.$nextTick(function () { // Vue 更新 DOM 后执行的代码 }); }, methods: { getValDefaultEmpty(val) { if (val) { return val; } else { return ""; } }, openEditPanel() { this.editPanelVisible = true; }, openCmdPanel() { this.cmdPanelVisible = true; }, handleCloseEditPane(done) { done(); }, handleCloseCmdPanel(done) { done(); }, changeMessage() { this.message = '消息已改变!'; }, sendMsg(item) { console.log(item); msgQueue.push(item.fixedMsg + item.msg); if (item.isClear) { item.msg = ""; } this.$message({ showClose: true, message: '操作成功', type: 'success' }); }, addElement() { this.elements.push( { mved: "", title: this.title, msg: this.msg, input: this.hasinput, fixedMsg: this.fixedMsg, isClear: this.isClear // btn: this.hasbtn }); }, removeElement(index) { try { this.elements.splice(index, 1); } catch (err) { this.$message.error('出错了'); console.error(err); } }, save() { GM_setValue("config", this.elements); this.$message({ showClose: true, message: '保存成功', type: 'success' }); }, moveArrayElement(fromIndex, toIndex) { if (toIndex < 0) { //已到达顶部 this.$message({ message: '已到达顶部', type: 'warning' }); return; } if (toIndex >= this.elements.length) { //已到达底部 this.$message({ message: '已到达底部', type: 'warning' }); return; } if (toIndex >= this.elements.length) { let k = toIndex - this.elements.length + 1; while (k--) { this.elements.push(undefined); } } this.elements.splice(toIndex, 0, this.elements.splice(fromIndex, 1)[0]); this.elements[toIndex].mved = "mved"; setTimeout(function (that) { that.elements[toIndex].mved = ""; that.$forceUpdate(); console.log(that.elements[toIndex].mved ); }, 500,this); } } }); console.log("################################### Vue #######################"); // 注册配置面板菜单 GM_registerMenuCommand('#️⃣ 打开命令面板', function () { vueObj.openCmdPanel(); }); GM_registerMenuCommand('#️⃣ 打开配置面板', function () { vueObj.openEditPanel(); }); function handleKeyDown(event) { // 检查是否按下了 Ctrl+Shift+D 组合键 //if (event.ctrlKey && event.shiftKey &&( event.key === 'f'|| event.key === 'F')) { if (( event.key.toLocaleUpperCase() === 'P')) { if(event.ctrlKey||event.shiftKey||event.altKey||event.winKey||event.metaKey){ console.log("按下了控制键") return; } if(document.activeElement === document.querySelector('textarea.chat-input')){ return; } console.log("按下键盘"); event.preventDefault(); if(vueObj){ vueObj.openCmdPanel(); } } } document.addEventListener('keydown', handleKeyDown); } })();