[DEBUG] 信息显式化

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

Installer ce script?
Script suggéré par l'auteur

Vous aimerez aussi B站稍后再看功能增强.

Installer ce script
// ==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)
  }
})()