B站直播间定时发随机弹幕

定时发送;凌晨打卡;设置随机弹幕;弹幕分组管理;各直播间弹幕互不干扰;无人值守参与、关闭天选时刻;直播间防休眠;隐藏模块简化直播间

// ==UserScript==
// @name            B站直播间定时发随机弹幕
// @namespace       https://live.bilibili.com/暂无主页
// @home            https://github.com/Gamyou/bilibili-live-random-send
// @update          https://greasyfork.org/scripts/446725-b%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%AE%9A%E6%97%B6%E5%8F%91%E9%9A%8F%E6%9C%BA%E5%BC%B9%E5%B9%95/code/B%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%AE%9A%E6%97%B6%E5%8F%91%E9%9A%8F%E6%9C%BA%E5%BC%B9%E5%B9%95.user.js
// @download        https://greasyfork.org/scripts/446725-b%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%AE%9A%E6%97%B6%E5%8F%91%E9%9A%8F%E6%9C%BA%E5%BC%B9%E5%B9%95/code/B%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E5%AE%9A%E6%97%B6%E5%8F%91%E9%9A%8F%E6%9C%BA%E5%BC%B9%E5%B9%95.user.js
// @version         2.5.0
// @description     定时发送;凌晨打卡;设置随机弹幕;弹幕分组管理;各直播间弹幕互不干扰;无人值守参与、关闭天选时刻;直播间防休眠;隐藏模块简化直播间
// @author          Gamyou
// @match           *://live.bilibili.com/*
// @icon            https://www.bilibili.com/favicon.ico
// @license         Apache License, Version 2.0
// @run-at          document-idle
// @grant           GM_info
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_notification
// @grant           GM_openInTab
// @grant           GM_xmlhttpRequest
// @connect         jsdelivr.net
// @connect         greasyfork.org
// @note            24-09-10 2.5.0      处理缓存长时间不失效导致加载不了新版本的问题
// ==/UserScript==


window.onload = (function () {
    const local = {
            "id": "RandomSendHelper",
            "text": "打卡",
            "default": "danmu",
            "config": "config"
        },
        infoUrl = "https://update.greasyfork.org/scripts/448354/RandomSendBaseInfo.js",
        frameUrl = 'https://greasyfork.org/zh-CN/scripts/446725',
        isNull = str => {
            if (!str || str == "") {
                return true;
            }

            let regu = "^[ ]+$";
            let re = new RegExp(regu);
            return re.test(str);
        },
        delValue = key => GM_deleteValue(key),
        getValue = (key, defaultVaue) => {
            return GM_getValue(key, defaultVaue);
        },
        setValue = (key, obj) => {
            if (isNull(key)) { console.warn('key为空,保存失败'); return; }
            if (obj) { GM_setValue(key, obj); }
        },
        notice = (msg) => {
            if (isNull(msg)) { GM_notification(msg); }
            else { console.warn('消息对象为空,无效通知'); }
        },
        compareVersion = (v1, v2) => {
            if (isNull(v1)) return -1;
            if (isNull(v2)) return 1;

            const a1 = v1.split('.').map(x => x * 1),
                a2 = v2.split('.').map(x => x * 1),
                len = Math.max(a1.length, a2.length);
            for (let i = 0; i < len; i++) {
                if ((a1[i] || 0) > (a2[i] || 0)) return 1;
                if ((a1[i] || 0) < (a2[i] || 0)) return -1;
            }

            return 0;
        },
        getVersion = txt => {
            let m = txt.match(/@version\s+\d+\.\d+\.\d+/i);
            if (!m || 1 > m.length || !m[0]) {
                return 0;
            }

            let n = m[0].match(/\d+\.\d+\.\d+/);
            if (!n || 1 > n.length || !n[0]) {
                return 0;
            }

            return n[0];
        },
        getInfo = count => {
            count = count || 0;
            if (1 < count) {
                create();
            }
            if (8 > count) {
                const d = {
                    "url": infoUrl,
                    "method": "GET",
                    "nocache": true,
                    "timeout": 15e3,
                    "responseType": "json",
                    "ontimeout": t => {
                        console.log(`===> 获取信息超时【${t}】`);
                        getInfo(++count);
                    },
                    "onerror": e => {
                        console.log(`===> 获取信息出错【${e}】`);
                        getInfo(++count);
                    },
                    "onload": r => {
                        if (!r || !r.response) {
                            console.log(`===> 获取信息为空,进行重试`);
                            getInfo(++count);
                            return;
                        }

                        let info = {...r.response, ...local};
                        info.url = (info.url || "").replace("@latest/", `@${info.version || "latest"}/`);
                        if (0 < compareVersion(info.frame, GM_info?.script?.version)) {
                            alert(`定时发随机弹幕版本过低,请及时升级`);
                            GM_openInTab(frameUrl, {active: true});
                            return;
                        }

                        updateScript(info);
                    }
                };
                GM_xmlhttpRequest(d);
            }
        },
        updateScript = (info, count) => {
            count = count || 0;
            let c = getValue((info.config || local.config), {});
            if (!isNull(c.moduleVersion) && 0 == compareVersion(c.moduleVersion, info.version)) {
                create(info, c.script);
                return;
            }
            if (1 < count) {
                create(info, c.script);
            }
            if (!isNull(info.url) && 6 > count) {
                const d = {
                    "url": info.url,
                    "method": "GET",
                    "nocache": true,
                    "timeout": 20e3,
                    "ontimeout": t => {
                        console.log(`===> 获取脚本超时【${t}】`);
                        updateScript(info, ++count);
                    },
                    "onerror": e => {
                        console.log(`===> 获取脚本出错【${e}】`);
                        updateScript(info, ++count);
                    },
                    "onload": r => {
                        if (!r || !r.responseText) {
                            console.log(`===> 获取脚本为空,进行重试`);
                            updateScript(info, ++count);
                            return;
                        }

                        create(info, r.responseText);
                        c.script = r.responseText;
                        c.moduleVersion = info.version;
                        c.lastUpdate = new Date().toLocaleString();
                        setValue(info.config, c);
                        checkVersion();
                    }
                };
                GM_xmlhttpRequest(d);
            }
        },
        create = (info, txt) => {
            if (!info) info = local;
            let script = document.getElementById(info.id);
            if (!script) {
                if (isNull(txt)) {
                    let config = getValue((info.config), null);
                    if (!config || !config.script) {
                        return;
                    }

                    txt = config.script;
                }

                script = document.createElement('script');
                script.id = info.id;
                script.type = 'text/javascript';
                script.text = txt;
                document.head.appendChild(script);
                init(info);
            }
        },
        init = info => {
            setGmNotice(notice);
            setGmGetValue(getValue);
            setGmSetValue(setValue);
            setGmDelValue(delValue);
            setBaseInfo(info);
            runStart();
        };

    getInfo();
})();