[DEBUG] 信息显式化

用 alert() 提示符合匹配规则的日志或未捕获异常,帮助开发者在日常使用网页时发现潜藏问题

安裝腳本?
作者推薦腳本

您可能也會喜歡 B站稍后再看功能增强

安裝腳本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            [DEBUG] 信息显式化
// @version         2.9.2.20230314
// @namespace       laster2800
// @author          Laster2800
// @description     用 alert() 提示符合匹配规则的日志或未捕获异常,帮助开发者在日常使用网页时发现潜藏问题
// @homepageURL     https://greasyfork.org/zh-CN/scripts/429521
// @supportURL      https://greasyfork.org/zh-CN/scripts/429521/feedback
// @license         LGPL-3.0
// @include         *
// @grant           GM_registerMenuCommand
// @grant           GM_unregisterMenuCommand
// @grant           GM_setValue
// @grant           GM_getValue
// @run-at          document-start
// @compatible      edge 版本不小于 93
// @compatible      chrome 版本不小于 93
// @compatible      firefox 版本不小于 92
// ==/UserScript==

(function() {
  'use strict'

  const errorLog = console.error
  const gm = {
    id: 'gm429521',
    injectUpdate: 20230121,
    config: {},
    fn: {
      /**
       * 获取封装日志函数
       * @param {Object} console 控制台对象
       * @param {Function} log 日志函数
       * @param {string} type 类型
       * @param {string} [source] 源
       * @returns {Function} 封装日志函数
       */
      wrappedLog(console, log, type, source) {
        const { config, fn } = gm
        return (...args) => {
          Reflect.apply(log, console, args)
          try {
            if (config.enabled) {
              const m = [args, type]
              if (fn.match(m, config.include) && !fn.match(m, config.exclude)) {
                let msg = null
                if (args.length === 1) {
                  msg = (args[0] && typeof args[0] === 'object') ? JSON.stringify(args[0], null, 2) : args[0]
                } else {
                  msg = JSON.stringify(args, null, 2)
                }
                fn.explicit(msg, type, source)
              }
            }
          } catch (e) {
            innerError(e)
          }
        }
      },
      /**
       * 显式地显示信息
       * @param {*} msg 信息
       * @param {string} [type] 类型
       * @param {string} [source] 源
       */
      explicit(msg, type, source) {
        alert(`${GM_info.script.name}${type ? `\nTYPE: ${type}` : ''}${source ? `\nSOURCE: ${source}` : ''}\n\n${msg}`)
      },
      /**
       * @param {*} obj 匹配对象
       * @param {RegExp} regex 匹配正则表达式
       * @param {number} [depth=5] 匹配查找深度
       * @returns {boolean} 是否匹配成功
       */
      match(obj, regex, depth = 5) {
        return (obj && regex && depth > 0) ? inner(obj, depth, new WeakSet()) : false

        function inner(obj, depth, objSet) {
          if (!obj) return false
          // eslint-disable-next-line guard-for-in
          innerLoop: for (const key in obj) {
            if (regex.test(key)) return true
            try {
              const value = obj[key]
              if (value && (typeof value === 'object' || typeof value === 'function')) {
                if (value === obj) continue
                if (value === value.window) continue // exclude Window
                for (const type of [Function, Node, StyleSheet]) {
                  if (value instanceof type) continue innerLoop
                }

                if (regex.test(value.toString())) return true
                if (depth > 1) {
                  if (!objSet.has(value)) {
                    objSet.add(value)
                    if (inner(value, depth - 1, objSet)) return true
                  }
                }
              } else if (regex.test(String(value))) return true
            } catch { /* value that cannot be accessed */ }

          }
          return false
        }
      },
      /**
       * 检查更新
       * @param {string} source 源
       * @param {boolean} [injectAhead] 注入版版本超前
       */
      updateCheck(source, injectAhead) {
        if (injectAhead) {
          this.explicit(`「[DEBUG] 信息显式化」版本落后于「${source}」中的注入版。请在稍后弹出的新页面中获取最新版主脚本。\n若弹出页被浏览器阻止,请手动查看浏览器的「已阻止弹出窗口」,前往主脚本主页进行更新。`, 'UPDATE', source)
          window.open('https://greasyfork.org/zh-CN/scripts/429521')
        } else {
          this.explicit(`需要更新「[DEBUG] 信息显式化(注入版)」。请在稍后弹出的新页面中获取最新版 URL 并更新「${source}」中的「@require」属性值。\n若弹出页被浏览器阻止,请手动查看浏览器的「已阻止弹出窗口」,前往注入版主页进行更新。`, 'UPDATE', source)
          window.open('https://greasyfork.org/zh-CN/scripts/429525')
        }
      },
    },
  }
  unsafeWindow[Symbol.for('ExplicitMessage')] = gm

  try {
    // 配置
    const df = { include: '.*', exclude: '^LOG$' }
    const gmInclude = GM_getValue('include') ?? df.include
    const gmExclude = GM_getValue('exclude') ?? df.exclude
    gm.config.enabled = GM_getValue('enabled') ?? true
    gm.config.include = gmInclude ? new RegExp(gmInclude) : null
    gm.config.exclude = gmExclude ? new RegExp(gmExclude) : null

    // 日志
    const { console } = unsafeWindow
    for (const n of ['log', 'debug', 'info', 'warn', 'error']) {
      console[n] = gm.fn.wrappedLog(console, console[n], n.toUpperCase())
    }

    // 未捕获异常
    unsafeWindow.addEventListener('error', /** @param {ErrorEvent} event */ event => { // 常规
      try {
        if (!gm.config.enabled) return
        const message = event.error?.stack ?? event.message
        const m = [message, event.filename, 'Uncaught Exception (Normal)']
        if (gm.fn.match(m, gm.config.include) && !gm.fn.match(m, gm.config.exclude)) {
          gm.fn.explicit(message, 'Uncaught Exception (Normal)')
        }
      } catch (e) {
        innerError(e)
      }
    })
    unsafeWindow.addEventListener('unhandledrejection', /** @param {PromiseRejectionEvent} event */ event => { // Promise
      try {
        if (!gm.config.enabled) return
        const message = event.reason.stack ?? event.reason
        const m = [message, 'Uncaught Exception (in Promise)']
        if (gm.fn.match(m, gm.config.include) && !gm.fn.match(m, gm.config.exclude)) {
          gm.fn.explicit(message, 'Uncaught Exception (in Promise)')
        }
      } catch (e) {
        innerError(e)
      }
    })

    // 菜单
    if (self === top) { // frame 中不要执行
      const initScriptMenu = () => {
        const menuMap = {}
        menuMap.enabled = GM_registerMenuCommand(`当前${gm.config.enabled ? '开启' : '关闭'}`, () => {
          try {
            gm.config.enabled = confirm(`${GM_info.script.name}\n\n「确定」以开启功能,「取消」以关闭功能。`)
            GM_setValue('enabled', gm.config.enabled)
            for (const menuId of Object.values(menuMap)) {
              GM_unregisterMenuCommand(menuId)
            }
            initScriptMenu()
          } catch (e) {
            innerError(e)
          }
        })
        menuMap.filter = GM_registerMenuCommand('设置过滤器', () => {
          try {
            const sInclude = prompt(`${GM_info.script.name}\n\n设置匹配过滤器:`, gm.config.include?.source ?? df.include)
            if (typeof sInclude === 'string') {
              gm.config.include = sInclude ? new RegExp(sInclude) : null
              GM_setValue('include', sInclude)
            }
            const sExclude = prompt(`${GM_info.script.name}\n\n设置排除过滤器:`, gm.config.exclude?.source ?? df.exclude)
            if (typeof sExclude === 'string') {
              gm.config.exclude = sExclude ? new RegExp(sExclude) : null
              GM_setValue('exclude', sExclude)
            }
          } catch (e) {
            innerError(e)
          }
        })
        menuMap.help = GM_registerMenuCommand('使用说明', () => window.open('https://gitee.com/liangjiancang/userscript/blob/master/script/ExplicitMessage/README.md#使用说明'))
        menuMap.inject = GM_registerMenuCommand('获取注入版', () => window.open('https://greasyfork.org/zh-CN/scripts/429525'))
      }
      initScriptMenu()
    }
  } catch (e) {
    innerError(e)
  }

  /**
   * 内部错误
   * @param {*} e 错误
   */
  function innerError(e) {
    gm.fn.explicit(e, 'UNKNOWN', GM_info.script.name)
    errorLog('[UNKNOWN ERROR] %s: %o', GM_info.script.name, e)
  }
})()