UserscriptAPI

My API for userscripts.

Verze ze dne 06. 09. 2021. Zobrazit nejnovější verzi.

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/409641/967908/UserscriptAPI.js

  1. /* exported UserscriptAPI */
  2. /**
  3. * UserscriptAPI
  4. *
  5. * 需要引入模块方可工作。所有模块均依赖于 `UserscriptAPI`,模块间的依赖关系如下:
  6. *
  7. * ```plaintext
  8. * +─────────+─────────+
  9. * 模块 | 依赖模块
  10. * +─────────+─────────+
  11. * dom |
  12. * logger |
  13. * message | dom
  14. * tool |
  15. * wait | tool
  16. * web |
  17. * +─────────+─────────+
  18. * ```
  19. * @version 2.0.0.20210906
  20. * @author Laster2800
  21. * @see {@link https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI UserscriptAPI}
  22. */
  23. class UserscriptAPI {
  24. /** @type {UserscriptAPIDom} */
  25. dom = this.#getModuleInstance('dom')
  26. /** @type {UserscriptAPILogger} */
  27. logger = this.#getModuleInstance('logger')
  28. /** @type {UserscriptAPIMessage} */
  29. message = this.#getModuleInstance('message')
  30. /** @type {UserscriptAPITool} */
  31. tool = this.#getModuleInstance('tool')
  32. /** @type {UserscriptAPIWait} */
  33. wait = this.#getModuleInstance('wait')
  34. /** @type {UserscriptAPIWeb} */
  35. web = this.#getModuleInstance('web')
  36.  
  37. /**
  38. * @param {Object} [options] 选项
  39. * @param {string} [options.id='default'] 标识符
  40. * @param {string} [options.label] 日志标签,为空时不设置标签
  41. * @param {Object} [options.wait] `wait` API 默认选项(默认值见构造器代码)
  42. * @param {Object} [options.wait.condition] `wait` 条件 API 默认选项
  43. * @param {Object} [options.wait.element] `wait` 元素 API 默认选项
  44. * @param {number} [options.fadeTime=400] UI 渐变时间
  45. */
  46. constructor(options) {
  47. this.options = {
  48. id: 'default',
  49. label: null,
  50. fadeTime: 400,
  51. ...options,
  52. wait: {
  53. condition: {
  54. callback: result => api.logger.info(result),
  55. interval: 100,
  56. timeout: 10000,
  57. onTimeout: function() {
  58. api.logger[this.stopOnTimeout ? 'error' : 'warn'](['TIMEOUT', 'executeAfterConditionPassed', options])
  59. },
  60. stopOnTimeout: true,
  61. stopCondition: null,
  62. onStop: () => api.logger.error(['STOP', 'executeAfterConditionPassed', options]),
  63. stopInterval: 50,
  64. stopTimeout: 0,
  65. onError: e => api.logger.error(['ERROR', 'executeAfterConditionPassed', options, e]),
  66. stopOnError: true,
  67. timePadding: 0,
  68. ...options?.wait?.condition,
  69. },
  70. element: {
  71. base: document,
  72. exclude: null,
  73. callback: el => api.logger.info(el),
  74. subtree: true,
  75. multiple: false,
  76. repeat: false,
  77. throttleWait: 100,
  78. timeout: 10000,
  79. onTimeout: function() {
  80. api.logger[this.stopOnTimeout ? 'error' : 'warn'](['TIMEOUT', 'executeAfterElementLoaded', options])
  81. },
  82. stopOnTimeout: false,
  83. stopCondition: null,
  84. onStop: () => api.logger.error(['STOP', 'executeAfterElementLoaded', options]),
  85. onError: e => api.logger.error(['ERROR', 'executeAfterElementLoaded', options, e]),
  86. stopOnError: true,
  87. timePadding: 0,
  88. ...options?.wait?.element,
  89. },
  90. },
  91. }
  92.  
  93. const win = typeof unsafeWindow == 'undefined' ? window : unsafeWindow
  94. /** @type {UserscriptAPI} */
  95. let api = win[`_userscriptAPI_${this.options.id}`]
  96. if (api) {
  97. api.options = this.options
  98. return api
  99. }
  100. api = win[`_userscriptAPI_${this.options.id}`] = this
  101.  
  102. if (!api.dom) {
  103. api.dom = {
  104. addStyle(css) {
  105. const style = document.createElement('style')
  106. style.setAttribute('type', 'text/css')
  107. style.className = `${api.options.id}-style`
  108. style.appendChild(document.createTextNode(css))
  109. const parent = document.head || document.documentElement
  110. if (parent) {
  111. parent.appendChild(style)
  112. }
  113. },
  114. }
  115. }
  116. if (!api.logger) {
  117. api.logger = {
  118. info: console.log,
  119. warn: console.warn,
  120. error: console.error,
  121. }
  122. }
  123.  
  124. api.dom.addStyle(`
  125. :root {
  126. --${api.options.id}-light-text-color: white;
  127. --${api.options.id}-shadow-color: #000000bf;
  128. }
  129.  
  130. .${api.options.id}-msgbox {
  131. z-index: 100000000;
  132. background-color: var(--${api.options.id}-shadow-color);
  133. font-size: 16px;
  134. max-width: 24em;
  135. min-width: 2em;
  136. color: var(--${api.options.id}-light-text-color);
  137. padding: 0.5em 1em;
  138. border-radius: 0.6em;
  139. opacity: 0;
  140. transition: opacity ${api.options.fadeTime}ms ease-in-out;
  141. user-select: none;
  142. }
  143.  
  144. .${api.options.id}-msgbox .gm-advanced-table td {
  145. vertical-align: middle;
  146. }
  147. .${api.options.id}-msgbox .gm-advanced-table td:first-child {
  148. padding-right: 0.6em;
  149. }
  150. `)
  151. }
  152.  
  153. /** 可访问模块 */
  154. static #modules = {}
  155.  
  156. /**
  157. * 注册模块
  158. * @param {string} name 模块名称
  159. * @param {Object} module 模块类
  160. */
  161. static registerModule(name, module) {
  162. this.#modules[name] = module
  163. }
  164.  
  165. /**
  166. * 获取模块实例
  167. * @param {string} name 模块名称
  168. * @returns {Object} 模块实例,无对应模块时返回 `null`
  169. */
  170. #getModuleInstance(name) {
  171. const module = UserscriptAPI.#modules[name]
  172. return module ? new module(this) : null
  173. }
  174. }