UserscriptAPIWeb

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

ของเมื่อวันที่ 06-09-2021 ดู เวอร์ชันล่าสุด

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @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) }