UserscriptAPIWeb

https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI

Tính đến 06-09-2021. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/432003/967900/UserscriptAPIWeb.js

  1. /**
  2. * UserscriptAPIWeb
  3. *
  4. * 依赖于 `UserscriptAPI`。
  5. *
  6. * 需要通过 `@grant` 引入 `GM_xmlhttpRequest` 或 `GM_download`。
  7. * @version 1.0.0.20210906
  8. * @author Laster2800
  9. * @see {@link https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI UserscriptAPI}
  10. */
  11. class UserscriptAPIWeb {
  12. /**
  13. * @param {UserscriptAPI} api `UserscriptAPI`
  14. */
  15. constructor(api) {
  16. this.api = api
  17. }
  18.  
  19. /**
  20. * @typedef {XMLHttpRequest} GM_XHR GM 定义的类 `XMLHttpRequest` 对象
  21. */
  22. /**
  23. * 发起网络请求,获取 `GM_XHR`
  24. * @param {Object} details 定义及细节类似于 `GM_xmlhttpRequest` `details`
  25. * @param {'GET' | 'HEAD' | 'POST'} [details.method='GET'] `METHOD`
  26. * @param {string} [details.url] `URL`
  27. * @param {number} [details.timeout] 超时时间
  28. * @param {(xhr: GM_XHR) => void} [details.ontimeout] 超时回调
  29. * @param {(xhr: GM_XHR) => void} [details.onerror] 错误回调
  30. * @param {(xhr: GM_XHR) => void} [details.onload] 加载回调
  31. * @param {string | URLSearchParams | FormData} [details.data] `DATA`
  32. * @param {Object} [options] 选项
  33. * @param {(xhr: GM_XHR) => boolean} [options.check] 检查 `GM_XHR` 是否符合条件
  34. * @param {boolean} [options.throwOnFailed = true] 失败时是否抛出异常,否则打印错误信息
  35. * @returns {Promise<GM_XHR>} `GM_XHR`
  36. * @throws 等待超时、达成终止条件、等待错误时抛出
  37. * @see {@link https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest GM_xmlhttpRequest}
  38. */
  39. async requestXHR(details, options) {
  40. if (details) {
  41. const api = this.api
  42. const { check, throwOnFailed = true } = options ?? {}
  43. return new Promise((resolve, reject) => {
  44. if (details.data && details.data instanceof URLSearchParams) {
  45. details.data = details.data.toString()
  46. details.headers = {
  47. 'content-type': 'application/x-www-form-urlencoded',
  48. ...details.headers,
  49. }
  50. if (GM_info.scriptHandler == 'Violentmonkey' && !details.headers.origin) {
  51. details.headers.origin = ''
  52. }
  53. }
  54. details.ontimeout ??= xhr => fail(['TIMEOUT', 'request', details, xhr])
  55. details.onerror ??= xhr => fail(['ERROR', 'request', details, xhr])
  56. details.onload ??= xhr => {
  57. if (check && !check(xhr)) {
  58. fail(['CHECK-FAIL', 'request', details, check, xhr])
  59. if (throwOnFailed) return
  60. }
  61. resolve(xhr)
  62. }
  63. GM_xmlhttpRequest(details)
  64.  
  65. function fail(msg) {
  66. throwOnFailed ? reject(msg) : api.logger.error(msg)
  67. }
  68. })
  69. }
  70. }
  71.  
  72. /**
  73. * 发起网络请求,获取解析结果
  74. * @param {Object} details 定义及细节类似于 `GM_xmlhttpRequest` `details`
  75. * @param {'GET' | 'HEAD' | 'POST'} [details.method='GET'] `METHOD`
  76. * @param {string} [details.url] `URL`
  77. * @param {number} [details.timeout] 超时时间
  78. * @param {(xhr: GM_XHR) => void} [details.ontimeout] 超时回调
  79. * @param {(xhr: GM_XHR) => void} [details.onerror] 错误回调
  80. * @param {(xhr: GM_XHR) => void} [details.onload] 加载回调
  81. * @param {string | URLSearchParams | FormData} [details.data] `DATA`
  82. * @param {Object} [options] 选项
  83. * @param {'json' | 'check' | 'silentCheck'} [options.parser='json'] ```plaintext
  84. * json: 返回 JSON.parse(resp)
  85. * check: 返回 check(resp, xhr),检查失败时打印信息
  86. * silentCheck: 返回 check(resp, xhr),检查失败时不打印信息
  87. * ```
  88. * @param {(resp: Object, xhr: GM_XHR) => boolean} [options.check] 检查 `GM_XHR` 是否符合条件
  89. * @param {boolean} [options.throwOnFailed=true] 失败时是否抛出异常,否则打印错误信息
  90. * @returns {Promise<Object>} 解析结果
  91. * @see {@link https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest GM_xmlhttpRequest}
  92. */
  93. async request(details, options) {
  94. const api = this.api
  95. const { parser = 'json', check, throwOnFailed = true } = options ?? {}
  96. try {
  97. const xhr = await this.requestXHR(details)
  98. let resp = null
  99. try {
  100. resp = JSON.parse(xhr.response)
  101. } catch (e) {
  102. fail(['PARSE', 'request', details, xhr])
  103. return null
  104. }
  105. const checkResult = !check || check(resp, xhr)
  106. if (parser == 'silentCheck') {
  107. return checkResult
  108. } else if (parser == 'check') {
  109. if (!checkResult) {
  110. api.logger.error(['CHECK-FAIL', 'request', details, check, resp, xhr])
  111. }
  112. return checkResult
  113. } else {
  114. if (!checkResult) {
  115. fail(['CHECK-FAIL', 'request', details, check, resp, xhr])
  116. }
  117. return resp
  118. }
  119. } catch (e) {
  120. fail(e)
  121. }
  122.  
  123. function fail(msg) {
  124. if (throwOnFailed) {
  125. throw msg
  126. } else {
  127. api.logger.error(msg)
  128. }
  129. }
  130. }
  131.  
  132. /**
  133. * 下载资源
  134. * @param {Object} details 定义及细节同 `GM_download` `details`
  135. * @returns {() => void} 用于终止下载的方法
  136. * @see {@link https://www.tampermonkey.net/documentation.php#GM_download GM_download}
  137. */
  138. download(details) {
  139. if (details) {
  140. const api = this.api
  141. try {
  142. const cfg = { ...details }
  143. let name = cfg.name
  144. if (name.indexOf('.') >= 0) {
  145. let parts = cfg.url.split('/')
  146. const last = parts[parts.length - 1].split('?')[0]
  147. if (last.indexOf('.') >= 0) {
  148. parts = last.split('.')
  149. name = `${name}.${parts[parts.length - 1]}`
  150. } else {
  151. name = name.replaceAll('.', '_')
  152. }
  153. cfg.name = name
  154. }
  155. if (!cfg.onerror) {
  156. cfg.onerror = function(error, details) {
  157. api.logger.error('DOWNLOAD ERROR')
  158. api.logger.error([error, details])
  159. }
  160. }
  161. if (!cfg.ontimeout) {
  162. cfg.ontimeout = function() {
  163. api.logger.error('DOWNLOAD TIMEOUT')
  164. }
  165. }
  166. GM_download(cfg)
  167. } catch (e) {
  168. api.logger.error('DOWNLOAD ERROR')
  169. api.logger.error(e)
  170. }
  171. }
  172. return () => {}
  173. }
  174.  
  175. /**
  176. * 判断给定 URL 是否匹配
  177. * @param {RegExp | RegExp[]} reg 用于判断是否匹配的正则表达式,或正则表达式数组
  178. * @param {'SINGLE' | 'AND' | 'OR'} [mode='SINGLE'] 匹配模式
  179. * @returns {boolean} 是否匹配
  180. */
  181. urlMatch(reg, mode = 'SINGLE') {
  182. let result = false
  183. const href = location.href
  184. if (mode == 'SINGLE') {
  185. if (reg instanceof Array) {
  186. if (reg.length > 0) {
  187. reg = reg[0]
  188. } else {
  189. reg = null
  190. }
  191. }
  192. if (reg) {
  193. result = reg.test(href)
  194. }
  195. } else {
  196. if (!(reg instanceof Array)) {
  197. reg = [reg]
  198. }
  199. if (reg.length > 0) {
  200. if (mode == 'AND') {
  201. result = true
  202. for (const r of reg) {
  203. if (!r.test(href)) {
  204. result = false
  205. break
  206. }
  207. }
  208. } else if (mode == 'OR') {
  209. for (const r of reg) {
  210. if (r.test(href)) {
  211. result = true
  212. break
  213. }
  214. }
  215. }
  216. }
  217. }
  218. return result
  219. }
  220. }
  221.  
  222. /* global UserscriptAPI */
  223. { UserscriptAPI.registerModule('web', UserscriptAPIWeb) }