// ==UserScript==
// @name B站直播间弹幕广播
// @namespace http://tampermonkey.net/
// @version 0.2
// @description B站直播间弹幕转发,需要用户已登录(不可用)。若有滥用等问题概不负责,诶嘿。顺便关注一下小东人鱼和noworld吧~
// @author 太陽闇の力
// @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @grant none
// @require https://gf.qytechs.cn/scripts/417560-bliveproxy/code/bliveproxy.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// 命令分析参考自 https://segmentfault.com/a/1190000017328813
//界面参考自小东人鱼午安社五更耗纸 https://github.com/gokoururi-git/gachihelper/
// 弹幕api使用参考自 https://gf.qytechs.cn/zh-CN/scripts/434726
// @license MIT
// ==/UserScript==
(function() {
//-----------配置区----------
//0默认收起,1默认展开
let isunfold = 0;
// 转发对象的UID,也即转发谁的弹幕
let uid = 353039514;
// 要转发的直播间号,也即发到哪儿
let rooms = `
9806022
2077803
431458
145657
`.replace(/ /g, '').trim().split('\n');
// 设置弹幕的发送间隔(秒),默认1秒,指转发到直播间A,然后再转发到直播间B,中间的时间间隔
//如果设置太小,会因发送频率过快而被B站吞掉弹幕,将自动重发并且自动设置发送间隔为1秒(如果本来是1秒,将设置成1.5秒)
let inter = 1;
//默认正则,带【】()[]()的弹幕都会被转发。
let regdef = "[【】()\\[\\]()]";
//默认自己为20字数限制,一般不改了(有需要的就找我反馈吧)
// 当前直播间号
const proomid = /(?!https:\/\/live.bilibili.com\/)\d+/.exec(window.location.href)[0];
//-----------UI区----------
let unfold = ["展开","收起"];
// 总容器
const container = window.document.createElement('div');
container.style.cssText = 'width:218px;position:fixed;bottom:5px;right:60px;z-index:999;box-sizing:border-box;';
// 工具名称
const topTool = window.document.createElement('div');
topTool.innerText = '弹幕转发';
topTool.style.cssText = 'text-align:center;line-height:20px;width:100%;color:rgb(210,143,166);font-size:14px;';
// 最小化按钮
const collapseButton = window.document.createElement('button');
collapseButton.innerText = unfold[isunfold];
collapseButton.style.cssText = 'float:right;width:40px;height:20px;border:none;cursor:pointer;background-color:#1890ff;border-radius:1px;color:#ffffff;';
// 主窗口
const mainWindow = window.document.createElement('div');
mainWindow.style.cssText = 'width:100%;background-color:rgba(220, 192, 221, .5);padding:10px;box-sizing:border-box;';
if(isunfold==0){
mainWindow.style.display = "none";
}
// 直播间号输入框
const textArea = window.document.createElement('textarea');
textArea.placeholder = '转发的直播间号,换行分隔'
textArea.style.cssText = 'width:100%;height:60px;resize:none;outline:none;background-color:rgba(255,255,255,.5);';
// 按钮区容器
const buttonArea = window.document.createElement('div');
buttonArea.style.cssText = 'width:100%;height:30px;box-sizing:border-box;display:flex;';
// 按钮区容器
const buttonArea2 = window.document.createElement('div');
buttonArea2.style.cssText = 'width:100%;height:28;box-sizing:border-box;display:flex;margin-top:10px;';
// 开始按钮
const goButton = window.document.createElement('button');
goButton.innerText = '开始';
goButton.style.cssText = 'width:max-content;height:28px;padding:0 5px;margin-left:5px;';
//用户UID
const uidBox = window.document.createElement('input');
uidBox.placeholder = "输入用户uid";
uidBox.style.cssText = 'width:70px;height:28px;padding:0 5px;margin-left:5px;';
//发送间隔
const interBox = window.document.createElement('input');
interBox.value = 1;
interBox.placeholder = "间隔(秒";
interBox.style.cssText = 'width:40px;height:28px;padding:0 5px;margin-left:5px;';
// 正则提示文本
const regLabel = window.document.createElement('div');
regLabel.innerText = '正则:'
regLabel.style.cssText = 'width:20%;height:28;line-height:30px;';
// 正则输入
const regBox = window.document.createElement('input');
regBox.placeholder = ".*匹配任意字符";
regBox.value = regdef;
regBox.style.cssText = 'width:60%;height:18px;padding:0 5px;margin-left:5px;';
// 组装
topTool.appendChild(collapseButton);
container.appendChild(topTool);
mainWindow.appendChild(textArea);
buttonArea.appendChild(uidBox);
buttonArea.appendChild(interBox);
buttonArea.appendChild(goButton);
buttonArea2.appendChild(regLabel);
buttonArea2.appendChild(regBox);
mainWindow.appendChild(buttonArea);
mainWindow.appendChild(buttonArea2);
container.appendChild(mainWindow);
window.document.body.appendChild(container);
// 显示逻辑控制
collapseButton.addEventListener('click', () => {
if (collapseButton.innerText === '收起') {
mainWindow.style.display = 'none';
collapseButton.innerText = '展开';
return;
}
if (collapseButton.innerText === '展开') {
mainWindow.style.display = 'block';
collapseButton.innerText = '收起';
return;
}
}, false);
function fadeOut(ele,time) {
let count = 20;
ele.style.opacity=1;
return setInterval(function() {
ele.style.opacity = ele.style.opacity - 1/count;
}, time/count);
}
function showMessage(intext) {
const div = window.document.createElement('div');
div.innerText = intext;
div.style.cssText = 'box-sizing:border-box;width:200px;height:40px;position:fixed;bottom:40px;left:50px;z-index:999;background-color:rgba(255, 255, 0,.2);border-radius:5px;color:#FF0000;font-size:medium;line-height:40px;text-align:center;';
window.document.body.appendChild(div);
let st = fadeOut(div, 2000);
if (inter == 1) {
inter = 1.5;
} else {
inter = 1;
}
setTimeout((ele) => {
clearInterval(st);
ele.remove();
}, 2000, div);
}
//-----------队列------------
function Queue() {
let list = [];
//向队列中添加数据
this.push = function(data,rlist) {
for(let i = 0; i < rlist.length;i++){
let pre = this.getRear();
if(pre){
if(pre[0]==data){
data+="\u200b";
}
if(data.length>20){
list.unshift([data.substring(0,20),rlist[i]]);
list.unshift([data.substring(20,data.length-1),rlist[i]]);
}else{
list.unshift([data,rlist[i]]);
}
}else{
if(data.length>20){
list.unshift([data.substring(0,20),rlist[i]]);
list.unshift([data.substring(20,data.length-1),rlist[i]]);
}else{
list.unshift([data,rlist[i]]);
}
}
}
return true;
}
this.getFront = function(){
return list[list.length-1];
}
this.getRear = function(){
return list[0];
}
this.pushHead = function(data,r){
let post = this.getFront;
if(post){
if(post[0]==data){
data+="\u200b";
}
if(data.length>20){
list[list.length] = [data.substring(0,20),r];
list[list.length] = [data.substring(20,data.length-1),r];
}else{
list[list.length] = [data,r];
}
}else{
if(data.length>20){
list[list.length] = [data.substring(0,20),r];
list[list.length] = [data.substring(20,data.length-1),r];
}else{
list[list.length] = [data,rlist[i]];
}
}
list[list.length] = [data,r];
return true;
}
//从队列中取出数据
this.pop = function() {
return list.pop();
}
//返回队列的大小
this.size = function() {
return list.length;
}
}
//-----------逻辑区----------
let msgQueue =new Queue();
let apiClient = axios.create({
baseURL: 'https://api.live.bilibili.com',
withCredentials: true
})
function objectCookies(cookie) {
let cookies = cookie.split(';');
let result = {};
for (let i = 0; i < cookies.length; i++) {
let keyvaluepair = cookies[i].split('=');
result[keyvaluepair[0].trim()] = keyvaluepair[1];
}
return result;
}
async function Request(msg, roomid) {
let cookie = document.cookie;
let rnd = parseInt(+new Date() / 1000);
let ObjectCookie = objectCookies(cookie)
let data = new FormData()
data.append('bubble', 0)
data.append('color', 16777215)
data.append('fontsize', 25)
data.append('mode', 1)
data.append('rnd', rnd)
data.append('msg', msg)
data.append('roomid', roomid)
data.append('csrf', ObjectCookie.bili_jct)
data.append('csrf_token', ObjectCookie.bili_jct)
let ajaxObj = (await apiClient.post('/msg/send', data, {
cookie: cookie
})).data
return ajaxObj;
}
try{
let qt;
let tempt;
let flag= false;//qt是否在运行中
const originFetch = window.fetch;
window.fetch = (...arg) => {
if (arg[0].indexOf('api.live.bilibili.com/msg/send') > -1) {
let ReturnPackage = Request(arg[1].data.msg,arg[1].data.roomid);
ReturnPackage.then(res => {
if(res.msg == "您发送弹幕的频率过快"){
showMessage(res.msg);
if(flag){
showMessage("正在重发");
msgQueue.pushHead(arg[1].data.msg,arg[1].data.roomid);
}
}else if (res.msg == "f") {
showMessage("全局屏蔽词");
}else if(res.msg == "k"){
showMessage("房间屏蔽词");
}
})
return new Promise(() => {
throw new Error();
});
} else {
return originFetch(...arg);
}
}
goButton.addEventListener('click', () => {
if (goButton.innerText == '暂停') {
bliveproxy.removeCommandHandler('DANMU_MSG', hdl)
tempt = setInterval(()=>{
if(msgQueue.size()==0){
flag = false;
clearInterval(qt);
clearInterval(tempt);
}
},1000*inter);
goButton.innerText = '开始';
return;
}
uid = uidBox.value;
if(uid==''){
showMessage("您未输入uid");
return;
}
rooms=textArea.value;
if(rooms==''){
showMessage("您未输入直播间号");
return;
}
rooms = textArea.value.replace(/ /g, '').trim().split('\n');
if (rooms.indexOf(proomid) > -1) {
showMessage("不能转发到所在直播间");
return;
}
bliveproxy.addCommandHandler('DANMU_MSG', hdl)
goButton.innerText = '暂停';
if(!flag){
flag = true;
qt = setInterval(()=>{
if(msgQueue.size()>0){
let [msg,roomid] = msgQueue.pop();
let ReturnPackage = Request(msg,roomid);
ReturnPackage.then(res=>{
if(res.msg == "您发送弹幕的频率过快"){
showMessage(res.msg+"正在重发");
msgQueue.pushHead(msg,roomid);
}
}//数据参考
//code: -500 data: [] message: "超出限制长度" msg: "超出限制长度" [[Prototype]]: Object
)//code: 10030 data: [] message: "您发送弹幕的频率过快" msg: "您发送弹幕的频率过快"
}
},1000*inter);
}
}, false);
}catch (e) {
alert('弹幕转发:发生未知错误\n' + e);
}
// 主要逻辑为检测弹幕,转发弹幕
function hdl(command) {
let info = command.info;
if (info[2][0] != uid) {
return;
}
const reg = new RegExp(regBox.value);
if(!(reg.test(info[1]))){
return;
};
msgQueue.push(info[1],rooms);
}
})();