B站玩家指示器

B站评论区自动标注玩家,依据是动态里是否有游戏的相关内容。灵感来自于原神玩家指示器。

// ==UserScript==
// @name             B站玩家指示器
// @namespace        http://853lab.com/
// @version          1.1
// @description      B站评论区自动标注玩家,依据是动态里是否有游戏的相关内容。灵感来自于原神玩家指示器。
// @author           Sonic853
// @match            https://www.bilibili.com/video/*
// @icon             https://static.hdslb.com/images/favicon.ico
// @run-at           document-end
// @license          MIT License
// @grant            GM_setValue
// @grant            GM_getValue
// @grant            GM_registerMenuCommand
// @grant            GM_xmlhttpRequest
// @original-author  xulaupuz
// @original-license MIT
// @original-script  https://greasyfork.org/zh-CN/scripts/450720-%E5%8E%9F%E7%A5%9E%E7%8E%A9%E5%AE%B6%E6%8C%87%E7%A4%BA%E5%99%A8
// ==/UserScript==
// 凭实力走在对立面,
// 这就是我的觉悟。
// https://h.bilibili.com/67313825
// So FUCK YOU, miHoYo!

// 如何超越米哈游公司?

// ChatGPT:米哈游公司是一家拥有极高知名度和成功游戏产品的公司,要超越它需要做出一些艰苦的工作和采取正确的策略。以下是几个可能有用的建议:

// 注重产品质量:米哈游公司之所以能够取得成功,是因为他们推出了高质量、受玩家欢迎的游戏。要超越米哈游公司,您需要注重产品质量,确保游戏具有高水准的游戏体验和优秀的画面效果。

// 创新:在游戏行业,创新是非常重要的。米哈游公司之所以能够在市场上脱颖而出,是因为他们不断尝试新的玩法和元素。如果您想要超越米哈游公司,需要不断探索新的玩法和游戏元素,推出具有创新性的产品。

// 建立强大的社区:米哈游公司拥有一个庞大的社区,这个社区不仅可以帮助他们推广游戏,还可以为游戏提供反馈和建议。要超越米哈游公司,您需要建立一个强大的社区,与玩家建立良好的互动关系,并且倾听他们的反馈和建议。

// 投入足够的资源:游戏开发需要大量的资源,包括人力、物力、资金等。如果您想要超越米哈游公司,需要投入足够的资源,包括聘请优秀的开发团队、研发创新技术、开展广告宣传等。

// 扩大市场:米哈游公司的成功,与其在全球市场上的表现密不可分。如果您想要超越米哈游公司,需要扩大市场,将游戏推向更广泛的受众,特别是在海外市场上建立自己的品牌和口碑。

// 请注意,要超越米哈游公司是一个艰难的任务,需要付出大量的努力和资源。同时,市场竞争也是不断变化的,需要时刻保持敏锐的洞察力和灵活性,随时调整自己的策略。

