创作中心广告管理自动屏蔽米哈游相关的广告

自动屏蔽在“创作中心”→“创作激励”→“广告管理”中与米哈游相关的广告。So FUCK YOU, miHoYo!

// ==UserScript==
// @name         创作中心广告管理自动屏蔽米哈游相关的广告
// @namespace    http://853lab.com/
// @version      1.2
// @description  自动屏蔽在“创作中心”→“创作激励”→“广告管理”中与米哈游相关的广告。So FUCK YOU, miHoYo!
// @author       Sonic853
// @match        https://member.bilibili.com/*
// @match        https://cm.bilibili.com/*
// @match        https://space.bilibili.com/*
// @match        https://www.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

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

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

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

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

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

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

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

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


// phind:从所提供的上下文来看,如何超越米哈游公司的问题并不完全清楚。在全球范围内大受欢迎。

// 值得注意的是,米哈游已将其投资组合从游戏扩展到能源领域,通过投资聚变能源公司能量奇点,此举表明米哈游并不局限于游戏,而是愿意探索其他增长和投资途径。

// 要回答如何超越米哈游的问题,我们需要了解是什么让米哈游成功,以及其他公司如何复制这种成功。促成米哈游成功的一些因素是:

// - 聚焦核心人群:米哈游清楚了解目标人群,迎合他们的需求和喜好。忠实的粉丝群。
// - 始终如一的质量和创新:miHoYo 以提供创新和引人入胜的高质量游戏而闻名。“Genshin Impact”就是一个很好的例子,因为它引入了新的游戏机制和功能,让玩家保持参与。
// - 社区参与:米哈游围绕其游戏培养了一个强大的社区,这有助于提升其品牌和扩大影响力。公司定期与粉丝互动,并将他们的反馈纳入其游戏中。

// 为了超越米哈游,其他公司需要关注这些因素并围绕其产品建立强大的品牌和社区。此外,他们还需要创新并提供满足目标受众需求和偏好的高质量产品。

// 不过,值得注意的是,米哈游的成功并不仅仅是因为专注于游戏,公司还涉足其他领域,比如能源,这表明它愿意探索新的增长和投资途径,超越米哈游,公司需要具有灵活性和适应性,并愿意探索核心业务以外的新机会。

// bing:超越米哈游公司是一个很有挑战性的目标,因为米哈游是一家成立于2012年的中国游戏公司,拥有多款高品质人气产品,如《崩坏3》和《原神》12。在2021年2月,米哈游的收入超越了Supercell,成为全球第六大游戏公司3。如果你想超越米哈游公司,你可能需要考虑以下几个方面:

// 你的产品定位和目标用户是什么?
// 你的产品有什么独特的创意和优势?
// 你的产品如何在全球市场上进行推广和运营?
// 你的产品如何与其他竞争对手进行区分和竞争?
// 你的产品如何保持持续的更新和创新?
// 这些问题并不容易回答,但也不是不可能实现。我希望你能找到自己的答案,并取得成功。


