// ==UserScript==
// @name 快捷弹幕
// @namespace http://tampermonkey.net/
// @version 0.5.5
// @description B站直播间发送快捷弹幕
// @author mianju
// @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @require https://cdn.staticfile.org/axios/0.27.2/axios.min.js
// @grant none
// ==/UserScript==
(function () {
function main() {
initLib()
initCss()
waitForLoaded(() => {
initUi()
})
}
function initLib() {
let scriptElement = document.createElement('script')
scriptElement.src = 'https://cdn.staticfile.org/vue/2.6.9/vue.min.js'
document.head.appendChild(scriptElement)
let linkElement = document.createElement('link')
linkElement.rel = 'stylesheet'
linkElement.href = 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/theme-chalk/index.css'
document.head.appendChild(linkElement)
scriptElement = document.createElement('script')
scriptElement.src = 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/index.js'
document.head.appendChild(scriptElement)
}
function initCss() {
let css = `
.el-select .el-input {
width: 80px;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
`
let styleElement = document.createElement('style')
styleElement.innerText = css
document.head.appendChild(styleElement)
}
function waitForLoaded(callback, timeout = 10 * 1000) {
let startTime = new Date()
function poll() {
if (isLoaded()) {
callback()
return
}
if (new Date() - startTime > timeout) {
return
}
setTimeout(poll, 1000)
}
poll()
}
function isLoaded() {
if (window.ELEMENT === undefined) {
return false
}
if (document.querySelector('#control-panel-ctnr-box') === null) {
return false
}
return true
}
function initUi() {
var father = document.getElementsByClassName('icon-left-part')[0];
let quickDanmakuElement = document.createElement('div');
father.appendChild(quickDanmakuElement);
new Vue({
el: quickDanmakuElement,
template: `
<span>
<template style="width: 200px" >
<el-select v-model="value" placeholder="选择" @change="sendDanmaku" style="width: 80px;height: 24px" size="mini">
<el-option-group
v-for="group in options"
:key="group.label"
:label="group.label">
<el-option
v-for="danmaku in group.danmakus"
:key="danmaku.label"
:label="danmaku.label"
:value="danmaku.label">
</el-option>
</el-option-group>
</el-select>
<el-button icon="el-icon-message" size="mini" circle @click="sendDanmaku(value)"></el-button>
<el-popover
placement="bottom"
title="快捷弹幕设置"
width="300px"
trigger="click"
>
<span class="demonstration">添加: </span>
<el-tooltip class="item" effect="dark" content="添加表情包教程:https://bbs.nga.cn/read.php?pid=594434351&opt=128" placement="top">
<el-input placeholder="请输入内容" v-model="danmaku4add" class="input-with-select" maxlength="20" size="mini" style="width: 300px">
<el-select v-model="select" slot="prepend" placeholder="请选择">
<el-option label="当前" value="cur"></el-option>
<el-option label="所有" value="all"></el-option>
</el-select>
<el-button slot="append" icon="el-icon-check" @click="addDanmaku" size="mini"></el-button>
</el-input>
</el-tooltip>
<div class="block" style="margin-top: 10px">
<span class="demonstration">删除: </span>
<el-cascader
size="mini"
v-model="danmakus4del"
:options="options"
:props="{ expandTrigger: 'hover','children': 'danmakus','multiple': 'true','emitPath':'false' }"
></el-cascader>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="delDanmakus"></el-button>
</div>
<el-button slot="reference" icon="el-icon-setting" size="mini" circle></el-button>
<div style="margin-top: 10px">
自动发送:
<el-radio-group v-model="freq" size="mini">
<el-radio-button label="高频"></el-radio-button>
<el-radio-button label="正常"></el-radio-button>
<el-radio-button label="低频"></el-radio-button>
</el-radio-group>
<el-switch
size="mini"
v-model="value4switch"
:disabled="flag4switch"
@change="autoSend">
</el-switch>
</div>
</el-popover>
</template>
</span>
`,
data: {
options: [
{
label: '所有直播间',
value: 'all',
danmakus: []
},
{
label: '当前直播间',
value: 'cur',
danmakus: []
}
],
value: '',
danmaku4add: '',
select: 'cur',
danmakus4del: [],
roomId: 0,
allRoomDanmakus: [],
curRoomDanmakus: [],
key4all: 'allRoomDanmukus',
key4cur: '',
value4switch: false,
interval: null,
freq: '正常',
freqopts: {'高频':[30,6000],'正常':[15,12000],'低频':[10,18000]},
chatInput: null
},
methods: {
"delDanmakus": function () {
function del(val,danmakus) {
let index = danmakus.indexOf(val);
danmakus.splice(index,1);
};
for (let i=0;i<this.danmakus4del.length;i++){
let danmaku = this.danmakus4del[i];
if (danmaku[0] == "cur") {
del(danmaku[1],this.curRoomDanmakus);
} else {
del(danmaku[1],this.allRoomDanmakus);
}
}
localStorage.setItem(this.key4cur,JSON.stringify(this.curRoomDanmakus));
localStorage.setItem(this.key4all,JSON.stringify(this.allRoomDanmakus));
this.danmakus4del = [];
this.$message.success("删除成功");
this.handleDanmakus();
},
"getRoomId": function () {
if (window.__NEPTUNE_IS_MY_WAIFU__) {
this.roomId = window.__NEPTUNE_IS_MY_WAIFU__.roomInfoRes.data.room_info.room_id;
} else {
let url = document.URL;
var re = /\/\d+/.exec(url);
this.roomId = re[0].substr(1);
}
},
"getDanmakus": function (key) {
if (localStorage.getItem(key)){
return JSON.parse(localStorage.getItem(key));
}
return [];
},
"handleDanmakus": function () {
function handle(danmakus4opt,danmakus4room){
danmakus4opt.splice(0);
for (let i=0;i<danmakus4room.length;i++) {
danmakus4opt.push({label:danmakus4room[i],value:danmakus4room[i]});
}
}
handle(this.options[0].danmakus,this.allRoomDanmakus);
handle(this.options[1].danmakus,this.curRoomDanmakus);
},
"addDanmaku": function () {
function add(key,danmakus,danmaku){
danmakus.push(danmaku);
localStorage.setItem(key,JSON.stringify(danmakus));
};
if (this.select == "cur") {
add(this.key4cur,this.curRoomDanmakus,this.danmaku4add);
} else {
add(this.key4all,this.allRoomDanmakus,this.danmaku4add);
}
this.danmaku4add = '';
this.$message.success("添加成功");
this.handleDanmakus();
},
"sendDanmaku": function (danmaku4send) {
var patt = /(room|official)(_\d+){1,2}/
var jct = getCookie('bili_jct');
var date = new Date();
var data = new FormData();
data.append('bubble',0);
data.append('color',16777215);
data.append('fontsize',25);
data.append('mode',1);
data.append('msg',danmaku4send);
data.append('rnd',parseInt(date.getTime()/1000));
data.append('roomid',this.roomId);
data.append('csrf',jct);
data.append('csrf_token',jct);
if (patt.test(danmaku4send)) {
data.set('msg',patt.exec(danmaku4send)[0])
data.append('dm_type',1);
}
let apiClient = axios.create({
baseURL: 'https://api.live.bilibili.com',
withCredentials: true
});
apiClient.post('/msg/send',data).then((res)=>{
if (res.data.code == 0) {
switch (res.data.msg) {
case '':
this.$message.success('发送成功 - ' + danmaku4send);
break;
/*case 'f':
this.$message.error('发送失败 - 包含B站屏蔽词: ' + danmaku4send);
break;
case 'k':
this.$message.error('发送失败 - 包含直播间屏蔽词: ' + danmaku4send);
break;*/
case 'same restriction':
this.$message.error('发送失败,该弹幕已被限制,请选择其它弹幕!');
break;
case 'max limit exceeded':
this.$message.error('发送失败,弹幕池达到上限!');
break;
default:
this.$message.error('发送失败 - ' + res.data.message);
}
} else {
this.$message.error('发送失败 - ' + res.data.message);
}
}).catch(()=>{
this.$message.error('发送失败 - ' + danmaku4send);
});
},
"autoSend": function(flag) {
if (flag) {
this.$message.info('自动发送已开启');
let count = 0;
let _this = this
let opt = this.freqopts[this.freq]
this.interval = setInterval(
()=>{
count++;
if (count == opt[0]) {
_this.value4switch = false;
clearInterval(_this.interval)
_this.$message.info('自动发送已关闭');
}
_this.sendDanmaku(_this.value);
},opt[1]
);
} else {
this.$message.info('自动发送已关闭');
clearInterval(this.interval);
}
},
"sendListener": function() {
var _this = this;
function keydown(event) {
if (event.keyCode == 13) {
if (_this.chatInput.chatInput != '') {
_this.sendDanmaku(_this.chatInput.chatInput);
}
setTimeout(_this.chatInput.clearInput, 10);
}
};
this.chatInput.onKeyDown = keydown;
}
} ,
created: function () {
this.getRoomId();
this.key4cur = this.roomId + '-danmukus';
this.curRoomDanmakus = this.getDanmakus(this.key4cur);
this.allRoomDanmakus = this.getDanmakus(this.key4all);
this.handleDanmakus();
this.chatInput = document.querySelector('#control-panel-ctnr-box').__vue__;
this.sendListener();
},
computed: {
flag4switch: function() {
return (this.value == '')
}
}
});
}
function getCookie(cname){
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name)==0) { return c.substring(name.length,c.length); }
}
return "";
}
main()
})();