ayase

A userscript + websocket to observe and record Bilibili live danmakus.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ayase
// @namespace    https://github.com/Vincent-the-gamer/ayase
// @version      0.1.1
// @author       Vincent-the-gamer
// @description  A userscript + websocket to observe and record Bilibili live danmakus.
// @license      https://github.com/Vincent-the-gamer/ayase/blob/main/LICENSE.md
// @icon         https://img.moegirl.org.cn/common/6/61/%E4%B8%89%E5%8F%B8%E7%BB%AB%E6%BF%91_%E8%A7%92%E8%89%B2%E6%AD%8C%E4%B8%93%E8%BE%91%E5%B0%81%E9%9D%A2.jpg
// @match        https://live.bilibili.com/*
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  const d=new Set;const importCSS = async e=>{d.has(e)||(d.add(e),(t=>{typeof GM_addStyle=="function"?GM_addStyle(t):(document.head||document.documentElement).appendChild(document.createElement("style")).append(t);})(e));};

  let currentUser = null;
  let danmakuType = null;
  function game2048(ws, danmaku) {
    if (danmaku.text === "2048上机") {
      currentUser = danmaku.uname;
      danmakuType = "2048";
      console.log(`2048用户: [${danmaku.uname}] 已上机!`);
    }
    if (danmaku.uname === "诡锋Vincent" && danmaku.text === "强制下机") {
      currentUser = null;
      danmakuType = "danmaku";
      console.log(`2048 -- 管理员已强制用户下机!`);
    }
    if (!currentUser) {
      danmakuType = "danmaku";
      ws.send(
        JSON.stringify({
          ...danmaku,
          type: danmakuType
        })
      );
      return;
    }
    if (currentUser === danmaku.uname) {
      if (danmaku.text === "2048下机") {
        currentUser = null;
        danmakuType = "danmaku";
        console.log(`2048用户: [${danmaku.uname}] 已下机!`);
        ws.send(
          JSON.stringify({
            ...danmaku,
            type: danmakuType
          })
        );
      } else {
        danmakuType = "2048";
        console.log(`2048用户: [${danmaku.uname}] 发送指令`, danmaku);
        ws.send(
          JSON.stringify({
            ...danmaku,
            type: danmakuType
          })
        );
      }
    }
  }
  function setupObserver(serverLink) {
    let observer = null;
    const startObserver = () => {
      try {
        const ws = new WebSocket(serverLink);
        observer = new MutationObserver((mutations, _) => {
          mutations.forEach((mutation) => {
            if (mutation.type === "childList") {
              const addedNodes = Array.from(mutation.addedNodes);
              const node = addedNodes[0];
              const danmaku = {
                type: "danmaku",
                uname: node.querySelector("span.user-name")?.innerHTML,
                text: node.getAttribute("data-danmaku"),
                img: "",
                replacement: ""
              };
              const emoticon = node.querySelector("span.emoticon");
              if (emoticon) {
                danmaku.img = emoticon.querySelector("img.open-menu")?.getAttribute("src");
                danmaku.replacement = emoticon.querySelector("span.open-menu")?.innerHTML;
              }
              game2048(ws, danmaku);
            }
          });
        });
        const config = {
          attributes: false,
          childList: true,
          subtree: true
        };
        const danmakuDOMList = document.querySelector(".chat-history-list");
        if (danmakuDOMList) {
          observer.observe(danmakuDOMList, config);
        }
        alert(`WebSocket连接: ${serverLink}`);
      } catch (e) {
        alert(`WebSocket连接错误: ${e}`);
      }
    };
    const stopObserver = () => {
      observer?.disconnect();
    };
    return { observer, startObserver, stopObserver };
  }
  const styleCss = ".config{position:fixed;display:flex;flex-direction:row;justify-content:center;align-items:center;gap:7px;top:5px;right:5px;width:fit-content;height:fit-content;border-radius:10px;padding:8px;z-index:1000;background:#40e0d0}.config img{width:30px;height:30px}.config input{width:200px;height:20px}.config button{background-color:#000;color:#fff;height:25px;border-radius:3px}.config button:hover{background-color:orange}";
  importCSS(styleCss);
  (() => {
    const app = document.createElement("div");
    document.body.append(app);
    return app;
  })().innerHTML = `
  <div class="config" id="ayase-app">
      <img src="https://i0.hdslb.com/bfs/article/eba9c4eeae160d5f72edf1d0c1eb409a3dd8f4e7.png"/>
      <span>WebSocket地址: </span>
      <input id="ayase-link" type="text" value="ws://localhost:8081/websocket"/>
      <button id="start-ayase">连接</button>
  </div>
`;
  const input = document.querySelector("#ayase-link");
  const button = document.querySelector("#start-ayase");
  let inputValue = input.value;
  input.addEventListener("input", (event) => {
    inputValue = event.target.value;
  });
  button.addEventListener("click", () => {
    const { observer, startObserver, stopObserver } = setupObserver(inputValue);
    if (!observer) {
      stopObserver();
    }
    startObserver();
  });

})();