[DEBUG] 信息显式化

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

安装此脚本?
作者推荐脚本

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

安装此脚本
  1. // ==UserScript==
  2. // @name [DEBUG] 信息显式化
  3. // @version 2.9.2.20230314
  4. // @namespace laster2800
  5. // @author Laster2800
  6. // @description 用 alert() 提示符合匹配规则的日志或未捕获异常,帮助开发者在日常使用网页时发现潜藏问题
  7. // @homepageURL https://gf.qytechs.cn/zh-CN/scripts/429521
  8. // @supportURL https://gf.qytechs.cn/zh-CN/scripts/429521/feedback
  9. // @license LGPL-3.0
  10. // @include *
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_unregisterMenuCommand
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @run-at document-start
  16. // @compatible edge 版本不小于 93
  17. // @compatible chrome 版本不小于 93
  18. // @compatible firefox 版本不小于 92
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict'
  23.  
  24. const errorLog = console.error
  25. const gm = {
  26. id: 'gm429521',
  27. injectUpdate: 20230121,
  28. config: {},
  29. fn: {
  30. /**
  31. * 获取封装日志函数
  32. * @param {Object} console 控制台对象
  33. * @param {Function} log 日志函数
  34. * @param {string} type 类型
  35. * @param {string} [source] 源
  36. * @returns {Function} 封装日志函数
  37. */
  38. wrappedLog(console, log, type, source) {
  39. const { config, fn } = gm
  40. return (...args) => {
  41. Reflect.apply(log, console, args)
  42. try {
  43. if (config.enabled) {
  44. const m = [args, type]
  45. if (fn.match(m, config.include) && !fn.match(m, config.exclude)) {
  46. let msg = null
  47. if (args.length === 1) {
  48. msg = (args[0] && typeof args[0] === 'object') ? JSON.stringify(args[0], null, 2) : args[0]
  49. } else {
  50. msg = JSON.stringify(args, null, 2)
  51. }
  52. fn.explicit(msg, type, source)
  53. }
  54. }
  55. } catch (e) {
  56. innerError(e)
  57. }
  58. }
  59. },
  60. /**
  61. * 显式地显示信息
  62. * @param {*} msg 信息
  63. * @param {string} [type] 类型
  64. * @param {string} [source] 源
  65. */
  66. explicit(msg, type, source) {
  67. alert(`${GM_info.script.name}${type ? `\nTYPE: ${type}` : ''}${source ? `\nSOURCE: ${source}` : ''}\n\n${msg}`)
  68. },
  69. /**
  70. * @param {*} obj 匹配对象
  71. * @param {RegExp} regex 匹配正则表达式
  72. * @param {number} [depth=5] 匹配查找深度
  73. * @returns {boolean} 是否匹配成功
  74. */
  75. match(obj, regex, depth = 5) {
  76. return (obj && regex && depth > 0) ? inner(obj, depth, new WeakSet()) : false
  77.  
  78. function inner(obj, depth, objSet) {
  79. if (!obj) return false
  80. // eslint-disable-next-line guard-for-in
  81. innerLoop: for (const key in obj) {
  82. if (regex.test(key)) return true
  83. try {
  84. const value = obj[key]
  85. if (value && (typeof value === 'object' || typeof value === 'function')) {
  86. if (value === obj) continue
  87. if (value === value.window) continue // exclude Window
  88. for (const type of [Function, Node, StyleSheet]) {
  89. if (value instanceof type) continue innerLoop
  90. }
  91.  
  92. if (regex.test(value.toString())) return true
  93. if (depth > 1) {
  94. if (!objSet.has(value)) {
  95. objSet.add(value)
  96. if (inner(value, depth - 1, objSet)) return true
  97. }
  98. }
  99. } else if (regex.test(String(value))) return true
  100. } catch { /* value that cannot be accessed */ }
  101.  
  102. }
  103. return false
  104. }
  105. },
  106. /**
  107. * 检查更新
  108. * @param {string} source 源
  109. * @param {boolean} [injectAhead] 注入版版本超前
  110. */
  111. updateCheck(source, injectAhead) {
  112. if (injectAhead) {
  113. this.explicit(`「[DEBUG] 信息显式化」版本落后于「${source}」中的注入版。请在稍后弹出的新页面中获取最新版主脚本。\n若弹出页被浏览器阻止,请手动查看浏览器的「已阻止弹出窗口」,前往主脚本主页进行更新。`, 'UPDATE', source)
  114. window.open('https://gf.qytechs.cn/zh-CN/scripts/429521')
  115. } else {
  116. this.explicit(`需要更新「[DEBUG] 信息显式化(注入版)」。请在稍后弹出的新页面中获取最新版 URL 并更新「${source}」中的「@require」属性值。\n若弹出页被浏览器阻止,请手动查看浏览器的「已阻止弹出窗口」,前往注入版主页进行更新。`, 'UPDATE', source)
  117. window.open('https://gf.qytechs.cn/zh-CN/scripts/429525')
  118. }
  119. },
  120. },
  121. }
  122. unsafeWindow[Symbol.for('ExplicitMessage')] = gm
  123.  
  124. try {
  125. // 配置
  126. const df = { include: '.*', exclude: '^LOG$' }
  127. const gmInclude = GM_getValue('include') ?? df.include
  128. const gmExclude = GM_getValue('exclude') ?? df.exclude
  129. gm.config.enabled = GM_getValue('enabled') ?? true
  130. gm.config.include = gmInclude ? new RegExp(gmInclude) : null
  131. gm.config.exclude = gmExclude ? new RegExp(gmExclude) : null
  132.  
  133. // 日志
  134. const { console } = unsafeWindow
  135. for (const n of ['log', 'debug', 'info', 'warn', 'error']) {
  136. console[n] = gm.fn.wrappedLog(console, console[n], n.toUpperCase())
  137. }
  138.  
  139. // 未捕获异常
  140. unsafeWindow.addEventListener('error', /** @param {ErrorEvent} event */ event => { // 常规
  141. try {
  142. if (!gm.config.enabled) return
  143. const message = event.error?.stack ?? event.message
  144. const m = [message, event.filename, 'Uncaught Exception (Normal)']
  145. if (gm.fn.match(m, gm.config.include) && !gm.fn.match(m, gm.config.exclude)) {
  146. gm.fn.explicit(message, 'Uncaught Exception (Normal)')
  147. }
  148. } catch (e) {
  149. innerError(e)
  150. }
  151. })
  152. unsafeWindow.addEventListener('unhandledrejection', /** @param {PromiseRejectionEvent} event */ event => { // Promise
  153. try {
  154. if (!gm.config.enabled) return
  155. const message = event.reason.stack ?? event.reason
  156. const m = [message, 'Uncaught Exception (in Promise)']
  157. if (gm.fn.match(m, gm.config.include) && !gm.fn.match(m, gm.config.exclude)) {
  158. gm.fn.explicit(message, 'Uncaught Exception (in Promise)')
  159. }
  160. } catch (e) {
  161. innerError(e)
  162. }
  163. })
  164.  
  165. // 菜单
  166. if (self === top) { // frame 中不要执行
  167. const initScriptMenu = () => {
  168. const menuMap = {}
  169. menuMap.enabled = GM_registerMenuCommand(`当前${gm.config.enabled ? '开启' : '关闭'}`, () => {
  170. try {
  171. gm.config.enabled = confirm(`${GM_info.script.name}\n\n「确定」以开启功能,「取消」以关闭功能。`)
  172. GM_setValue('enabled', gm.config.enabled)
  173. for (const menuId of Object.values(menuMap)) {
  174. GM_unregisterMenuCommand(menuId)
  175. }
  176. initScriptMenu()
  177. } catch (e) {
  178. innerError(e)
  179. }
  180. })
  181. menuMap.filter = GM_registerMenuCommand('设置过滤器', () => {
  182. try {
  183. const sInclude = prompt(`${GM_info.script.name}\n\n设置匹配过滤器:`, gm.config.include?.source ?? df.include)
  184. if (typeof sInclude === 'string') {
  185. gm.config.include = sInclude ? new RegExp(sInclude) : null
  186. GM_setValue('include', sInclude)
  187. }
  188. const sExclude = prompt(`${GM_info.script.name}\n\n设置排除过滤器:`, gm.config.exclude?.source ?? df.exclude)
  189. if (typeof sExclude === 'string') {
  190. gm.config.exclude = sExclude ? new RegExp(sExclude) : null
  191. GM_setValue('exclude', sExclude)
  192. }
  193. } catch (e) {
  194. innerError(e)
  195. }
  196. })
  197. menuMap.help = GM_registerMenuCommand('使用说明', () => window.open('https://gitee.com/liangjiancang/userscript/blob/master/script/ExplicitMessage/README.md#使用说明'))
  198. menuMap.inject = GM_registerMenuCommand('获取注入版', () => window.open('https://gf.qytechs.cn/zh-CN/scripts/429525'))
  199. }
  200. initScriptMenu()
  201. }
  202. } catch (e) {
  203. innerError(e)
  204. }
  205.  
  206. /**
  207. * 内部错误
  208. * @param {*} e 错误
  209. */
  210. function innerError(e) {
  211. gm.fn.explicit(e, 'UNKNOWN', GM_info.script.name)
  212. errorLog('[UNKNOWN ERROR] %s: %o', GM_info.script.name, e)
  213. }
  214. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址