// ==UserScript== // @name 旋转的五分硬币排队 // @namespace http://tampermonkey.net/ // @version 0.0.9 // @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 // @downloadURL none // ==/UserScript== (() => { if (window.top !== window.self) return; const cacheId = new Set(); const cacheName = new Map(); const interval = 30e3; const listMaster = ['旋转的五分硬币', '御御子']; const observer = new MutationObserver(() => pick()); const port = 9644; const speaker = new SpeechSynthesisUtterance(); let isPaused = false; const add = async (name, id) => { if (!validate(name, id)) return; if (isPaused) { speak('现在暂时不能排队'); return; } const data = await get(`http://localhost:${port}/add?name=${name}`); if (!data) return; speak(data.message); }; const get = (url) => new Promise(resolve => { GM_xmlhttpRequest({ method: 'GET', onerror: () => resolve(null), onload: (response) => resolve(JSON.parse(response.responseText)), url, }); }); const log = (message) => { console.log(message); return message; }; const main = () => { pauseVideo(); observe(); }; 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 pause = (name, id) => { if (!validate(name, id)) return; isPaused = true; speak('排队已结束'); }; 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() || ''; if ([ '恢复排队', '排队恢复', '开始排队', '排队开始', ].includes(content) && listMaster.includes(name)) return resume(name, id); if ([ '结束排队', '排队结束', '停止排队', '排队停止', '暂停排队', '排队暂停', ].includes(content) && listMaster.includes(name)) return pause(name, id); if (content === '排队') return add(name, id); if (content.startsWith('查询排队') || content.startsWith('排队查询')) { const name2 = content .replace('查询排队', '') .replace('排队查询', '') .trim() || ''; return search(name2 || name, id); } return; }); const resume = (name, id) => { if (!validate(name, id)) return; isPaused = false; speak('排队已开始'); }; const search = async (name, id) => { if (!validate(name, id)) return; const data = await get(`http://localhost:${port}/search?name=${name}`); if (!data) return; speak(data.message); }; const speak = (message) => { log(message); speaker.text = message; window.speechSynthesis.speak(speaker); }; const validate = (name, id) => { if (cacheId.has(id)) return false; cacheId.add(id); const ts = cacheName.get(name) || 0; const now = Date.now(); if (now - ts < interval) return false; cacheName.set(name, now); return true; }; window.setTimeout(main, 1e3); })();