B站直播间弹幕广播

B站直播间弹幕转发,辅助联动同传的工作,需要用户已登录(不可用)。若有滥用等问题概不负责,诶嘿。顺便关注一下小东人鱼和noworld吧~

// ==UserScript==
// @name         B站直播间弹幕广播
// @namespace    http://tampermonkey.net/
// @version      0.4.2
// @description  B站直播间弹幕转发,辅助联动同传的工作,需要用户已登录(不可用)。若有滥用等问题概不负责,诶嘿。顺便关注一下小东人鱼和noworld吧~
// @author       太陽闇の力
// @include      /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @require      https://cdn.jsdelivr.net/gh/eric2788/bliveproxy@d66adfa34cbf41db3d313f49d0814e47cb3b6c4c/bliveproxy-unsafe.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// @grant        unsafeWindow
// @license MIT
// ==/UserScript==


// 命令分析参考自  https://segmentfault.com/a/1190000017328813
//界面参考自小东人鱼午安社五更耗纸 https://github.com/gokoururi-git/gachihelper/
// 弹幕api使用参考自   https://gf.qytechs.cn/zh-CN/scripts/434726

(function() {
    //-----------配置区----------
    //0默认收起,1默认展开
    let isunfold = 0;
    // 转发对象的UID,也即转发谁的弹幕
    let uid = `
353039514
`.replace(/ /g, '').trim().replace(/\n{2,}/g, '\n').split('\n');
    // 要转发的直播间号,也即发到哪儿
    let rooms = `
9806022
2077803
431458
145657
`.replace(/ /g, '').trim().replace(/\n{2,}/g, '\n').split('\n');
    // 设置弹幕的发送间隔(秒),默认1秒,指转发到直播间A,然后再转发到直播间B,中间的时间间隔
    //如果设置太小,会因发送频率过快而被B站吞掉且有系统屏蔽风险
    let inter = 1;
    let logic = 0;//取值为0则为与,1则为或,3则为非。
    //默认正则,带【】()[]()的弹幕都会被转发。
    let regdef = "[【】()\\[\\]()]";
    //默认自己为20字数限制,一般不改了(有需要的就找我反馈吧)
    // 当前直播间号
    const proomid = /(?<=https?:\/\/live\.bilibili\.com\/(blanc\/)?)\d+/.exec(window.location.href)[0];

    if(!(window.document.firstChild instanceof window.Comment)){//「あなたに逢えなくなって、錆びた時計と泣いたけど…」
        return;
    }
    //-----------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 = 'display: flex;flex-wrap: wrap;justify-content:space-between;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:45%;height:60px;resize:none;outline:none;background-color:rgba(255,255,255,.5);border-radius:2px';

    // 直播间号输入框
    const textArea2 = window.document.createElement('textarea');
    textArea2.placeholder = '选择的uid,换行分隔'
    textArea2.style.cssText = 'width:45%;height:60px;resize:none;outline:none;background-color:rgba(255,255,255,.5);border-radius:2px';

    // 按钮区容器
    const buttonArea = window.document.createElement('div');
    buttonArea.style.cssText = 'width:100%;height:30px;box-sizing:border-box;display:flex;justify-content: space-around;margin-top:10px;';

    // 按钮区容器
    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;';


    //发送间隔
    const interBox = window.document.createElement('input');
    interBox.value = 1;
    interBox.title = "转发间隔的秒数";
    interBox.style.cssText = 'width:30px;height:20px;padding:0 5px;';

    //前缀
    const prefix = window.document.createElement('input');
    prefix.placeholder = "前缀";
    prefix.style.cssText = 'width:30px;height:20px;padding:0 5px;';

    // 逻辑按钮
    const logi = window.document.createElement('select');
    logi.options.add(new Option("与",0));
    logi.options.add(new Option("或",1));
    logi.options.add(new Option("非",2));;
    logi.value = logic;
    logi.style.cssText = 'text-align:center;width:20px;height:20px;appearance:none;margin:auto;border-radius:2px';
    // 正则提示文本
    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.title = ".*匹配任意字符";
    regBox.value = regdef;
    regBox.style.cssText = 'width:60%;height:18px;padding:0 5px:5px;margin:auto';
    // 组装
    topTool.appendChild(collapseButton);
    container.appendChild(topTool);
    mainWindow.appendChild(textArea);
    mainWindow.appendChild(textArea2)
    buttonArea.appendChild(goButton);
    buttonArea.appendChild(prefix);
    buttonArea.appendChild(interBox);
    buttonArea2.appendChild(logi);
    buttonArea2.appendChild(regLabel);
    buttonArea2.appendChild(regBox);
    mainWindow.appendChild(buttonArea2);
    mainWindow.appendChild(buttonArea);
    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 = 'flex';
            collapseButton.innerText = '收起';
            return;
        }
    }, false);
    function fadeOut(ele,time) {
        const 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:max-content;padding:0 10px;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);
        setTimeout((ele) => {
            clearInterval(st);
            ele.remove();
        }, 2000, div);
    }
    let prefixValue = "";
    let limit = 20;
    //-----------队列------------
    function Queue() {
        let list = [];

        //向队列中添加数据
        this.push = function(data,rlist,dmtype) {
            data = prefixValue+data;
            for(let i = 0; i < rlist.length;i++){
                if(data.length>limit){
                    list.unshift([data.substring(0,limit),rlist[i],dmtype]);
                    list.unshift([prefixValue+"【…"+data.substring(limit,data.length),rlist[i],dmtype]);
                }else{
                    list.unshift([data,rlist[i],dmtype]);
                }
                /*
                const pre = this.getRear();
                if(pre && pre[0]==data&&!dmtype){
                    data+="\u200b";
                }
                if(data.length>20){
                    list.unshift([data.substring(0,16),rlist[i],dmtype]);
                    list.unshift([prefixValue+"【…"+data.substring(16,data.length),rlist[i],dmtype]);
                }else{
                    list.unshift([data,rlist[i],dmtype]);
                }
                */

            }
            return true;
        }
        this.getFront = function(){
            return list[list.length-1];

        }
        this.getRear = function(){
            return list[0];
        }
        this.pushHead = function(data,r,dmtype){
            /*
            const post = this.getFront();
            if(post&&post[0]==data&&!dmtype){
                data+="\u200b";
            }
            if(data.length>20){
                list[list.length] = [data.substring(0,16),r,dmtype];
                list[list.length] = [data.substring(16,data.length),r,dmtype];
            }else{
                list[list.length] = [data,r,dmtype];
            }
            */
            list[list.length] = [data,r,dmtype];
            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
    })
    const bili_jct = document.cookie.replace(/(?:(?:^|.*;\s*)bili_jct\s*=\s*([^;]*).*$)|^.*$/, '$1');
    async function Request(msg, roomid,dm_type = 0) {
        let rnd = parseInt(+new Date() / 1000);
        let data = new FormData()
        data.append('dm_type', dm_type);
        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', bili_jct)
        data.append('csrf_token', bili_jct)
        let ajaxObj = (await apiClient.post('/msg/send', data)).data
        return ajaxObj;
    }
    const send = ()=>{
        if(msgQueue.size()>0){
            let [msg,roomid,dmType] = msgQueue.pop();
            let ReturnPackage = Request(msg,roomid,dmType);
            ReturnPackage.then(res=>{
                if(res.code ==0 && res.message == ""){
                    //发送成功
                }else if(res.message == "您发送弹幕的频率过快"){
                    showMessage(res.message+"正在重发");
                    msgQueue.pushHead(msg,roomid,dmType);
                    clearInterval(qt);
                    setTimeout(()=>{
                        send();
                        qt = setInterval(send,1000*inter);
                    },2000)
                }else if (res.message == "f") {
                    showMessage("全局屏蔽词或被一定程度限制发言:"+msg);
                }else if(res.message == "k"){
                    showMessage("房间屏蔽词:"+msg);
                }else if (res.message == "表情发送失败~"){
                    msgQueue.pushHead("[表情_"+msg+"]",roomid,0);
                }else{
                    showMessage(res.message);
                }
            }//数据参考
                               //code: -500 data: [] message: "超出限制长度" msg: "超出限制长度" [[Prototype]]: Object
                              )//code: 10030 data: [] message: "您发送弹幕的频率过快" msg: "您发送弹幕的频率过快"

        }
    }
    let qt;
    let flag= false;//qt是否在运行中
    const originFetch = fetch;
    unsafeWindow.fetch = (...arg) => {
        if (arg[0].indexOf('send') > -1) {
            if(flag){
                msgQueue.pushHead(arg[1].data.msg,arg[1].data.roomid,arg[1].data.dm_type);
            }else{
                let ReturnPackage = Request(arg[1].data.msg,arg[1].data.roomid,arg[1].data.dm_type);
                ReturnPackage.then(res => {
                    if(res.code ==0 && res.message == ""){
                        return
                    }else if(res.message == "您发送弹幕的频率过快"){
                        showMessage(res.message);
                    }else if (res.message == "f") {
                        showMessage("全局屏蔽词或被一定程度限制发言");
                    }else if(res.message == "k"){
                        showMessage("房间屏蔽词");
                    }else{
                        showMessage(res.message);
                    }
                    const biliTextArea = window.document.querySelector("textarea");
                    const inputEvent = document.createEvent("Event");
                    inputEvent.initEvent("input",true, true);
                    biliTextArea.value = arg[1].data.msg;
                    biliTextArea.dispatchEvent(inputEvent);
                })
            }
            return new Promise(() => {
                throw new Error();
            });

        } else {
            return originFetch(...arg);
        }
    }
    function hdl(command) {
        const info = command.info;
        const uidlogi = uid.indexOf(info[2][0].toString())>-1;
        const reg = new RegExp(regBox.value);
        const reglogi = reg.test(info[1]);
        switch(logic){
            case "0":if(!(uidlogi&&reglogi)){return};
                break;
            case "1":if(!(uidlogi||reglogi)){return};
                break;
            case "2":if(reglogi){return};
                break;
        }
        let dmType = command.info[0][12];
        msgQueue.push(info[1],rooms,dmType);

    }

    try{
        goButton.addEventListener('click', () => {
            if (goButton.innerText == '暂停') {
                bliveproxy.removeCommandHandler('DANMU_MSG', hdl)
                flag = false;
                clearInterval(qt);
                goButton.innerText = '开始';
                return;
            }
            limit = parseInt(window.document.querySelector('.input-limit-hint').innerHTML.split('/')[1]);
            uid = textArea2.value.replace(/ /g, '').trim().replace(/\n{2,}/g, '\n').split('\n');
            logic = logi.value;
            rooms=textArea.value;
            if(rooms==''){
                showMessage("您未输入直播间号");
                return;
            }
            rooms = textArea.value.replace(/ /g, '').trim().replace(/\n{2,}/g, '\n').split('\n');
            if (rooms.indexOf(proomid) > -1) {
                showMessage("不能转发到所在直播间");
                return;
            }
            prefixValue = prefix.value;
            bliveproxy.addCommandHandler('DANMU_MSG', hdl);
            goButton.innerText = '暂停';
            if(!flag){
                flag = true;
                qt = setInterval(send,1000*inter);
            }


        }, false);
    }catch (e) {
        alert('弹幕转发:发生未知错误\n' + e);
        bliveproxy.removeCommandHandler('DANMU_MSG', hdl);
    }
})();

//点击弹幕区的人显示uid和悬停显示自己的uid
window.onload = function(){
    const div = window.document.createElement("div");
    let chat = window.document.querySelector("#chat-items");
    const parent = window.document.querySelector(".user-panel.panel-shadow");
    const a = window.document.querySelector(".msg-hinter")?.parentNode;
    const myId = parent?.querySelector("span");
    let username;
    if(chat){
        chat.addEventListener("click",(e)=>{
            if(!username){
                username = window.document.querySelector(".danmaku-menu");
                username.insertBefore(div,username.childNodes[1]);
            }
            if(e.target.className.split(" ").indexOf("pointer")>-1){
                const userID=e.target.parentNode.getAttribute("data-uid");
                div.innerText = userID;
            }
        })

    }else{
        console.log("无法获取弹幕栏");
    }
    if(myId&&a){

        parent.classList.remove("none-select");
        myId.style = "font-size:15px";
        setTimeout(()=>{
            const myUid = /(?=.*)\d+/.exec(a.href)[0];
            myId.innerHTML = myUid + "<br>" + myId.innerHTML;
        },1000);

    }else{
        console.log("无法获取个人资料卡");
    }
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址