旋转的五分硬币排队

旋转的五分硬币直播间深渊排队脚本

目前為 2022-03-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         旋转的五分硬币排队
// @namespace    http://tampermonkey.net/
// @version      0.0.14
// @description  旋转的五分硬币直播间深渊排队脚本
// @author       Mimiko
// @license      MIT
// @match        *://live.bilibili.com/3140454*
// @icon         http://i0.hdslb.com/bfs/activity-plat/static/20211202/dddbda27ce6f43bf18f5bca141752a99/fCo7evLooK.webp@128w
// @grant        GM.xmlHttpRequest
// ==/UserScript==

(() => {
    if (window.top !== window.self)
        return;
    const Lines = {
        add_exist: '{name}已经排过队了,序号是{idx}',
        add_success: '{name}已经排队成功,序号是{idx}',
        admin_none: '尚未为排队姬指定饲养员',
        find_none: '{name}还没有排队',
        find_success: '{name}已经排过队了,序号是{idx}',
        server_none: '排队姬尚未启动',
        server_ready: '排队姬已经启动',
        voice_disconnect: '无法连接语音服务器',
        voice_fail: '语音设置失败',
        voice_invalid: '语音名称不存在',
        voice_success: '语音已设置为{name}',
        waiting_cancel: '开车已取消',
        waiting_countdown: '发车倒计时十秒',
        waiting_fail: '未能成功发车',
        waiting_none: '没有人在等车',
        waiting_start: '开车啦,请各位乘客输入自己的序号',
        waiting_success: '请序号为{idx}的{name}使用手机扫码上车',
    };
    const Monkey = GM;
    const cacheId = new Set();
    const cacheName = new Map();
    const cacheTimer = new Map();
    const delay = 30e3;
    const interval = 30e3;
    const listVoice = [
        'hiumaan',
        'hsiaochen',
        'huihui',
        'kangkang',
        'xiaoxiao',
        'yaoyao',
        'yunyang',
    ];
    const observer = new MutationObserver(() => {
        pick();
        clearDanmaku();
    });
    const port = 9644;
    const setAdmin = new Set();
    const setWaiting = new Set();
    const speaker = new SpeechSynthesisUtterance();
    let isWaiting = false;
    const add = async (name, id) => {
        if (!validate(name, id))
            return;
        const data = await get(`http://localhost:${port}/queue/add?name=${name}`);
        if (!data)
            return;
        if (!data.status)
            speak(Lines.add_exist, { idx: data.idx, name });
        else
            speak(Lines.add_exist, { idx: data.idx, name });
    };
    const addTimer = (token, delay, callback) => {
        removeTimer(token);
        cacheTimer.set(token, window.setTimeout(callback, delay));
    };
    const cancelWaiting = () => {
        if (!isWaiting)
            return;
        isWaiting = false;
        setWaiting.clear();
        removeTimer('waiting/countdown');
        removeTimer('waiting/speak');
        speak(Lines.waiting_cancel);
    };
    const clearDanmaku = () => {
        const $el = document.getElementById('chat-items');
        if (!$el)
            return;
        $el.innerHTML = '';
    };
    const endWaiting = () => {
        if (!isWaiting)
            return;
        isWaiting = false;
        if (!setWaiting.size) {
            speak(Lines.waiting_none);
            return;
        }
        const idx = Math.min(...setWaiting);
        setWaiting.clear();
        setCurrent(idx);
    };
    const find = async (name, id) => {
        if (!validate(name, id))
            return;
        const data = await get(`http://localhost:${port}/queue/find?name=${name}`);
        if (!data)
            return;
        if (!data.idx)
            speak(Lines.find_none, { name });
        else
            speak(Lines.find_success, { idx: data.idx, name });
    };
    const get = (url) => new Promise(resolve => {
        Monkey.xmlHttpRequest({
            method: 'GET',
            onerror: () => resolve(null),
            onload: (response) => resolve(url.includes('localhost') ? JSON.parse(response.responseText) : response.responseText),
            url,
        });
    });
    const getListAdmin = async () => {
        const data = await get(`http://localhost:${port}/admin/list`);
        if (!data)
            return false;
        if (!data.list.length) {
            speak(Lines.admin_none);
            return false;
        }
        data.list
            .filter(name => name.trim())
            .forEach(name => setAdmin.add(name.replace(/\r/g, '')));
        return true;
    };
    const log = (message) => {
        console.log(message);
        return message;
    };
    const main = async () => {
        pauseVideo();
        if (!(await ping()))
            return;
        if (!(await getListAdmin()))
            return;
        observe();
        clearDanmaku();
    };
    const observe = () => {
        const timer = window.setInterval(() => {
            const $el = document.getElementById('chat-items');
            if (!$el)
                return;
            window.clearInterval(timer);
            observer.observe($el, {
                childList: true,
                attributes: true,
                characterData: true,
            });
        }, 50);
    };
    const pauseVideo = () => document.querySelector('video')?.pause();
    const pick = () => Array.from(document.querySelectorAll('#chat-items .danmaku-item')).forEach($danmaku => {
        const content = $danmaku.getAttribute('data-danmaku')?.trim() || '';
        const id = $danmaku.getAttribute('data-ct')?.trim() || '';
        const name = $danmaku.getAttribute('data-uname')?.trim() || '';
        console.log(content, id, name);
        if (setAdmin.has(name)) {
            if (content === '开车')
                return startWaiting();
            if (content === '刹车')
                return cancelWaiting();
            for (const keyword of ['切换语音', '语音切换']) {
                if (content.startsWith(keyword))
                    return setVoice(content.replace(keyword, '').trim() || '');
            }
        }
        if (content === '排队')
            return add(name, id);
        for (const keyword of ['查询排队', '排队查询']) {
            if (content.startsWith(keyword))
                return find(content.replace(keyword, '').trim() || name, id);
        }
        if (isWaiting) {
            const idx = parseInt(content);
            if (idx > 0 && idx.toString() === content)
                return setWaiting.add(idx);
        }
        return;
    });
    const ping = async () => {
        const data = await get(`http://localhost:${port}/system/ping`);
        if (!data) {
            speak(Lines.server_none);
            return false;
        }
        speak(Lines.server_ready);
        return true;
    };
    const removeTimer = (token) => {
        const n = cacheTimer.get(token);
        if (!n)
            return;
        cacheTimer.delete(token);
        window.clearTimeout(n);
    };
    const setCurrent = async (idx) => {
        const data = await get(`http://localhost:${port}/queue/setCurrent?idx=${idx}`);
        if (!data)
            return;
        if (!data.idx)
            speak(Lines.waiting_fail);
        else
            speak(Lines.waiting_success, { idx: data.idx, name: data.name });
    };
    const setVoice = async (name) => {
        if (!name)
            return;
        if (!listVoice.includes(name)) {
            speak(Lines.voice_invalid);
            return;
        }
        const isLocal = [
            'huihui',
            'kangkang',
            'yaoyao',
        ].includes(name);
        if (!isLocal) {
            const result = await get('https://speech.platform.bing.com/');
            if (!result) {
                speak(Lines.voice_disconnect);
                return;
            }
        }
        let n = 0;
        const fn = () => {
            const voice = speechSynthesis.getVoices().filter(it => it.name.toLowerCase().includes(name))[0];
            if (!voice) {
                n++;
                if (n > 10) {
                    speak(Lines.voice_fail);
                    return;
                }
                addTimer('voice/set', 100, fn);
                return;
            }
            speaker.voice = voice;
            speak(Lines.voice_success, { name });
        };
        fn();
    };
    const speak = (message, data = {}) => {
        let msg = message;
        Object.keys(data).forEach(key => msg = msg.replace(`{${key}}`, data[key].toString()));
        log(msg);
        speaker.text = msg;
        window.speechSynthesis.speak(speaker);
    };
    const startWaiting = () => {
        if (isWaiting)
            return;
        isWaiting = true;
        setWaiting.clear();
        addTimer('waiting/countdown', delay, endWaiting);
        speak(Lines.waiting_start);
        addTimer('waiting/speak', delay - 10e3, () => speak(Lines.waiting_countdown));
    };
    const validate = (name, id) => {
        if (cacheId.has(id))
            return false;
        cacheId.add(id);
        if (setAdmin.has(name))
            return true;
        const ts = cacheName.get(name) || 0;
        const now = Date.now();
        if (now - ts < interval)
            return false;
        cacheName.set(name, now);
        return true;
    };
    addTimer('main', 1e3, main);
})();

QingJ © 2025

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