UserscriptAPI

My API for userscripts.

当前为 2020-08-22 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/409641/840383/UserscriptAPI.js

  1. /* exported API */
  2. /**
  3. * API
  4. * @author Laster2800
  5. */
  6. class API {
  7. /**
  8. * @param {Object} [options] 选项
  9. * @param {string} [options.id='_0'] 标识符
  10. * @param {string} [options.label] 日志标签,为空时不设置标签
  11. * @param {number} [options.fadeTime=400] UI 渐变时间(单位:ms)
  12. */
  13. constructor(options) {
  14. const defaultOptions = {
  15. id: '_0',
  16. label: null,
  17. fadeTime: 400,
  18. }
  19. this.options = {
  20. ...defaultOptions,
  21. ...options,
  22. }
  23.  
  24. const original = window[`_api_${this.options.id}`]
  25. if (original) {
  26. original.options = this.options
  27. return original
  28. }
  29. window[`_api_${this.options.id}`] = this
  30.  
  31. const api = this
  32. /** DOM 相关 */
  33. this.dom = {
  34. /**
  35. * 创建 locationchange 事件
  36. * @see {@link https://stackoverflow.com/a/52809105 How to detect if URL has changed after hash in JavaScript}
  37. */
  38. createLocationchangeEvent() {
  39. if (!unsafeWindow._createLocationchangeEvent) {
  40. history.pushState = (f => function pushState() {
  41. const ret = f.apply(this, arguments)
  42. window.dispatchEvent(new Event('pushstate'))
  43. window.dispatchEvent(new Event('locationchange'))
  44. return ret
  45. })(history.pushState)
  46. history.replaceState = (f => function replaceState() {
  47. const ret = f.apply(this, arguments)
  48. window.dispatchEvent(new Event('replacestate'))
  49. window.dispatchEvent(new Event('locationchange'))
  50. return ret
  51. })(history.replaceState)
  52. window.addEventListener('popstate', () => {
  53. window.dispatchEvent(new Event('locationchange'))
  54. })
  55. unsafeWindow._createLocationchangeEvent = true
  56. }
  57. },
  58.  
  59. /**
  60. * 将一个元素绝对居中
  61. *
  62. * 要求该元素此时可见且尺寸为确定值(一般要求为块状元素)。运行后会在 `target` 上附加 `_absoluteCenter` 方法,若该方法已存在,则无视 `config` 直接执行 `target._absoluteCenter()`。
  63. * @param {HTMLElement} target 目标元素
  64. * @param {Object} [config] 配置
  65. * @param {string} [config.position='fixed'] 定位方式
  66. * @param {string} [config.top='50%'] `style.top`
  67. * @param {string} [config.left='50%'] `style.left`
  68. */
  69. setAbsoluteCenter(target, config) {
  70. if (!target._absoluteCenter) {
  71. const defaultConfig = {
  72. position: 'fixed',
  73. top: '50%',
  74. left: '50%',
  75. }
  76. config = { ...defaultConfig, ...config }
  77. target._absoluteCenter = () => {
  78. const style = getComputedStyle(target)
  79. const top = (parseFloat(style.height) + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)) / 2
  80. const left = (parseFloat(style.width) + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)) / 2
  81. target.style.top = `calc(${config.top} - ${top}px)`
  82. target.style.left = `calc(${config.left} - ${left}px)`
  83. target.style.position = config.position
  84. }
  85.  
  86. // 实现一个简单的 debounce 来响应 resize 事件
  87. let tid
  88. window.addEventListener('resize', function() {
  89. if (target && target._absoluteCenter) {
  90. if (tid) {
  91. clearTimeout(tid)
  92. tid = null
  93. }
  94. tid = setTimeout(() => {
  95. target._absoluteCenter()
  96. }, 500)
  97. }
  98. })
  99. }
  100. target._absoluteCenter()
  101. },
  102.  
  103. /**
  104. * 处理 HTML 元素的渐显和渐隐
  105. * @param {boolean} inOut 渐显/渐隐
  106. * @param {HTMLElement} target HTML 元素
  107. * @param {() => void} [callback] 处理完成的回调函数
  108. */
  109. fade(inOut, target, callback) {
  110. // fadeId 等同于当前时间戳,其意义在于保证对于同一元素,后执行的操作必将覆盖前的操作
  111. const fadeId = new Date().getTime()
  112. target._fadeId = fadeId
  113. if (inOut) { // 渐显
  114. // 只有 display 可视情况下修改 opacity 才会触发 transition
  115. if (getComputedStyle(target).display == 'none') {
  116. target.style.display = 'unset'
  117. }
  118. setTimeout(() => {
  119. let success = false
  120. if (target._fadeId <= fadeId) {
  121. target.style.opacity = '1'
  122. success = true
  123. }
  124. callback && callback(success)
  125. }, 10) // 此处的 10ms 是为了保证修改 display 后在浏览器上真正生效,按 HTML5 定义,浏览器需保证 display 在修改 4ms 后保证生效,但实际上大部分浏览器貌似做不到,等个 10ms 再修改 opacity
  126. } else { // 渐隐
  127. target.style.opacity = '0'
  128. setTimeout(() => {
  129. let success = false
  130. if (target._fadeId <= fadeId) {
  131. target.style.display = 'none'
  132. success = true
  133. }
  134. callback && callback(success)
  135. }, api.options.fadeTime)
  136. }
  137. },
  138.  
  139. /**
  140. * 为 HTML 元素添加 `class`
  141. * @param {HTMLElement} el 目标元素
  142. * @param {string} className `class`
  143. */
  144. addClass(el, className) {
  145. if (el instanceof HTMLElement) {
  146. if (!el.className) {
  147. el.className = className
  148. } else {
  149. const clz = el.className.split(' ')
  150. if (clz.indexOf(className) < 0) {
  151. clz.push(className)
  152. el.className = clz.join(' ')
  153. }
  154. }
  155. }
  156. },
  157.  
  158. /**
  159. * 为 HTML 元素移除 `class`
  160. * @param {HTMLElement} el 目标元素
  161. * @param {string} [className] `class`,未指定时移除所有 `class`
  162. */
  163. removeClass(el, className) {
  164. if (el instanceof HTMLElement) {
  165. if (typeof className == 'string') {
  166. if (el.className == className) {
  167. el.className = ''
  168. } else {
  169. let clz = el.className.split(' ')
  170. clz = clz.reduce((prev, current) => {
  171. if (current != className) {
  172. prev.push(current)
  173. }
  174. return prev
  175. }, [])
  176. el.className = clz.join(' ')
  177. }
  178. } else {
  179. el.className = ''
  180. }
  181. }
  182. },
  183.  
  184. /**
  185. * 判断 HTML 元素类名中是否含有 `class`
  186. * @param {HTMLElement} el 目标元素
  187. * @param {string | string[]} className `class`,支持同时判断多个
  188. * @param {boolean} [and] 同时判断多个 `class` 时,默认采取 `OR` 逻辑,是否采用 `AND` 逻辑
  189. * @returns {boolean} 是否含有 `class`
  190. */
  191. containsClass(el, className, and = false) {
  192. if (el instanceof HTMLElement) {
  193. if (el.className == className) {
  194. return true
  195. } else {
  196. const clz = el.className.split(' ')
  197. if (className instanceof Array) {
  198. if (and) {
  199. for (const c of className) {
  200. if (clz.indexOf(c) < 0) {
  201. return false
  202. }
  203. }
  204. return true
  205. } else {
  206. for (const c of className) {
  207. if (clz.indexOf(c) >= 0) {
  208. return true
  209. }
  210. }
  211. return false
  212. }
  213. } else {
  214. return clz.indexOf(className) >= 0
  215. }
  216. }
  217. }
  218. },
  219. }
  220. /** 信息通知相关 */
  221. this.message = {
  222. /**
  223. * 创建信息
  224. * @param {string} msg 信息
  225. * @param {Object} [config] 设置
  226. * @param {boolean} [config.autoClose=true] 是否自动关闭信息,配合 `config.ms` 使用
  227. * @param {number} [config.ms=gm.const.messageTime] 显示时间(单位:ms,不含渐显/渐隐时间)
  228. * @param {boolean} [config.html=false] 是否将 `msg` 理解为 HTML
  229. * @param {string} [config.width] 信息框的宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  230. * @param {{top: string, left: string}} [config.position] 信息框的位置,不设置该项时,相当于设置为 `{ top: '70%', left: '50%' }`
  231. * @return {HTMLElement} 信息框元素
  232. */
  233. create(msg, config) {
  234. const defaultConfig = {
  235. autoClose: true,
  236. ms: 1200,
  237. html: false,
  238. width: null,
  239. position: {
  240. top: '70%',
  241. left: '50%',
  242. },
  243. }
  244. config = { ...defaultConfig, ...config }
  245.  
  246. const msgbox = document.body.appendChild(document.createElement('div'))
  247. msgbox.className = `${api.options.id}-msgbox`
  248. if (config.width) {
  249. msgbox.style.minWidth = 'auto' // 为什么一个是 auto 一个是 none?真是神奇的设计
  250. msgbox.style.maxWidth = 'none'
  251. msgbox.style.width = config.width
  252. }
  253.  
  254. msgbox.style.display = 'block'
  255. setTimeout(() => {
  256. api.dom.setAbsoluteCenter(msgbox, config.position)
  257. }, 10)
  258.  
  259. if (config.html) {
  260. msgbox.innerHTML = msg
  261. } else {
  262. msgbox.innerText = msg
  263. }
  264. api.dom.fade(true, msgbox, () => {
  265. if (config.autoClose) {
  266. setTimeout(() => {
  267. this.close(msgbox)
  268. }, config.ms)
  269. }
  270. })
  271. return msgbox
  272. },
  273.  
  274. /**
  275. * 关闭信息
  276. * @param {HTMLElement} msgbox 信息框元素
  277. */
  278. close(msgbox) {
  279. if (msgbox) {
  280. api.dom.fade(false, msgbox, () => {
  281. msgbox && msgbox.remove()
  282. })
  283. }
  284. },
  285.  
  286. /**
  287. * 创建高级信息
  288. * @param {HTMLElement} el 启动元素
  289. * @param {string} msg 信息
  290. * @param {string} flag 标志信息
  291. * @param {Object} [config] 设置
  292. * @param {string} [config.flagSize='1.8em'] 标志大小
  293. * @param {string} [config.width] 信息框的宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  294. * @param {{top: string, left: string}} [config.position] 信息框的位置,不设置该项时,相当于设置为 `{ top: gm.const.messageTop, left: gm.const.messageLeft }`
  295. * @param {() => boolean} [config.disabled] 是否处于禁用状态
  296. */
  297. advanced(el, msg, flag, config) {
  298. const defaultConfig = {
  299. flagSize: '1.8em',
  300. // 不能把数据列出,否则解构的时候会出问题
  301. }
  302. config = { ...defaultConfig, ...config }
  303.  
  304. const _self = this
  305. el.show = false
  306. el.onmouseenter = function() {
  307. if (config.disabled && config.disabled()) {
  308. return
  309. }
  310.  
  311. const htmlMsg = `
  312. <table class="gm-advanced-table"><tr>
  313. <td style="font-size:${config.flagSize};line-height:${config.flagSize}">${flag}</td>
  314. <td>${msg}</td>
  315. </tr></table>
  316. `
  317. this.msgbox = _self.create(htmlMsg, { ...config, html: true, autoClose: false })
  318.  
  319. // 可能信息框刚好生成覆盖在 el 上,需要做一个处理
  320. this.msgbox.onmouseenter = function() {
  321. this.mouseOver = true
  322. }
  323. // 从信息框出来也会关闭信息框,防止覆盖的情况下无法关闭
  324. this.msgbox.onmouseleave = function() {
  325. _self.close(this)
  326. }
  327. }
  328. el.onmouseleave = function() {
  329. setTimeout(() => {
  330. if (this.msgbox && !this.msgbox.mouseOver) {
  331. this.msgbox.onmouseleave = null
  332. _self.close(this.msgbox)
  333. }
  334. })
  335. }
  336. },
  337. }
  338. /** 用于等待元素加载/条件达成再执行操作 */
  339. this.wait = {
  340. /**
  341. * 在条件满足后执行操作
  342. *
  343. * 当条件满足后,如果不存在终止条件,那么直接执行 `callback(result)`。
  344. *
  345. * 当条件满足后,如果存在终止条件,且 `stopTimeout` 大于 0,则还会在接下来的 `stopTimeout` 时间内判断是否满足终止条件,称为终止条件的二次判断。
  346. * 如果在此期间,终止条件通过,则表示依然不满足条件,故执行 `onStop()` 而非 `callback(result)`。
  347. * 如果在此期间,终止条件一直失败,则顺利通过检测,执行 `callback(result)`。
  348. *
  349. * @param {Object} options 选项
  350. * @param {() => *} options.condition 条件,当 `condition()` 返回的 `result` 为真值时满足条件
  351. * @param {(result) => void} [options.callback] 当满足条件时执行 `callback(result)`
  352. * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
  353. * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
  354. * @param {() => void} [options.onTimeout] 检测超时时执行 `onTimeout()`
  355. * @param {() => *} [options.stopCondition] 终止条件,当 `stopCondition()` 返回的 `stopResult` 为真值时终止检测
  356. * @param {() => void} [options.onStop] 终止条件达成时执行 `onStop()`(包括终止条件的二次判断达成)
  357. * @param {number} [options.stopInterval=50] 终止条件二次判断期间的检测时间间隔(单位:ms)
  358. * @param {number} [options.stopTimeout=0] 终止条件二次判断期间的检测超时时间(单位:ms)
  359. * @param {number} [options.timePadding=0] 等待 `timePadding`ms 后才开始执行;包含在 `timeout` 中,因此不能大于 `timeout`
  360. */
  361. executeAfterConditionPassed(options) {
  362. const defaultOptions = {
  363. callback: result => api.logger.info(result),
  364. interval: 100,
  365. timeout: 5000,
  366. onTimeout: null,
  367. stopCondition: null,
  368. onStop: null,
  369. stopInterval: 50,
  370. stopTimeout: 0,
  371. timePadding: 0,
  372. }
  373. options = {
  374. ...defaultOptions,
  375. ...options,
  376. }
  377.  
  378. let tid
  379. let cnt = 0
  380. const maxCnt = (options.timeout - options.timePadding) / options.interval
  381. const task = async () => {
  382. const result = await options.condition()
  383. const stopResult = options.stopCondition && await options.stopCondition()
  384. if (stopResult) {
  385. clearInterval(tid)
  386. options.onStop && options.onStop.call(options)
  387. } else if (++cnt > maxCnt) {
  388. clearInterval(tid)
  389. options.onTimeout && options.onTimeout.call(options)
  390. } else if (result) {
  391. clearInterval(tid)
  392. if (options.stopCondition && options.stopTimeout > 0) {
  393. this.executeAfterConditionPassed({
  394. condition: options.stopCondition,
  395. callback: options.onStop,
  396. interval: options.stopInterval,
  397. timeout: options.stopTimeout,
  398. onTimeout: () => options.callback.call(options, result)
  399. })
  400. } else {
  401. options.callback.call(options, result)
  402. }
  403. }
  404. }
  405. setTimeout(() => {
  406. tid = setInterval(task, options.interval)
  407. task()
  408. }, options.timePadding)
  409. },
  410.  
  411. /**
  412. * 在元素加载完成后执行操作
  413. *
  414. * 当条件满足后,如果不存在终止条件,那么直接执行 `callback(element)`。
  415. *
  416. * 当条件满足后,如果存在终止条件,且 `stopTimeout` 大于 `0`,则还会在接下来的 `stopTimeout` 时间内判断是否满足终止条件,称为终止条件的二次判断。
  417. * 如果在此期间,终止条件通过,则表示依然不满足条件,故执行 `onStop()` 而非 `callback(element)`。
  418. * 如果在此期间,终止条件一直失败,则顺利通过检测,执行 `callback(element)`。
  419. *
  420. * @param {Object} options 选项
  421. * @param {string} options.selector 该选择器指定要等待加载的元素 `element`
  422. * @param {HTMLElement} [options.base=document] 基元素
  423. * @param {(element: HTMLElement) => void} [options.callback] 当 `element` 加载成功时执行 `callback(element)`
  424. * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
  425. * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
  426. * @param {() => void} [options.onTimeout] 检测超时时执行 `onTimeout()`
  427. * @param {string | (() => *)} [options.stopCondition] 终止条件。若为函数,当 `stopCondition()` 返回的 `stopResult` 为真值时终止检测;若为字符串,则作为元素选择器指定终止元素 `stopElement`,若该元素加载成功则终止检测
  428. * @param {() => void} [options.onStop] 终止条件达成时执行 `onStop()`(包括终止条件的二次判断达成)
  429. * @param {number} [options.stopInterval=50] 终止条件二次判断期间的检测时间间隔(单位:ms)
  430. * @param {number} [options.stopTimeout=0] 终止条件二次判断期间的检测超时时间(单位:ms)
  431. * @param {number} [options.timePadding=0] 等待 `timePadding`ms 后才开始执行;包含在 `timeout` 中,因此不能大于 `timeout`
  432. */
  433. executeAfterElementLoaded(options) {
  434. const defaultOptions = {
  435. base: document,
  436. callback: el => api.logger.info(el),
  437. interval: 100,
  438. timeout: 5000,
  439. onTimeout: null,
  440. stopCondition: null,
  441. onStop: null,
  442. stopInterval: 50,
  443. stopTimeout: 0,
  444. timePadding: 0,
  445. }
  446. options = {
  447. ...defaultOptions,
  448. ...options,
  449. }
  450. this.executeAfterConditionPassed({
  451. ...options,
  452. condition: () => options.base.querySelector(options.selector),
  453. stopCondition: () => {
  454. if (options.stopCondition) {
  455. if (options.stopCondition) {
  456. return options.stopCondition()
  457. } else if (typeof options.stopCondition == 'string') {
  458. return document.querySelector(options.stopCondition)
  459. }
  460. }
  461. },
  462. })
  463. },
  464.  
  465. /**
  466. * 等待条件满足
  467. *
  468. * 执行细节类似于 {@link executeAfterConditionPassed}。在原来执行 `callback(result)` 的地方执行 `resolve(result)`,被终止或超时执行 `reject()`。
  469. * @async
  470. * @see executeAfterConditionPassed
  471. * @param {Object} options 选项
  472. * @param {() => *} options.condition 条件,当 `condition()` 返回的 `result` 为真值时满足条件
  473. * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
  474. * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
  475. * @param {() => *} [options.stopCondition] 终止条件,当 `stopCondition()` 返回的 `stopResult` 为真值时终止检测
  476. * @param {number} [options.stopInterval=50] 终止条件二次判断期间的检测时间间隔(单位:ms)
  477. * @param {number} [options.stopTimeout=0] 终止条件二次判断期间的检测超时时间(单位:ms)
  478. * @param {number} [options.timePadding=0] 等待 `timePadding`ms 后才开始执行;包含在 `timeout` 中,因此不能大于 `timeout`
  479. * @returns {Promise} `result`
  480. * @throws 当等待超时或者被终止时抛出
  481. */
  482. async waitForConditionPassed(options) {
  483. return new Promise((resolve, reject) => {
  484. this.executeAfterConditionPassed({
  485. ...options,
  486. callback: result => resolve(result),
  487. onTimeout: function() {
  488. reject(['TIMEOUT', 'waitForConditionPassed', this])
  489. },
  490. onStop: function() {
  491. reject(['STOP', 'waitForConditionPassed', this])
  492. },
  493. })
  494. })
  495. },
  496.  
  497. /**
  498. * 等待元素加载
  499. *
  500. * 执行细节类似于 {@link executeAfterElementLoaded}。在原来执行 `callback(element)` 的地方执行 `resolve(element)`,被终止或超时执行 `reject()`。
  501. * @async
  502. * @see executeAfterElementLoaded
  503. * @param {string} selector 该选择器指定要等待加载的元素 `element`
  504. * @param {HTMLElement} [base=document] 基元素
  505. * @returns {Promise<HTMLElement>} `element`
  506. * @throws 当等待超时或者被终止时抛出
  507. */
  508. /**
  509. * 等待元素加载
  510. *
  511. * 执行细节类似于 {@link executeAfterElementLoaded}。在原来执行 `callback(element)` 的地方执行 `resolve(element)`,被终止或超时执行 `reject()`。
  512. * @async
  513. * @see executeAfterElementLoaded
  514. * @param {Object} options 选项
  515. * @param {string} options.selector 该选择器指定要等待加载的元素 `element`
  516. * @param {HTMLElement} [options.base=document] 基元素
  517. * @param {number} [options.interval=100] 检测时间间隔(单位:ms)
  518. * @param {number} [options.timeout=5000] 检测超时时间,检测时间超过该值时终止检测(单位:ms)
  519. * @param {string | (() => *)} [options.stopCondition] 终止条件。若为函数,当 `stopCondition()` 返回的 `stopResult` 为真值时终止检测;若为字符串,则作为元素选择器指定终止元素 `stopElement`,若该元素加载成功则终止检测
  520. * @param {number} [options.stopInterval=50] 终止条件二次判断期间的检测时间间隔(单位:ms)
  521. * @param {number} [options.stopTimeout=0] 终止条件二次判断期间的检测超时时间(单位:ms)
  522. * @param {number} [options.timePadding=0] 等待 `timePadding`ms 后才开始执行;包含在 `timeout` 中,因此不能大于 `timeout`
  523. * @returns {Promise<HTMLElement>} `element`
  524. * @throws 当等待超时或者被终止时抛出
  525. */
  526. async waitForElementLoaded() {
  527. let options
  528. if (arguments.length > 0) {
  529. if (typeof arguments[0] == 'string') {
  530. options = { selector: arguments[0] }
  531. if (arguments[1]) {
  532. options.base = arguments[1]
  533. }
  534. } else {
  535. options = arguments[0]
  536. }
  537. }
  538. return new Promise((resolve, reject) => {
  539. this.executeAfterElementLoaded({
  540. ...options,
  541. callback: element => resolve(element),
  542. onTimeout: function() {
  543. reject(['TIMEOUT', 'waitForElementLoaded', this])
  544. },
  545. onStop: function() {
  546. reject(['STOP', 'waitForElementLoaded', this])
  547. },
  548. })
  549. })
  550. },
  551. }
  552. /** 网络相关 */
  553. this.web = {
  554. /** @typedef {Object} GM_xmlhttpRequest_details */
  555. /** @typedef {Object} GM_xmlhttpRequest_response */
  556. /**
  557. * 发起网络请求
  558. * @async
  559. * @param {GM_xmlhttpRequest_details} details 定义及细节同 {@link GM_xmlhttpRequest} 的 `details`
  560. * @returns {Promise<GM_xmlhttpRequest_response>} 响应对象
  561. * @throws 当请求发生错误或者超时时抛出
  562. * @see {@link https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest GM_xmlhttpRequest}
  563. */
  564. async request(details) {
  565. if (details) {
  566. return new Promise((resolve, reject) => {
  567. const throwHandler = function(msg) {
  568. api.logger.error('NETWORK REQUEST ERROR')
  569. reject(msg)
  570. }
  571. details.onerror = details.onerror || (() => throwHandler(['ERROR', 'request', details]))
  572. details.ontimeout = details.ontimeout || (() => throwHandler(['TIMEOUT', 'request', details]))
  573. details.onload = details.onload || (response => resolve(response))
  574. GM_xmlhttpRequest(details)
  575. })
  576. }
  577. },
  578.  
  579. /**
  580. * 判断当前 URL 是否匹配
  581. * @param {RegExp} reg 用于判断是否匹配的正则表达式
  582. * @returns {boolean} 是否匹配
  583. */
  584. urlMatch(reg) {
  585. return reg.test(location.href)
  586. },
  587. }
  588. /**
  589. * 日志
  590. */
  591. this.logger = {
  592. /**
  593. * 打印格式化日志
  594. * @param {*} message 日志信息
  595. * @param {string} label 日志标签
  596. * @param {boolean} [error] 是否错误信息
  597. */
  598. log(message, label, error) {
  599. const css = `
  600. background-color: black;
  601. color: white;
  602. border-radius: 2px;
  603. padding: 2px;
  604. margin-right: 2px;
  605. `
  606. const output = console[error ? 'error' : 'log']
  607. const type = typeof message == 'string' ? '%s' : '%o'
  608. output(`%c${label}%c${type}`, css, '', message)
  609. },
  610.  
  611. /**
  612. * 打印日志
  613. * @param {*} message 日志信息
  614. */
  615. info(message) {
  616. if (message !== undefined) {
  617. if (api.options.label) {
  618. this.log(message, api.options.label)
  619. } else {
  620. console.log(message)
  621. }
  622. }
  623. },
  624.  
  625. /**
  626. * 打印错误日志
  627. * @param {*} message 错误日志信息
  628. */
  629. error(message) {
  630. if (message !== undefined) {
  631. if (api.options.label) {
  632. this.log(message, api.options.label, true)
  633. } else {
  634. console.error(message)
  635. }
  636. }
  637. },
  638. }
  639.  
  640. GM_addStyle(`
  641. :root {
  642. --light-text-color: white;
  643. --shadow-color: #000000bf;
  644. }
  645.  
  646. .${api.options.id}-msgbox {
  647. z-index: 65535;
  648. background-color: var(--shadow-color);
  649. font-size: 16px;
  650. max-width: 24em;
  651. min-width: 2em;
  652. color: var(--light-text-color);
  653. padding: 0.5em 1em;
  654. border-radius: 0.6em;
  655. opacity: 0;
  656. transition: opacity ${api.options.fadeTime}ms ease-in-out;
  657. user-select: none;
  658. }
  659.  
  660. .${api.options.id}-msgbox .gm-advanced-table td {
  661. vertical-align: middle;
  662. }
  663. .${api.options.id}-msgbox .gm-advanced-table td:first-child {
  664. padding-right: 0.6em;
  665. }
  666. `)
  667. }
  668. }