(async function () {
  'use strict'

  const DEV_Log = Boolean(localStorage.getItem("Dev-853"))
  const localItem = "miHoYoCheck"
  const NAME = "miHoYoCheck"
  const D = () => {
    return new Date().toLocaleTimeString()
  }
  const Console_log = function (...text) {
    let d = new Date().toLocaleTimeString()
    console.log(`[${NAME}][${d}]: `, ...text)
  }
  const Console_Devlog = function (...text) {
    let d = new Date().toLocaleTimeString()
    DEV_Log && (console.log(`[${NAME}][${d}]: `, ...text))
  }
  const Console_error = function (...text) {
    let d = new Date().toLocaleTimeString()
    console.error(`[${NAME}][${d}]: `, ...text)
  }

  const snooze = ms => new Promise(resolve => setTimeout(resolve, ms))

  const RList = new class {
    time = 200
    #list = -1
    async Push() {
      this.#list++
      await snooze(this.#list * this.time)
      Promise.resolve().finally(() => {
        setTimeout(() => { this.#list-- }, (this.#list + 1) * this.time)
      })
    }
  }

  if (typeof GM_xmlhttpRequest === 'undefined'
    && typeof GM_registerMenuCommand === 'undefined'
    && typeof GM_setValue === 'undefined'
    && typeof GM_getValue === 'undefined') {
    console.error(`[${NAME}][${D()}]: `, "GM is no Ready.")
  } else {
    console.log(`[${NAME}][${D()}]: `, "GM is Ready.")
  }

  /**
   *
   * @param {string} url
   * @param {string} method
   * @param {Object.<string, any>} headers
   * @param {string} responseType
   * @param {*} successHandler
   * @param {*} errorHandler
   * @returns
   */
  let HTTPsend = function (url, method, headers, responseType, successHandler, errorHandler) {
    Console_Devlog(url)
    if (typeof GM_xmlhttpRequest != 'undefined') {
      return new Promise((rl, rj) => {
        try {
          GM_xmlhttpRequest({
            method,
            url,
            headers,
            responseType,
            onerror: function (response) {
              Console_Devlog(response.status)
              errorHandler && errorHandler(response.status)
              rj(response.status)
            },
            onload: function (response) {
              let status
              if (response.readyState == 4) { // `DONE`
                status = response.status
                if (status == 200) {
                  Console_Devlog(response.response)
                  successHandler && successHandler(response.response)
                  rl(response.response)
                } else {
                  Console_Devlog(status)
                  errorHandler && errorHandler(status)
                  rj(status)
                }
              }
            },
          })
        } catch (error) {
          rj(error)
        }
      })
    } else {
      return new Promise((rl, rj) => {
        try {
          let xhr = new XMLHttpRequest()
          xhr.open(method, url, true)
          xhr.withCredentials = true
          xhr.responseType = responseType
          xhr.onreadystatechange = function () {
            let status
            if (xhr.readyState == 4) { // `DONE`
              status = xhr.status
              if (status == 200) {
                Console_log(xhr.response)
                successHandler && successHandler(xhr.response)
                rl(xhr.response)
              } else {
                Console_log(status)
                errorHandler && errorHandler(status)
                rj(status)
              }
            }
          }
          xhr.send()
        } catch (error) {
          rj(error)
        }
      })
    }
  }

  let BLab8A = class {
    /**
     * @type {Object.<string, {
     * name: string,
     * uid: string
     *  }[]>} data
     */
    data
    constructor() {
      this.data = this.load()
    }
    load() {
      console.log(`[${NAME}][${D()}]: `, "正在加载数据")
      const defaultData = "{\"unknown\":[],\"miHoYo\":[],\"none_Player\":[]}"
      if (typeof GM_getValue !== 'undefined') {
        let gdata = GM_getValue(localItem, JSON.parse(defaultData))
        return gdata
      } else {
        let ldata = JSON.parse(localStorage.getItem(localItem) === null ? defaultData : localStorage.getItem(localItem))
        return ldata
      }
    }
    save(d) {
      console.log(`[${NAME}][${D()}]: `, "正在保存数据")
      d === undefined ? (d = this.data) : (this.data = d)
      typeof GM_getValue != 'undefined' ? GM_setValue(localItem, d) : localStorage.setItem(localItem, JSON.stringify(d))
      return this
    }
  }
  let bLab8A = new BLab8A()


  GM_registerMenuCommand("清空插件所有数据", () => {
    if (!confirm("确定要清空数据吗?")) return
    bLab8A.data = JSON.parse("{\"unknown\":[],\"miHoYo\":[],\"none_Player\":[]}")
    bLab8A.save()
    console.log(`[${NAME}][${D()}]: `, "数据已清空")
  })
  GM_registerMenuCommand("清空插件里的玩家数据", () => {
    if (!confirm("确定要清空数据吗?")) return
    // 除了 none_Player ,其他都清空
    for (let key in bLab8A.data) {
      if (key != "none_Player") {
        bLab8A.data[key] = []
      }
    }
    bLab8A.save()
    console.log(`[${NAME}][${D()}]: `, "数据已清空")
  })
  GM_registerMenuCommand("清空插件里的非玩家数据", () => {
    if (!confirm("确定要清空数据吗?")) return
    bLab8A.data.none_Player = []
    bLab8A.save()
    console.log(`[${NAME}][${D()}]: `, "数据已清空")
  })

  class Checker {
    running = false
    /**
       * @type {{
     * name: string,
     * uid: string,
     * dom: HTMLElement
     * }[]}
     */
    list = []
    /**
     * @type {{
     *  keywords: string[],
     *  soeWithKeywords: string[],
     *  uids: string[],
     *  games: string[],
     *  tag: string,
     *  type: string
     * }[]}
     */
    KeywordChecklist = [
      {
        keywords: [
          "玩原神",
          "原神玩家",
          "#原神#",
          "【原神",
          "《原神",
          "[原神",
          "米哈游",
          "崩坏三",
          "崩坏3",
          "崩坏学园",
          "崩坏学院",
          "miHoYo",
          "崩坏星穹铁道",
          "崩坏:星穹铁道",
          "崩坏:星穹铁道",
          "未定事件簿",
          "绝区零",
          "米游社",
          // "鹿鸣",
        ],
        soeWithKeywords: [
          "原神",
        ],
        uids: [
          // 崩坏3
          "256667467",
          // 米哈游miHoYo
          "318432901",
          // 崩坏学园2-灵依娘desu
          "133934",
          // 崩坏3第一偶像爱酱
          "27534330",
          // 崩坏3情报姬
          "358367842",
          // 崩坏星穹铁道
          "1340190821",
          // 原神
          "401742377",
          // 米哈游崩坏3客服娘
          "33307860",
          "52957002",
          // 米游姬
          "510189715",
          // yoyo鹿鸣_Lumi
          "488836173",
          // 绝区零
          "1636034895",
          // 未定事件簿
          "436175352",
        ],
        games: [
          "原神",
          "崩坏3",
          "崩坏学园2",
          "崩坏:星穹铁道",
          "绝区零",
        ],
        tag: "[米哈游玩家]",
        icon: "https://i1.hdslb.com/bfs/face/fda1b06144d41092a9ffb9a687f99bad078e7395.jpg",
        color: "#00abf1",
        type: "miHoYo",
      },
      {
        keywords: [
          "玩原神",
          "原神玩家",
          "#原神#",
          "【原神",
          "《原神",
          "[原神",
        ],
        soeWithKeywords: [
          "原神",
        ],
        uids: [
          // 原神
          "401742377",
        ],
        games: [
          "原神",
        ],
        tag: "[原神玩家]",
        icon: "https://i2.hdslb.com/bfs/face/d2a95376140fb1e5efbcbed70ef62891a3e5284f.jpg",
        color: "#ff0000",
        type: "Genshin",
      },
      // {
      //   keywords: [
      //     "王者荣耀",
      //   ],
      //   soeWithKeywords: [],
      //   uids: [
      //     "57863910",
      //     "392836434",
      //   ],
      //   games: [
      //     "王者荣耀",
      //   ],
      //   tag: "[王者荣耀玩家]",
      //   icon: "https://i2.hdslb.com/bfs/face/effbafff589a27f02148d15bca7e97031a31d772.jpg",
      //   color: "#79c7e7",
      //   type: "HOK",
      // },
    ]
    dynamicURL = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space"
    lastplaygameURL = "https://api.bilibili.com/x/space/lastplaygame"
    get is_new() {
      return document.querySelectorAll(".reply-list").length != 0
    }
    get getDom() {
      if (this.is_new) {
        return document.querySelectorAll(".reply-list")
        // sub-reply-list
      }
      else {
        return document.querySelectorAll(".comment-list")
        // reply-box
      }
    }
    /**
     * @param {HTMLElement} node 
     * @returns {{
     * name: string,
     * uid: string,
     * dom: HTMLElement
     * }}
     */
    getUser(node) {
      if (this.is_new) {
        // console.log(node)
        return {
          name: node.innerText,
          uid: node.getAttribute("data-user-id"),
          dom: node
        }
      }
      else {
        /** @type {HTMLAnchorElement} */
        let child = node.children[0]
        if (child.href == undefined) {
          for (let _child of node.children) {
            if (_child.href != undefined) {
              child = _child
              break
            }
          }
        }
        if (child === undefined && child.href == undefined) return
        // console.log(child)
        let uid = child.getAttribute("data-usercard-mid") === null ? child.href.replace(/[^\d]/g, "") : child.getAttribute("data-usercard-mid")
        return {
          name: child.innerText,
          uid,
          dom: child
        }
      }
    }
    getUserList() {
      /**
       * @type {{
       * name: string,
       * uid: string,
       * dom: HTMLElement
       * }[]}
       */
      let list = []
      if (this.is_new) {
        let users = document.querySelectorAll(".user-name")
        let subuser = document.querySelectorAll(".sub-user-name")
        for (let user of users) {
          let u = this.getUser(user)
          if (u != undefined
            && !list.some(e => e.uid == u.uid)) {
            {
              list.push(u)
            }
          }
        }
        for (let user of subuser) {
          let u = this.getUser(user)
          if (u != undefined
            && !list.some(e => e.uid == u.uid)) {
            {
              list.push(u)
            }
          }
        }
      }
      else {
        let users = document.querySelectorAll(".user")
        for (let user of users) {
          let u = this.getUser(user)
          if (u != undefined
            && !list.some(e => e.uid == u.uid)) {
            {
              list.push(u)
            }
          }
        }
      }
      return list
    }
    /**
     * @param {string} uid 
     * @param {string} offset
     */
    async getUserDynamic(uid, offset = "") {
      // ?offset=&host_mid=
      if (uid == undefined) return
      /**
       * @type {{
       * code: number,
       * message: string,
       * ttl: number,
       * data: {
       *  has_more: boolean,
       *  items: {
       *   id_str: string,
       *   modules: {
       *    module_dynamic: {
       *     desc?: {
       *      rich_text_nodes: {
       *       orig_text: string,
       *       text: string,
       *       type: string
       *      }[],
       *      text: string
       *     },
       *     major?: {
       *      archive: {
       *       aid: string,
       *       title: string,
       *       desc: string,
       *      }
       *     }
       *    }
       *   },
       *   orig?: {
       *    modules: {
       *     module_author: {
       *      mid: string,
       *      name: string
       *     },
       *     module_dynamic: {
       *      desc?: {
       *       rich_text_nodes: {
       *        orig_text: string,
       *        text: string,
       *        type: string
       *       }[],
       *       text: string
       *      },
       *      major?: {
       *       archive: {
       *        aid: string,
       *        title: string,
       *        desc: string,
       *       }
       *      }
       *     }
       *    },
       *    type: string
       *   },
       *   type: string
       *  }[],
       *  offset: string,
       *  update_baseline: string,
       *  update_num: number
       * }
       * }}
       */
      let data = JSON.parse(await HTTPsend(`${this.dynamicURL}?offset=${offset}&host_mid=${uid}`,
        "GET",
        {
          "Referer": `https://space.bilibili.com/${uid}/dynamic`,
        }
      ))
      if (data.code != 0) {
        console.error(`[${NAME}][${D()}]: `, data)
        return
      }
      let items = data.data.items
      return {
        items,
        offset: data.data.offset,
      }
    }
    async getUserLastPlayGame(uid) {
      /**
       * @type {{
       * code: number,
       * message: string,
       * ttl: number,
       * data: {
       *  website: string,
       *  name: string,
       *  image: string
       * }[]
       * }}
       */
      let data = JSON.parse(await HTTPsend(`${this.lastplaygameURL}?mid=${uid}`,
        "GET",
        {
          "Referer": `https://space.bilibili.com/${uid}`,
        }
      ))
      if (data.code != 0) {
        console.error(`[${NAME}][${D()}]: `, data)
        return []
      }
      let items = data.data
      return items
    }
    /**
     * @param {{
       *   id_str: string,
     *   modules: {
     *    module_dynamic: {
     *     desc?: {
     *      rich_text_nodes: {
     *       orig_text: string,
     *       text: string,
     *       type: string
     *      }[],
     *      text: string
     *     },
     *     major?: {
     *      archive: {
     *       aid: string,
     *       title: string,
     *       desc: string,
     *      }
     *     }
     *    }
     *   },
     *   orig?: {
     *    modules: {
     *     module_author: {
     *      mid: string,
     *      name: string
     *     },
     *     module_dynamic: {
     *      desc?: {
     *       rich_text_nodes: {
     *        orig_text: string,
     *        text: string,
     *        type: string
     *       }[],
     *       text: string
     *      },
     *      major?: {
     *       archive: {
     *        aid: string,
     *        title: string,
     *        desc: string,
     *       }
     *      }
     *     }
     *    },
     *    type: string
     *   },
     *   type: string
     *  }[]} items
     */
    checkDynamicsIncludeKeyword(items) {
      let types = []
      for (let item of items) {
        let _types = this.checkDynamicIncludeKeyword(item)
        for (let type of _types) {
          if (!types.includes(type)) {
            types.push(type)
          }
        }
      }
      return [...new Set(types)]
      // return types
    }
    /**
     * @param {{
       *   id_str: string,
     *   modules: {
     *    module_dynamic: {
     *     desc?: {
     *      rich_text_nodes: {
     *       orig_text: string,
     *       text: string,
     *       type: string
     *      }[],
     *      text: string
     *     },
     *     major?: {
     *      archive: {
     *       aid: string,
     *       title: string,
     *       desc: string,
     *      }
     *     }
     *    }
     *   },
     *   orig?: {
     *    modules: {
     *     module_author: {
     *      mid: string,
     *      name: string
     *     },
     *     module_dynamic: {
     *      desc?: {
     *       rich_text_nodes: {
     *        orig_text: string,
     *        text: string,
     *        type: string
     *       }[],
     *       text: string
     *      },
     *      major?: {
     *       archive: {
     *        aid: string,
     *        title: string,
     *        desc: string,
     *       }
     *      }
     *     }
     *    },
     *    type: string
     *   },
     *   type: string
     *  }} item
     */
    checkDynamicIncludeKeyword(item) {
      if (item == undefined) return []
      /** @type {string[]} */
      let types = []
      if (item.modules.module_dynamic.desc != null) {
        types = this.checkKeyword(item.modules.module_dynamic.desc.text)
      }
      switch (item.type) {
        case "DYNAMIC_TYPE_FORWARD":
          {
            if (item.orig != undefined) {
              for (let _item of this.KeywordChecklist) {
                for (let uid of _item.uids) {
                  if (item.orig.modules.module_author.mid == uid) {
                    if (!types.includes(_item.type)) {
                      types.push(_item.type)
                    }
                  }
                }
              }
              switch (item.orig.type) {
                case "DYNAMIC_TYPE_DRAW":
                case "DYNAMIC_TYPE_COMMON_SQUARE":
                case "DYNAMIC_TYPE_WORD":
                  {
                    if (item.orig.modules.module_dynamic.desc != null) {
                      let _types = this.checkKeyword(item.orig.modules.module_dynamic.desc.text)
                      // 加入 types 中并去重
                      for (let type of _types) {
                        if (!types.includes(type)) {
                          types.push(type)
                        }
                      }
                    }
                  }
                  break
                case "DYNAMIC_TYPE_AV":
                  {
                    if (item.orig.modules.module_dynamic.desc != null) {
                      let _types = this.checkKeyword(item.orig.modules.module_dynamic.desc.text)
                      // 加入 types 中并去重
                      for (let type of _types) {
                        if (!types.includes(type)) {
                          types.push(type)
                        }
                      }
                    }
                    if (item.orig.modules.module_dynamic.major != null) {
                      Console_Devlog(item.orig.modules.module_dynamic.major)
                      let _types = this.checkKeyword(item.orig.modules.module_dynamic.major?.archive?.title)
                      // 加入 types 中并去重
                      for (let type of _types) {
                        if (!types.includes(type)) {
                          types.push(type)
                        }
                      }
                      _types = this.checkKeyword(item.orig.modules.module_dynamic.major?.archive?.desc)
                      // 加入 types 中并去重
                      for (let type of _types) {
                        if (!types.includes(type)) {
                          types.push(type)
                        }
                      }
                    }
                  }
                  break
                case "DYNAMIC_TYPE_NONE":
                  {
                  }
                  break
              }
            }
          }
          break
        case "DYNAMIC_TYPE_AV":
          {
            if (item.modules.module_dynamic.major != null) {
              let _types = this.checkKeyword(item.modules.module_dynamic.major?.archive?.title)
              // 加入 types 中并去重
              for (let type of _types) {
                if (!types.includes(type)) {
                  types.push(type)
                }
              }
              _types = this.checkKeyword(item.modules.module_dynamic.major?.archive?.desc)
              // 加入 types 中并去重
              for (let type of _types) {
                if (!types.includes(type)) {
                  types.push(type)
                }
              }
            }
          }
          break
        case "DYNAMIC_TYPE_DRAW":
        case "DYNAMIC_TYPE_WORD":
          {
          }
          break
        default:
          break
      }
      return [...new Set(types)]
      // return types
    }
    /**
     * 
     * @param {string} text 
     * @returns {string[]}
     */
    checkKeyword(text) {
      let types = []
      // 循环 this.KeywordChecklist
      if (text != null) {
        for (let item of this.KeywordChecklist) {
          for (let keyword of item.keywords) {
            if (text.includes(keyword)
              && !types.includes(item.type)) {
              types.push(item.type)
            }
          }
          for (let keyword of item.soeWithKeywords) {
            if ((text.startsWith(keyword) || text.endsWith(keyword))
              && !types.includes(item.type)) {
              types.push(item.type)
            }
          }
        }
      }
      return types
    }
    async checkUser() {
      if (this.running) return
      if (this.list.length == 0) return
      this.running = true
      // 复制一份 this.list
      let list = this.list.slice()
      for (let user of list) {
        /** 
         * @type {{
         * website: string;
         * name: string;
         * image: string;
         * }[]|undefined} */
        let games = undefined
        let needCheck = false
        for (let item of this.KeywordChecklist) {
          if (!bLab8A.data[item.type]) {
            bLab8A.data[item.type] = []
          }
          if (bLab8A.data[item.type].some(e => e.uid == user.uid)) {
            console.log(`[${NAME}][${D()}]: `, `已知的${item.tag}`, user)
            if (user.dom != undefined) {
              this.insertSpan(user.dom, item)
            }
            continue
          }
          else if (!bLab8A.data.none_Player.some(e => e.uid == user.uid)) {
            needCheck = true
          }
        }
        if (!needCheck) {
          continue
        }
        console.log(`[${NAME}][${D()}]: `, `检查用户 ${user.name}`)
        /** @type {string[]} */
        let types = []
        if (games === undefined) {
          games = await this.getUserLastPlayGame(user.uid)
          if (games.length != 0) {
            for (let game of games) {
              // 判断 game.name 是否和 this.KeywordChecklist 里的 games 一致
              for (let item of this.KeywordChecklist) {
                if (item.games.includes(game.name)
                  && !types.includes(item.type)) {
                  types.push(item.type)
                }
              }
            }
          }
        }
        // await RList.Push()
        let dynamic = await this.getUserDynamic(user.uid)
        if (dynamic == undefined) continue
        if (dynamic.items.length == 0) continue
        types = [...new Set([...types, ...this.checkDynamicsIncludeKeyword(dynamic.items)])]
        // await RList.Push()
        dynamic = await this.getUserDynamic(user.uid, dynamic.offset)
        if (dynamic != undefined
          && dynamic.items.length !== 0) {
          // 合并 types 并去重
          types = [...new Set([...types, ...this.checkDynamicsIncludeKeyword(dynamic.items)])]
        }
        if (types.length !== 0) {
          console.log(`[${NAME}][${D()}]: `, `游戏玩家${types.join(",")}`, user)
          for (let type of types) {
            if (!bLab8A.data[type]) {
              bLab8A.data[type] = []
            }
            bLab8A.data[type].push({
              uid: user.uid,
              name: user.name,
            })
            bLab8A.save()
            // 从 this.KeywordChecklist 找到 type 一致的项
            let _item = this.KeywordChecklist.find(e => e.type == type)
            if (_item != undefined) {
              this.insertSpan(user.dom, _item)
            }
          }
        }
        else {
          console.log(`[${NAME}][${D()}]: `, "非游戏玩家", user)
          if (!bLab8A.data.none_Player) {
            bLab8A.data.none_Player = []
          }
          bLab8A.data.none_Player.push({
            uid: user.uid,
            name: user.name,
          })
          bLab8A.save()
        }
      }
      this.running = false
      // 删除已经检查过的用户
      for (let user of list) {
        this.list.splice(this.list.indexOf(user), 1)
      }
      if (this.list.length != 0) {
        this.checkUser()
      }
    }
    /**
     * @param {HTMLElement} dom 
     * @param {{
     *  tag: string,
     *  type: string,
     *  color: string,
     * }} item 
     */
    insertSpan(dom, item) {
      if (dom != undefined) {
        if (dom.querySelector(`span.check_tag_${item.type}`) != null) {
          return
        }
        let span = document.createElement("span")
        span.classList.add(`check_tag_${item.type}`)
        span.style.color = item.color
        span.title = `非准确结果,是否为${item.tag}请自行判断`
        span.innerText = item.tag
        dom.appendChild(span)
      }
    }
  }
  let checker = new Checker()

  /**
   * @param {MutationRecord[]} mutationList 
   * @param {MutationObserver} observer 
   */
  let callback = (mutationList, observer) => {
    Console_Devlog(`[${NAME}][${D()}]: `, "callback", mutationList)
    let list = checker.getUserList()
    for (let item of list) {
      if (!checker.list.some(e => e.dom == item.dom)) {
        checker.list.push(item)
      }
    }
    checker.checkUser()
    // mutationList.forEach(mutation => {
    //   if (mutation.type == "childList") {
    //     // [0].addedNodes[0].children[1].children[0].children[0].href
    //     // mutation.addedNodes.forEach(node => {
    //     //   if (checker.is_new) {
    //     //     try {

    //     //     } catch (error) {

    //     //     }
    //     //   }
    //     //   else {
    //     //     try {
    //     //       console.log(node.childNodes[1].childNodes[0].childNodes[0].href)
    //     //     } catch (error) {

    //     //     }
    //     //   }
    //     // })
    //   }
    // })
  }

  /** @type {MutationObserverInit} */
  let observerOption = {
    childList: true,
    subtree: true,
  }

  let observer = new MutationObserver(callback)
  // while 检测 checker.getDom
  while (checker.getDom.length == 0) {
    console.log(`[${NAME}][${D()}]: `, "寻找中")
    await RList.Push()
  }
  for (let _dom of checker.getDom) {
    observer.observe(_dom, observerOption)
  }
  // observer.observe(checker.getDom[0], observerOption)
  Console_Devlog(`[${NAME}][${D()}]: `, "开始监听")

  // 先获取一次列表
  let list = checker.getUserList()
  for (let item of list) {
    if (!checker.list.some(e => e.dom == item.dom)) {
      checker.list.push(item)
    }
  }
  checker.checkUser()
})()