b-live-random-send-诗词

定时从设置的字幕中随机取出一条在B站直播间发送,需先登录B站账号

目前為 2022-07-23 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/447936/1073386/b-live-random-send-%E8%AF%97%E8%AF%8D.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            b-live-random-send-诗词
// @description     定时从设置的字幕中随机取出一条在B站直播间发送,需先登录B站账号
// @author          Gamyou
// @version         1.2.4
// @note            22-07-23 1.2.4 修复弹幕分组数据都为空时,旧数据重复合并的BUG
// @note            22-07-16 1.2.3 修复设置弹幕时旧(初始)数据没有清空的BUG
// @note            22-06-24 1.2.2 优化部分代码
// @note            22-06-20 1.2.1 修复定时停止不能再开始的BUG
// @note            22-06-19 1.2.0 添加弹幕设置功能
// ==/UserScript==


(function () {
    'use strict';

    window.autoSendDanmuModuleLoaded = false;
    let data = null, timer = null, signInContent = '签到';
    let storeDataCallback = (obj) => {console.log('回调保存数据')}, removeDataCallback = (obj) => {console.log('回调删除数据')};
    let waiters = [], objData = {data1: [], data2: [], data3: [], data4: [], data5: []};
    let count = 0, lastSent = 0, arrayIndex = 0, isRandom = true, default_timeout = 600; // 超时时间,默认10分钟
    let dmButtonSend, dmInput, divSetting, dataText1, dataText2, dataText3, dataText4, dataText5, spanApplyTip, rdCheckbox;
    const waitCount = 10,
    initCss = () => {
        let linkElement = document.createElement('link');
        linkElement.rel = 'stylesheet';
        linkElement.href = 'https://unpkg.com/[email protected]/lib/theme-chalk/index.css';
        document.head.appendChild(linkElement);

        // 图标库 https://ionic.io/ionicons
        // let scriptElement = document.createElement('script');
        // scriptElement.src = 'https://unpkg.com/[email protected]/dist/ionicons.js';
        // document.head.appendChild(scriptElement);

        let customerStyle = document.createElement('style');
        customerStyle.setAttribute('type', 'text/css');
        customerStyle.innerHTML = '.el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#FFF;border:1px solid #DCDFE6;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;padding:12px 20px;font-size:14px;border-radius:4px}.el-button.is-circle{border-radius:50%;padding:12px}.el-button--mini.is-circle{padding:3px;}.el-button:focus,.el-button:hover{color:#409EFF;border-color:#c6e2ff;background-color:#ecf5ff}.el-icon-close.is-circle{padding:5px;color:#ff0000;border:1px solid #ff0000;margin-left:20px;}.el-icon-check.is-circle{padding:5px;color:#0000ff;border:1px solid #0000ff;margin-left:20px;}.random-check{display:inline-block;}.random-check label{display:inline-block;vertical-align:middle;border:1px solid #bdc3c7;border-radius:60px;width:40px;height:18px;position:relative;transition:all .3s;}.random-check input{display:none;}.random-check label:before{width:14px;height:14px;content:"";display:inline-block;background-color:#bdc3c7;border-radius:100%;position:absolute;top:2px;left:4px;transition:all .3s;}.random-check :checked ~ label{background-color:#26b22b;border-color:#26b22b;}.random-check :checked ~ label:before{left:22px;background-color:#fff;}';
        document.head.appendChild(customerStyle);
    },
    send = (msg, index) => {
        let dmTextArea = document.getElementById('aside-area-vm').getElementsByClassName('chat-input border-box')[0];
        if (!dmTextArea) {
            alert('找不到输入弹幕文本框,请尝试刷新页面');
            return;
        }

        let btnSend = document.getElementsByClassName('bl-button live-skin-highlight-button-bg live-skin-button-text bl-button--primary bl-button--small')[0];
        if (!btnSend) {
            alert('找不到发送按钮,请尝试刷新页面');
            return;
        }
        // if (getCurrentTimestamp() - lastSent < 5000) {
        //     setTimeout(() => send(msg, index), 5000);
        //     console.log('++++++++++++++++++++++> 发送间隔少于5秒,进入递归处理 <++++++++++++++++++++++');
        // } else {
            dmTextArea.value = msg;
            // 定义事件, 定义才可发送
            dmTextArea.dispatchEvent(new Event('input', { "bubbles": true, "cancelable": true }));
            btnSend.click();
            lastSent = getCurrentTimestamp();
            console.log('=================> lastSent=' + lastSent);
            ++count;
            console.log('=================> ' + new Date().toLocaleString() + ' 弹幕发送成功 ' + count + ' 次,第【' + index + '】条数据 === ' + msg);
        // }
    },
    getCurrentTimestamp = () => new Date().getTime(),
    isNull = (str) => {
        if (!str || str == "") {
            return true;
        }

        let regu = "^[ ]+$";
        let re = new RegExp(regu);
        return re.test(str);
    },
    randomSort = (arr) => {
        for (let i = 0; i < arr.length; i++) {
            const rdIndex = Math.floor(Math.random() * arr.length);
            const temp = arr[i];
            arr[i] = arr[rdIndex];
            arr[rdIndex] = temp;
        }

        return arr;
    },
    clearWaiters = () => {
        for (let i = 0; i < waiters.length; i++) {
            clearInterval(waiters[i]);
            waiters[i] = null;
        }

        waiters = [];
    },
    signIn = () => {
        let timestamp = new Date(new Date(new Date().setDate(new Date().getDate() + 1)).toDateString()).getTime() - getCurrentTimestamp();
        console.log('=================> 设置第二天凌晨签到定时器【' + timestamp + '】');
        setTimeout(() => {
            send(signInContent, 0);
            console.log('++++++++++++++++++++++> 完成签到发送,进入下一轮递归签到发送 <++++++++++++++++++++++');
            signIn();
        }, timestamp);
    },
    // selectRandom = () => isRandom = rdCheckbox.checked,
    openSetting = () => divSetting.style.display = 'block',
    closeSetting = () => {
        rdCheckbox.checked = isRandom; 
        divSetting.style.display = 'none';
    },
    initData = () => {
        if (objData.data1.length <= 0 
            && objData.data2.length <= 0 
            && objData.data3.length <= 0 
            && objData.data4.length <= 0 
            && objData.data5.length <= 0) {
            return data ? data : []; 
        }
  
        data = []; 
        data = data.concat(objData.data1).concat(objData.data2).concat(objData.data3).concat(objData.data4).concat(objData.data5);
        !rdCheckbox || rdCheckbox.checked ? data = randomSort(data) : arrayIndex = 0;
    },
    applySetting = () => {
        objData.data1 = isNull(dataText1.value) ? [] : dataText1.value.split('|');
        objData.data2 = isNull(dataText2.value) ? [] : dataText2.value.split('|');
        objData.data3 = isNull(dataText3.value) ? [] : dataText3.value.split('|');
        objData.data4 = isNull(dataText4.value) ? [] : dataText4.value.split('|');
        objData.data5 = isNull(dataText5.value) ? [] : dataText5.value.split('|');
        initData();
        callback(objData);
        isRandom = rdCheckbox.checked;
        spanApplyTip.style.display = 'block';
        setTimeout(() => {
            spanApplyTip.style.display = 'none';
        }, 1500);
    },
    danmu = () => {
        if (!data || data.length < 1) {
            alert('请任意在一个分组里输入一条弹幕');
            return;
        }
        if (isRandom) {
            arrayIndex = Math.floor((Math.random() * data.length));
        }
        
        send(data[arrayIndex], arrayIndex);
        ++arrayIndex;
        if (arrayIndex >= data.length)
            arrayIndex = 0;
    },
    offOrOn = () => {
        let timeout = 0;
        if (timer) {
            console.log('=================> ' + new Date().toLocaleString() + ' 停止发送弹幕');
            clearInterval(timer);
            timer = null;
            console.log('=================> ' + new Date().toLocaleString() + ' 停止成功!!');
            dmButtonSend.style.background = 'rgba(217,157,27,1)';
            dmButtonSend.textContent = '开始';
            dmInput.removeAttribute("disabled");
        } else {
            timeout = isNull(dmInput.value) ? default_timeout * 1000 : dmInput.value * 1000;
            danmu();
            console.log('=================> ' + new Date().toLocaleString() + ' 开启定时器');
            timer = setInterval(danmu, timeout);
            console.log('=================> ' + new Date().toLocaleString() + ' 开启成功!!');
            dmButtonSend.style.background = '#ff0000';
            dmButtonSend.textContent = '停止';
            dmInput.setAttribute('disabled', 'disabled');
        }
    },
    pageFullyLoaded = () => {
        let divButton = document.getElementsByClassName('bottom-actions p-relative')[0];
        if (!divButton) {
            console.error('没能找到发送按钮上层的div');
            --waitCount;
            if (0 >= waitCount) {
                clearWaiters();
            }
            return;
        }
        clearWaiters();

        let divSettingTitle = document.createElement('div');
        divSettingTitle.textContent = '弹幕设置';
        divSettingTitle.style.textAlign = 'center';
        divSettingTitle.style.fontSize = '16px';
        divSettingTitle.style.fontWeight = '700';
        divSettingTitle.style.color = '#1c5adc';
        divSettingTitle.style.margin = '4px 0px 8px 0';

        let divText1 = document.createElement('div');
        divText1.textContent = '分组 1 :'
        divText1.style.fontSize = '14px';
        divText1.style.paddingLeft = '2px';
        divText1.style.color = '#12388d';
        divText1.style.display = 'inline';

        let divTip = document.createElement('div');
        divTip.style.color = '#0b81cc';
        divTip.style.marginLeft = '5px';
        divTip.style.fontStyle = 'italic';
        divTip.style.display = 'inline';
        divTip.innerHTML = '任一分组内输入弹幕即可,多条用<span style="color:#dc6b07;margin:0 2px 0 4px;font-weight:700;font-style:normal;">竖线</span>分隔';

        dataText1 = document.createElement('textarea');
        dataText1.style.width = '98%';
        dataText1.style.height = '100px';
        dataText1.style.margin = '1px 0 4px 0';
        dataText1.style.border = '0';
        dataText1.style.resize = 'none';
        dataText1.setAttribute('placeholder', '请输入弹幕,多条弹幕请用“|”分隔');

        let divText2 = document.createElement('div');
        divText2.textContent = '分组 2 :'
        divText2.style.fontSize = '14px';
        divText2.style.paddingLeft = '2px';
        divText2.style.color = '#12388d';

        dataText2 = document.createElement('textarea');
        dataText2.style.width = '98%';
        dataText2.style.height = '100px';
        dataText2.style.margin = '1px 0 4px 0';
        dataText2.style.border = '0';
        dataText2.style.resize = 'none';
        dataText2.setAttribute('placeholder', '请输入弹幕,多条弹幕请用“|”分隔');

        let divText3 = document.createElement('div');
        divText3.textContent = '分组 3 :'
        divText3.style.fontSize = '14px';
        divText3.style.paddingLeft = '2px';
        divText3.style.color = '#12388d';

        dataText3 = document.createElement('textarea');
        dataText3.style.width = '98%';
        dataText3.style.height = '100px';
        dataText3.style.margin = '1px 0 4px 0';
        dataText3.style.border = '0';
        dataText3.style.resize = 'none';
        dataText3.setAttribute('placeholder', '请输入弹幕,多条弹幕请用“|”分隔');

        let divText4 = document.createElement('div');
        divText4.textContent = '分组 4 :'
        divText4.style.fontSize = '14px';
        divText4.style.paddingLeft = '2px';
        divText4.style.color = '#12388d';

        dataText4 = document.createElement('textarea');
        dataText4.style.width = '98%';
        dataText4.style.height = '100px';
        dataText4.style.margin = '1px 0 4px 0';
        dataText4.style.border = '0';
        dataText4.style.resize = 'none';
        dataText4.setAttribute('placeholder', '请输入弹幕,多条弹幕请用“|”分隔');

        let divText5 = document.createElement('div');
        divText5.textContent = '分组 5 :'
        divText5.style.fontSize = '14px';
        divText5.style.paddingLeft = '2px';
        divText5.style.color = '#12388d';

        dataText5 = document.createElement('textarea');
        dataText5.style.width = '98%';
        dataText5.style.height = '100px';
        dataText5.style.margin = '1px 0 4px 0';
        dataText5.style.border = '0';
        dataText5.style.resize = 'none';
        dataText5.setAttribute('placeholder', '请输入弹幕,多条弹幕请用“|”分隔');

        let descCheckbox = document.createElement('span');
        descCheckbox.textContent = '随机';
        descCheckbox.title = '将合并所有分组数据,从中随机选出一条发送';
        descCheckbox.style.fontSize = '16px';
        descCheckbox.style.verticalAlign = 'middle';
        descCheckbox.style.marginRight = '4px';
        descCheckbox.style.color = '#095ca2';

        rdCheckbox = document.createElement('input');
        rdCheckbox.type = 'checkbox';
        rdCheckbox.id = 'rdmCheckbox';
        rdCheckbox.checked = isRandom;
        // rdCheckbox.addEventListener('click', selectRandom);

        let lblCheckbox = document.createElement('label');
        lblCheckbox.setAttribute('for', 'rdmCheckbox');

        let divCheckbox = document.createElement('div');
        divCheckbox.classList.add('random-check');
        divCheckbox.style.marginLeft = '10px';
        divCheckbox.appendChild(descCheckbox);
        divCheckbox.appendChild(rdCheckbox);
        divCheckbox.appendChild(lblCheckbox);

        spanApplyTip = document.createElement('span');
        spanApplyTip.textContent = '设置成功'
        spanApplyTip.style.fontSize = '16px';
        spanApplyTip.style.color = '#128712';
        spanApplyTip.style.display = 'none';

        let divApplyTip = document.createElement('div');
        divApplyTip.style.textAlign = 'center';
        divApplyTip.style.display = 'inline-block';
        divApplyTip.style.verticalAlign = 'middle';
        divApplyTip.style.width = '34%';
        divApplyTip.appendChild(spanApplyTip);

        let btnApplySetting = document.createElement('i');
        btnApplySetting.setAttribute('title', '应用');
        btnApplySetting.classList.add('el-button');
        btnApplySetting.classList.add('el-icon-check');
        btnApplySetting.classList.add('is-circle');
        btnApplySetting.addEventListener('click', applySetting);

        let btnCloseSetting = document.createElement('i');
        btnCloseSetting.setAttribute('title', '关闭');
        btnCloseSetting.classList.add('el-button');
        btnCloseSetting.classList.add('el-icon-close');
        btnCloseSetting.classList.add('is-circle');
        btnCloseSetting.addEventListener('click', closeSetting);

        let divSettingButton = document.createElement('div');
        divSettingButton.style.display = 'inline-block';
        divSettingButton.style.verticalAlign = 'middle';
        divSettingButton.appendChild(btnApplySetting);
        divSettingButton.appendChild(btnCloseSetting);

        let divBottomContainer = document.createElement('div');
        divBottomContainer.style.width = '100%';
        divBottomContainer.style.height = '25px';
        divBottomContainer.style.margin = '4px 0 8px 0';
        // divBottomContainer.style.position = 'absolute';
        // divBottomContainer.style.bottom = '0';
        divBottomContainer.appendChild(divCheckbox);
        divBottomContainer.appendChild(divApplyTip);
        divBottomContainer.appendChild(divSettingButton);

        divSetting = document.createElement('div');
        divSetting.style.backgroundColor = '#d4f2e0';
        divSetting.style.borderRadius = '2px';
        divSetting.style.width = '100%';
        divSetting.style.height = '100%';
        divSetting.style.overflowY = 'auto';
        divSetting.style.position = 'absolute';
        divSetting.style.left = '0';
        divSetting.style.top = '0';
        divSetting.style.zIndex = '999';
        divSetting.style.display = 'none';
        divSetting.appendChild(divSettingTitle)
        divSetting.appendChild(divText1);
        divSetting.appendChild(divTip);
        divSetting.appendChild(dataText1);
        divSetting.appendChild(divText2);
        divSetting.appendChild(dataText2);
        divSetting.appendChild(divText3);
        divSetting.appendChild(dataText3);
        divSetting.appendChild(divText4);
        divSetting.appendChild(dataText4);
        divSetting.appendChild(divText5);
        divSetting.appendChild(dataText5);
        divSetting.appendChild(divBottomContainer);

        let asideAreaVm = document.getElementById('aside-area-vm');
        asideAreaVm.appendChild(divSetting);

        dmButtonSend = document.createElement('button');
        dmButtonSend.textContent = '开始';
        dmButtonSend.style.minWidth = '65px';
        dmButtonSend.style.height = '24px';
        dmButtonSend.style.fontSize = '12px';
        dmButtonSend.style.borderRadius = '4px';
        dmButtonSend.style.color = '#ffffff';
        dmButtonSend.style.background = 'rgba(217,157,27,1)';
        dmButtonSend.style.border = '0';
        dmButtonSend.style.cursor = 'pointer';
        //dmButtonSend.onclick = function() { alert('Hello world');}
        dmButtonSend.addEventListener('click', offOrOn);

        let beforeSpan = document.createElement('span');
        beforeSpan.textContent = '每';
        beforeSpan.style.color = '#ffffff';
        beforeSpan.style.fontSize = '12px';
        beforeSpan.style.marginLeft = '4px';
        beforeSpan.style.backgroundColor = '#ec6c1b';

        dmInput = document.createElement('input');
        dmInput.value = default_timeout;
        dmInput.style.width = '25px';
        dmInput.style.height = '15px';
        dmInput.style.margin = '0 3px';
        dmInput.style.border = '0';
        dmInput.style.borderRadius = '3px';
        dmInput.setAttribute('oninput', "this.value = this.value.replace(/[^0-9]/g, '')");

        let afterSpan = document.createElement('span');
        afterSpan.textContent = '秒发送';
        afterSpan.style.color = '#ffffff';
        afterSpan.style.fontSize = '12px';
        afterSpan.style.backgroundColor = '#ec6c1b';
        afterSpan.style.marginRight = '4px';

        let iElement = document.createElement('i');
        iElement.classList.add('el-icon-setting');

        let btnSetting = document.createElement('button');
        btnSetting.title = '设置';
        btnSetting.classList.add('el-button');
        btnSetting.classList.add('el-button--mini');
        btnSetting.classList.add('is-circle');
        btnSetting.addEventListener('click', openSetting);
        btnSetting.appendChild(iElement);

        // let btnSetting = document.createElement('ion-icon');
        // btnSetting.setAttribute('name', 'settings-sharp');
        // btnSetting.classList.add('el-button');
        // btnSetting.classList.add('el-button--mini');
        // btnSetting.classList.add('is-circle');
        // btnSetting.addEventListener('click', openSetting);

        let div = document.createElement('div');
        div.style.position = 'absolute';
        div.appendChild(dmButtonSend);
        div.appendChild(beforeSpan);
        div.appendChild(dmInput);
        div.appendChild(afterSpan);
        div.appendChild(btnSetting);
        divButton.appendChild(div);

        window.autoSendDanmuModuleLoaded = true;
    },
    arrayInfo = () => console.info(data),
    setDanmuData = (obj) => {
        console.log('=======================> 数据传入,进行数据填充');
        objData = obj;
        dataText1.value = objData.data1.join('|');
        dataText2.value = objData.data2.join('|');
        dataText3.value = objData.data3.join('|');
        dataText4.value = objData.data4.join('|');
        dataText5.value = objData.data5.join('|');
        console.log('=======================> 填充完成,进行数据源初始化');
        initData();
        console.log('=======================> 设置数据操作完成');
    },
    storeDanmuData = (cb) => storeDataCallback = cb,
    removeDanmuData = (cb) => removeDataCallback = cb;
    
    initCss();
    window.addEventListener("load", () => {
        waiters[waiters.length] = setInterval(pageFullyLoaded, 1500);
        window.arrayInfo = arrayInfo;
        window.setDanmuData = setDanmuData;
        window.storeDanmuData = storeDanmuData;
        window.removeDanmuData = removeDanmuData;
        signIn();
    });
})();