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

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

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         创作中心广告管理自动屏蔽米哈游相关的广告
// @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)
    }
  }
})()