(async function () {
  'use strict'
  const DEV_Log = Boolean(localStorage.getItem("Dev-853"))
  const localItem = "BlockmiHoYoAds"
  const NAME = "Block miHoYo Ads"
  const D = () => {
    return new Date().toLocaleTimeString()
  }

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

  const RList = class {
    static time = 200
    static #list = -1
    static 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.")
  }

  const BLab8A = class {
    /**
     * @type {Object.<string, string>} data
     */
    static data = this.load()
    // constructor() {
    //   this.data = this.load()
    // }
    static load() {
      console.log(`[${NAME}][${D()}]: `, "正在加载数据")
      const defaultData = "{}"
      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
      }
    }
    static 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
    }
  }

  class HSRequest {
    /**
     * Default: "GM"
     * @type {"GM"|"XHR"|"FETCH"|undefined}
     */
    mode = 'GM'
    /**
     * ["GM"|"XHR"|"FETCH"] Required. The URL to make the request to. Must be an absolute URL, beginning with the scheme. May be relative to the current page.
     * @type {string}
     */
    url = ''
    /**
     * ["GM"|"XHR"|"FETCH"] Required. Type of HTTP request to make (E.G. "GET", "POST")
     * @type {"GET"|"POST"|"PUT"|"DELETE"|"HEAD"|"OPTIONS"|"PATCH"}
     */
    method = 'GET'
    /**
     * ["GM"|"XHR"|"FETCH"] Optional. A set of headers to include in the request.
     * XHR and FETCH modes may not work.
     * @type {{
     * [key: string]: any
     * }}
     */
    headers = {}
    /**
     * ["GM"|"XHR"] Optional. Decode the response as specified type. Accepted values are "", "arraybuffer", "blob", "document", "json", "text", "ms-stream". Default value is "text". See XMLHttpRequest responseType.
     * @type {XMLHttpRequestResponseType}
     */
    responseType
    /**
     * ["GM"|"XHR"] Optional. Data to send in the request body. Usually for POST method requests.
     * like "username=johndoe&password=xyz123"
     * @type {string|undefined}
     */
    data
    /**
     * ["XHR"] Initiates the request. The body argument provides the request body, if any, and is ignored if the request method is GET or HEAD.
     * @type {Document | XMLHttpRequestBodyInit | null}
     */
    body
    /**
     * ["GM"] Optional, default false. When true, the data is sent as a Blob.
     * @type {boolean}
     */
    binary
    /**
     * ["GM"] (Compatibility: 1.10+) Optional, any object. This object will also be the context property of the #Response Object.
     * The same object passed into the original request.
     * @type {{
     * [key: string]: any
     * }}
     */
    context
    /**
     * ["GM"|"XHR"] Optional. A MIME type to specify with the request (E.G. "text/html; charset=ISO-8859-1").
     * @type {string}
     */
    overrideMimeType
    /**
     * ["GM"] Optional. User name to use for authentication purposes.
     * @type {string}
     */
    user
    /**
     * ["GM"] Optional. Password to use for authentication purposes.
     * @type {string}
     */
    password
    /**
     * ["GM"|"XHR"] Defaults to false. When true, this is a synchronous request. Be careful: The entire Firefox UI will be locked and frozen until the request completes. In this mode, more data will be available in the return value.
     * @type {boolean}
     * @deprecated
     */
    synchronous
    /**
     * ["GM"|"XHR"] The number of milliseconds to wait before terminating the call; zero (the default) means wait forever.
     * @type {number}
     */
    timeout
    /**
     * ["GM"] Optional. Object containing optional function callbacks (onabort, onerror, onload, onprogress) to monitor the upload of data. Each is passed one argument, the #Response Object.
    * @type {{
     *  onabort?: Function | undefined;
     *  onerror?: Function | undefined;
     *  onload?: Function | undefined;
     *  onprogress?: Function | undefined;
     * }}
     */
    upload
    /**
     * ["XHR"|"FETCH"] A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials.
     * @type {RequestCredentials}
     */
    credentials
    /**
     * ["FETCH"] RequestInit
     * @type {RequestInit}
     */
    requestInit
    /**
     * ["XHR"|"FETCH"] True when credentials are to be included in a cross-origin request. False when they are to be excluded in a cross-origin request and when cookies are to be ignored in its response. Initially false.
     * @type {boolean}
     */
    withCredentials
    /**
     * Debug mode. Defaults to false.
     * @type {boolean}
     */
    debug = false
    /**
     * Optional. Will be called when the request is aborted. Passed one argument, the #Response Object.
     * @type {Function}
     */
    onabort
    /**
     * Optional. Will be called if an error occurs while processing the request. Passed one argument, the #Response Object.
     * @type {Function}
     */
    onerror
    /**
     * Optional. Will be called when the request has completed successfully. Passed one argument, the #Response Object.
     * @type {Function}
     */
    onload
    /**
     * Optional. Will be called when the request progress changes. Passed one argument, the #Response Object.
     * @type {Function}
     */
    onprogress
    /**
     * Optional. Will be called repeatedly while the request is in progress. Passed one argument, the #Response Object.
     * @type {Function}
     */
    onreadystatechange
    /**
     * Optional. Will be called if/when the request times out. Passed one argument, the #Response Object.
     * @type {Function}
     */
    ontimeout
    /**
     * @param {Function} handler
     */
    set successHandler(handler) {
      this.onload = handler
    }
    get successHandler() {
      return this.onload
    }
    /**
     * @param {Function} handler
     */
    set errorHandler(handler) {
      this.onerror = handler
    }
    get errorHandler() {
      return this.onerror
    }
    /**
     * 
     * @param {{
     * mode?: "GM"|"XHR"|"FETCH"|undefined
     * url?: string
     * method?: "GET"|"POST"|"PUT"|"DELETE"|"HEAD"|"OPTIONS"|"PATCH"
     * headers?: {
     *  [key: string]: any
     * }
     * responseType?: XMLHttpRequestResponseType
     * data?: string|undefined
     * body?: Document | XMLHttpRequestBodyInit | null
     * binary?: boolean
     * context?: {
     *  [key: string]: any
     * }
     * overrideMimeType?: string
     * user?: string
     * password?: string
     * synchronous?: boolean
     * timeout?: number
     * upload?: {
     *  onabort?: Function | undefined;
     *  onerror?: Function | undefined;
     *  onload?: Function | undefined;
     *  onprogress?: Function | undefined;
     * }
     * credentials?: RequestCredentials
     * requestInit?: RequestInit
     * withCredentials?: boolean
     * debug?: boolean
     * onabort?: Function
     * onerror?: Function
     * onload?: Function
     * onprogress?: Function
     * onreadystatechange?: Function
     * ontimeout?: Function
     * }} [options] 
     */
    constructor({
      mode,
      url,
      method,
      headers,
      responseType,
      data,
      body,
      binary,
      context,
      overrideMimeType,
      user,
      password,
      synchronous,
      timeout,
      upload,
      credentials,
      requestInit,
      withCredentials,
      debug,
      onabort,
      onerror,
      onload,
      onprogress,
      onreadystatechange,
      ontimeout
    } = {}) {
      this.mode = mode
      this.url = url
      this.method = method
      this.headers = headers
      this.responseType = responseType
      this.data = data
      this.body = body
      this.binary = binary
      this.context = context
      this.overrideMimeType = overrideMimeType
      this.user = user
      this.password = password
      this.synchronous = synchronous
      this.timeout = timeout
      this.upload = upload
      this.debug = debug
      this.onabort = onabort
      this.onerror = onerror
      this.onload = onload
      this.credentials = credentials
      this.requestInit = requestInit
      this.withCredentials = withCredentials
      this.onprogress = onprogress
      this.onreadystatechange = onreadystatechange
      this.ontimeout = ontimeout
    }
  }
  /**
   * 
   * @param {HSRequest} hsRequest 
   * @param {boolean} stringOnly
   * @returns {Promise<string>|Promise<XMLHttpRequest>|Promise<Response>|Promise<Tampermonkey.Request>}
   */
  let HTTPSendPro = (hsRequest, stringOnly = false) => {
    hsRequest.mode = hsRequest.mode ?? 'GM'
    if (hsRequest.mode.toUpperCase() === 'GM' && typeof GM_xmlhttpRequest === 'undefined') {
      console.log('HTTPSendPro: GM_xmlhttpRequest not found, using XHR')
      hsRequest.mode = 'XHR'
    }
    hsRequest.method = hsRequest.method ?? 'GET'
    return new Promise((rl, rj) => {
      switch (hsRequest.mode.toUpperCase()) {
        default:
        case 'GM':
          {
            if (hsRequest.debug) console.log(`HTTPSendPro: GM mode - ${hsRequest.method} ${hsRequest.url}`)
            try {
              /**
               * @type {{
               * [key: string]: any
               * }}
               */
              const hsr = {
                method: hsRequest.method,
                url: hsRequest.url,
              }
              if (hsRequest.binary !== undefined) {
                hsr.binary = hsRequest.binary
              }
              if (hsRequest.context !== undefined) {
                hsr.context = hsRequest.context
              }
              if (hsRequest.data !== undefined) {
                hsr.data = hsRequest.data
              }
              if (hsRequest.headers !== undefined) {
                hsr.headers = hsRequest.headers
              }
              if (hsRequest.overrideMimeType !== undefined) {
                hsr.overrideMimeType = hsRequest.overrideMimeType
              }
              if (hsRequest.user !== undefined) {
                hsr.user = hsRequest.user
              }
              if (hsRequest.password !== undefined) {
                hsr.password = hsRequest.password
              }
              if (hsRequest.responseType !== undefined) {
                hsr.responseType = hsRequest.responseType
              }
              if (hsRequest.synchronous !== undefined) {
                hsr.synchronous = hsRequest.synchronous
              }
              if (hsRequest.timeout !== undefined) {
                hsr.timeout = hsRequest.timeout
              }
              if (hsRequest.upload !== undefined) {
                hsr.upload = hsRequest.upload
              }
              hsr.onabort = (response) => {
                if (hsRequest.onabort) hsRequest.onabort(response)
                rj(response)
              }
              hsr.onerror = (response) => {
                if (hsRequest.onerror) hsRequest.onerror(response)
                rj(response)
              }
              hsr.onload = (response) => {
                /**
                 * @type {number}
                 */
                let status
                if (response.readyState == 4) { // `DONE`
                  status = response.status
                  if (status == 200) {
                    if (hsRequest.onload) {
                      hsRequest.onload(stringOnly ? response.response : response)
                    }
                    rl(stringOnly ? response.response : response)
                  } else {
                    if (hsRequest.onerror) hsRequest.onerror(response)
                    rj(response)
                  }
                }
              }
              hsr.onprogress = (response) => {
                if (hsRequest.onprogress) hsRequest.onprogress(response)
              }
              hsr.onreadystatechange = (response) => {
                if (hsRequest.onreadystatechange) hsRequest.onreadystatechange(response)
              }
              hsr.ontimeout = (response) => {
                if (hsRequest.ontimeout) hsRequest.ontimeout(response)
                rj(response)
              }
              GM_xmlhttpRequest(hsr)
            } catch (error) {
              rj(error)
            }
          }
          break;
        case 'XHR':
          {
            if (hsRequest.debug) console.log(`HTTPSendPro: XHR mode - ${hsRequest.method} ${hsRequest.url}`)
            try {
              const xhr = new XMLHttpRequest()
              const _async = hsRequest.synchronous === undefined ? true : !hsRequest.synchronous
              if (hsRequest.user !== undefined
                || hsRequest.password !== undefined) {
                xhr.open(hsRequest.method, hsRequest.url, _async, hsRequest.user, hsRequest.password)
              }
              else {
                xhr.open(hsRequest.method, hsRequest.url, _async)
              }
              if (hsRequest.withCredentials !== undefined) {
                xhr.withCredentials = hsRequest.withCredentials
              }
              else if (hsRequest.credentials !== undefined) {
                switch (hsRequest.credentials) {
                  case 'omit':
                  case 'same-origin':
                    xhr.withCredentials = false
                    break;
                  case 'include':
                    xhr.withCredentials = true
                    break;
                }
              }
              if (hsRequest.responseType !== undefined) {
                xhr.responseType = hsRequest.responseType
              }
              if (hsRequest.timeout !== undefined) {
                xhr.timeout = hsRequest.timeout
              }
              if (hsRequest.overrideMimeType !== undefined) {
                xhr.overrideMimeType(hsRequest.overrideMimeType)
              }
              if (hsRequest.headers !== undefined) {
                for (const key in hsRequest.headers) {
                  xhr.setRequestHeader(key, hsRequest.headers[key])
                }
              }
              xhr.onabort = (event) => {
                if (hsRequest.onabort) hsRequest.onabort(xhr, event)
                rj(event)
              }
              let errored = false
              xhr.onerror = (event) => {
                if (!errored) {
                  errored = true
                  if (hsRequest.onerror) {
                    hsRequest.onerror(xhr, event)
                  }
                  rj(xhr, event)
                }
              }
              let loaded = false
              xhr.onload = (event) => {
                if (!loaded) {
                  loaded = true
                  if (hsRequest.onload) {
                    if (stringOnly) {
                      hsRequest.onload(xhr.response)
                    }
                    else {
                      hsRequest.onload(xhr, event)
                    }
                  }
                  if (stringOnly) {
                    rl(xhr.response)
                  }
                  else {
                    rl(xhr)
                  }
                }
              }
              xhr.onprogress = (event) => {
                if (hsRequest.onprogress) hsRequest.onprogress(xhr, event)
              }
              xhr.onreadystatechange = (event) => {
                if (hsRequest.onreadystatechange) hsRequest.onreadystatechange(xhr, event)
                else {
                  if (xhr.readyState == 4) { // `DONE`
                    if (xhr.status == 200) {
                      if (!loaded) {
                        loaded = true
                        if (hsRequest.onload) {
                          if (stringOnly) {
                            hsRequest.onload(xhr.response)
                          }
                          else {
                            hsRequest.onload(xhr, event)
                          }
                        }
                        if (stringOnly) {
                          rl(xhr.response)
                        }
                        else {
                          rl(xhr)
                        }
                      }
                    } else {
                      if (!errored) {
                        errored = true
                        if (hsRequest.onerror) hsRequest.onerror(xhr, event)
                        rj(xhr)
                      }
                    }
                  }
                }
              }
              xhr.ontimeout = (event) => {
                if (hsRequest.ontimeout) hsRequest.ontimeout(event)
                rj(event)
              }
              if (hsRequest.data !== undefined) {
                xhr.send(hsRequest.data)
              }
              else if (hsRequest.body !== undefined) {
                xhr.send(hsRequest.body)
              }
              else {
                xhr.send()
              }
            } catch (error) {
              rj(error)
            }
          }
          break;
        case 'FETCH':
          {
            if (hsRequest.debug) console.log(`HTTPSendPro: FETCH mode - ${hsRequest.method} ${hsRequest.url}`)
            try {
              let url = new URL(hsRequest.url)
              if (hsRequest.user !== undefined) {
                url.username = hsRequest.user
              }
              if (hsRequest.password !== undefined) {
                url.password = hsRequest.password
              }
              /**
               * @type {RequestInit}
               */
              const _init = hsRequest.requestInit || {
                method: hsRequest.method,
              }
              if (hsRequest.headers !== undefined) {
                _init.headers = hsRequest.headers
              }
              if (hsRequest.credentials !== undefined) {
                _init.credentials = hsRequest.credentials
              }
              else if (hsRequest.withCredentials !== undefined) {
                _init.credentials = hsRequest.withCredentials ? 'include' : 'omit'
              }
              fetch(url, _init).then((response) => {
                if (response.status == 200) {
                  if (hsRequest.onload) {
                    if (stringOnly) {
                      response.text().then((text) => {
                        hsRequest.onload(text)
                      })
                    }
                    else {
                      hsRequest.onload(response)
                    }
                  }
                  if (stringOnly) {
                    response.text().then((text) => {
                      rl(text)
                    })
                  }
                  else {
                    rl(response)
                  }
                }
                else {
                  if (hsRequest.onerror) hsRequest.onerror(response)
                  rj(response)
                }
              }).catch((error) => {
                if (hsRequest.onerror) hsRequest.onerror(error)
                rj(error)
              })
            } catch (error) {
              rj(error)
            }
          }
          break;
      }
    })
  }

  class BV2AV {
    static table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"
    /**
     * @type {[key: string]: number}
     */
    static tr = {}
    static s = [11, 10, 3, 8, 4, 6]
    static xor = 177451812
    static add = 8728348608
    // constructor() {
    //   for (let i = 0; i < 58; ++i) {
    //     this.tr[this.table[i]] = i
    //   }
    // }
    /**
     * BV2AV
     * @param {string} x 
     * @returns {string}
     */
    static dec(x) {
      let r = 0
      for (let i = 0; i < 6; ++i) {
        r += this.tr[x[this.s[i]]] * 58 ** i
      }
      return "av" + String((r - this.add) ^ this.xor)
    }
  }
  for (let i = 0; i < 58; ++i) {
    BV2AV.tr[BV2AV.table[i]] = i
  }

  class AdsManager {
    static filter_ads_by_pageUrl = "https://cm.bilibili.com/meet/api/open_api/v1/up/web/trust_ad/filter_ads_by_page"
    static filter_adsUrl = "https://cm.bilibili.com/meet/api/open_api/v1/up/web/trust_ad/filter_ads"
    // https://www.bilibili.com/video/av546869571/?t=21
    static keywords = [
      "米哈游",
      "miHoYo",
      "HoYoverse",
      "HoYoLAB",
      "崩坏学园",
      "崩坏学院",
      "崩坏3",
      "崩坏三",
      // "女武神",
      // "律者",
      "原神",
      "提瓦特大陆",
      // "好耶,是大冒险!",
      // "为世界上所有的美好而战!",
      "星穹铁道",
      "绝区零",
      "未定事件簿",
      "米游社",
    ]
    static PPC_keywords = [
      "bilibili://game_center/detail?id=103496&",
      "bilibili://game_center/detail?id=94&",
      "bilibili://game_center/detail?id=108434&",
      "bilibili://game_center/detail?id=107800&",
      "bilibili://game_center/detail?id=12&",
    ]
    static PPC_keywords_includes = [
      "id1523037824",
      "id1517783697",
      "id1143402987",
      "id737651307",
    ]
    /**
     * @type {string[]}
     */
    static isAdsByAid = []
    /**
     * @type {string[]}
     */
    static notAdsByAid = []
    static get runningTime() {
      /**
       * @type {{
       * runningTime: number,
       * }}
       */
      let data = BLab8A.load()
      return data.runningTime === undefined ? 0 : data.runningTime
    }
    /** @param {number} v */
    static set runningTime(v) {
      /**
       * @type {{
       * runningTime: number,
       * }}
       */
      let data = BLab8A.load()
      data.runningTime = v
      BLab8A.save(data)
    }
    static async getAdsList(page = 1, size = 10, ad_title = "", trust_status = "") {
      console.log(`[${NAME}][${D()}]: `, `正在获取广告列表第${page}页`)
      let url = `${this.filter_ads_by_pageUrl}?page=${page}&size=${size}&ad_title=${ad_title}&trust_status=${trust_status}`
      /**
       * @type {{
       *  status: "success",
       *  current_time: number,
       *  result: {
       *   banned_num: number,
       *   data: {
       *    app_name: string,
       *    button_copy: string,
       *    creative_desc: string,
       *    creative_id: number,
       *    creative_title: string,
       *    grade: number,
       *    image_url: string,
       *    mtime: number,
       *    promotion_purpose_content: string,
       *    trust_status: number,
       *   }[],
       *   page: number,
       *   showed_num: number,
       *   total_count: number,
       *  }
       * }}
       */
      let data = JSON.parse(await HTTPSendPro({
        url,
        method: "GET",
        headers: {
          accept: "application/json, text/plain, */*",
          referer: "https://cm.bilibili.com/divide/",
        }
      }, true))
      if (data.status == "success") {
        return {
          page: data.result.page,
          total_count: data.result.total_count,
          data: data.result.data
        }
      }
      console.error(data)
      return null
    }
    static async getAdsAllList() {
      const _runningTime = Date.now()
      this.runningTime = _runningTime
      let page = 1
      let size = 10
      let ad_title = ""
      let trust_status = "1"
      let data = await this.getAdsList(page, size, ad_title, trust_status)
      let total_count = data.total_count
      let list = data.data
      while (page < total_count / size) {
        if (this.runningTime > _runningTime) {
          console.log(`[${NAME}][${D()}]: `, "其它相同脚本正在运行")
          return []
        }
        page++
        await RList.Push()
        data = await this.getAdsList(page, size, ad_title, trust_status)
        list = list.concat(data.data)
      }
      return list
    }
    /**
     *
     * @param {number} creative_ids
     * @param {number} trust_status
     */
    static async setAdsTrustStatus(creative_ids, trust_status) {
      let url = this.filter_adsUrl
      let data = {
        creative_ids,
        trust_status
      }
      /**
       * @type {{
       *  status: "success",
       *  current_time: number,
       * }}
       */
      let result = JSON.parse(await HTTPSendPro({
        url,
        method: "POST",
        headers: {
          "content-type": "application/json",
          referer: "https://cm.bilibili.com/divide/",
          origin: "https://cm.bilibili.com"
        },
        data: JSON.stringify(data)
      }, true))
      let isSuccess = result.status == "success"
      if (!isSuccess) console.error(result)
      return isSuccess
    }
    /**
     * 
     * @param {string} aid 
     */
    static async getTagsFromAid(aid) {
      await RList.Push()
      console.log(`[${NAME}][${D()}]: `, `正在获取视频 av${aid} 的 Tag`)
      let url = `https://api.bilibili.com/x/tag/archive/tags?aid=${aid}&_=${Math.round(new Date() / 1000)}`
      /**
       * @type {{
       *  code: number,
       *  message: string,
       *  ttl: number,
       *  data: {
       *   tag_id: number,
       *   tag_name: string,
       *  }[],
       * }}
       */
      let result = JSON.parse(await HTTPSendPro({
        url,
        method: "GET"
      }, true))
      if (result.code == 0) {
        return result.data.map((item) => item.tag_name)
      }
      console.error(result)
      return []
    }
    /**
     * 
     * @param {string} bvid 
     */
    static async getTagsFromBvid(bvid) {
      return await this.getTagsFromAid(BV2AV.dec(bvid).slice(2))
    }
    /**
     * 
     * @param {string} aid 
     */
    static async getDetailFromAid(aid) {
      await RList.Push()
      console.log(`[${NAME}][${D()}]: `, `正在获取视频 av${aid} 的详细信息`)
      let url = `https://api.bilibili.com/x/web-interface/view/detail?aid=${aid}`
      /**
       * @type {{
       * code: number,
       * message: string,
       * ttl: number,
       * data: {
       *  Tags: {
       *   tag_name: string,
       *   tag_id: number,
       *  }[],
       *  View: {
       *   aid: number,
       *   bvid: string,
       *   desc: string,
       *   title: string,
       *  }
       * }
       * }}
       */
      let result = JSON.parse(await HTTPSendPro({
        url,
        method: "GET"
      }, true))
      if (result.code == 0) {
        return {
          tags: result.data.Tags.map((item) => item.tag_name),
          title: result.data.View.title,
          desc: result.data.View.desc,
          aid: result.data.View.aid,
          bvid: result.data.View.bvid,
        }
      }
      return null
    }
    /**
     * 
     * @param {string} bvid 
     */
    static async getDetailFromBvid(bvid) {
      return await this.getDetailFromAid(BV2AV.dec(bvid).slice(2))
    }
  }

  // 判断浏览器 url 是否为 https://cm.bilibili.com 开头
  if (location.href.startsWith("https://cm.bilibili.com")) {
  }
  else {
    // let adsManager = new AdsManager()
    const startBlock = async () => {
      console.log(`[${NAME}][${D()}]: `, "获取广告列表")
      let list = await AdsManager.getAdsAllList()
      /**
       * @type {{
       *  app_name: string;
       *  button_copy: string;
       *  creative_desc: string;
       *  creative_id: number;
       *  creative_title: string;
       *  grade: number;
       *  image_url: string;
       *  mtime: number;
       *  promotion_purpose_content: string;
       *  trust_status: number;
       * }[]}
       */
      let ads = []
      for (let item of list) {
        let isAds = false
        for (const keyword of AdsManager.keywords) {
          if (item.creative_title.includes(keyword)) {
            isAds = true
            break
          }
        }
        if (!isAds) for (const keyword of AdsManager.PPC_keywords) {
          if (item.promotion_purpose_content.startsWith(keyword)) {
            isAds = true
            break
          }
        }
        if (!isAds) for (const keyword of AdsManager.PPC_keywords_includes) {
          if (item.promotion_purpose_content.includes(keyword)) {
            isAds = true
            break
          }
        }
        if (!isAds && (item.promotion_purpose_content.toLocaleLowerCase().startsWith('https://www.bilibili.com/video/')
          || item.promotion_purpose_content.toLocaleLowerCase().startsWith('http://www.bilibili.com/video/')
          || item.promotion_purpose_content.toLocaleLowerCase().startsWith('bilibili://video/')
        )) {
          /**
           * @type {string}
           */
          let vid
          // bilibili://video/615778550?source_id=__SOURCEID__&resource_id=__RESOURCEID__&creative_id=__CREATIVEID__&linked_creative_id=110656445&track_id=__TRACKID__&from_spmid=__FROMSPMID__&trackid=__FROMTRACKID__&request_id=__REQUESTID__&caid=__CAID__&biz_extra=%7B%22ad_play_page%22%3A1%7D
          if (item.promotion_purpose_content.toLocaleLowerCase().startsWith('bilibili://video/')) {
            vid = item.promotion_purpose_content.split('/')[3].split('?')[0]
            if (!vid.startsWith("BV")) {
              vid = `av${vid}`
            }
          }
          else {
            vid = item.promotion_purpose_content.split('/')[4].split('?')[0]
          }
          /**
           * @type {{
           *  tags: string[];
           *  title: string;
           *  desc: string;
           *  aid: number;
           *  bvid: string;
           *}}
           */
          let detail = null
          if (vid.startsWith("BV")) {
            const aid = BV2AV.dec(vid).slice(2)
            if (AdsManager.notAdsByAid.includes(aid)) continue
            // detail = await AdsManager.getDetailFromBvid(vid)
            if (AdsManager.isAdsByAid.includes(aid))
              isAds = true
            else
              detail = await AdsManager.getDetailFromAid(aid)
          }
          if (vid.toLowerCase().startsWith("av")) {
            if (AdsManager.notAdsByAid.includes(vid.slice(2))) continue
            if (AdsManager.isAdsByAid.includes(vid.slice(2)))
              isAds = true
            else
              detail = await AdsManager.getDetailFromAid(vid.slice(2))
          }
          if (detail) {
            for (const keyword of AdsManager.keywords) {
              if (isAds) break
              if (detail.title.includes(keyword)) {
                isAds = true
                AdsManager.isAdsByAid.push(detail.aid.toString())
                break
              }
              if (detail.desc.includes(keyword)) {
                isAds = true
                AdsManager.isAdsByAid.push(detail.aid.toString())
                break
              }
              for (const tag of detail.tags) {
                if (tag.includes(keyword)) {
                  isAds = true
                  AdsManager.isAdsByAid.push(detail.aid.toString())
                  break
                }
              }
            }
            if (!isAds) {
              AdsManager.notAdsByAid.push(detail.aid.toString())
            }
          }
        }
        if (isAds) ads.push(item)
      }
      if (ads.length == 0) return console.log(`[${NAME}][${D()}]: `, "没有 miHoYo 相关的广告")
      let blocked = 0
      for (let item of ads) {
        console.log(`[${NAME}][${D()}]: `, `正在屏蔽广告:${item.creative_title}`)
        await RList.Push()
        let r = await AdsManager.setAdsTrustStatus(item.creative_id, 0)
        console.log(`[${NAME}][${D()}]: `, r ? "屏蔽成功" : "屏蔽失败")
        if (r) blocked++
      }
      console.log(`[${NAME}][${D()}]: `, `已屏蔽 ${blocked} 条广告`)
    }
    GM_registerMenuCommand("Block miHoYo Ads", startBlock)
    try {
      await startBlock()
    } catch (error) {
      console.error(error)
    }
  }
})()