Requests

Useful library for sending requests.

当前为 2020-06-25 提交的版本,查看 最新版本

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

  1. // ==UserScript==
  2. // @name Requests
  3. // @namespace https://rafaelgssa.gitlab.io/monkey-scripts
  4. // @version 2.0.0
  5. // @author rafaelgssa
  6. // @description Useful library for sending requests.
  7. // @match *://*/*
  8. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  9. // @require https://greasyfork.org/scripts/405802-monkey-dom/code/Monkey%20DOM.js
  10. // @require https://greasyfork.org/scripts/405813-monkey-utils/code/Monkey%20Utils.js
  11. // @grant GM.xmlHttpRequest
  12. // @grant GM_xmlhttpRequest
  13. // ==/UserScript==
  14.  
  15. /* global DOM, Utils */
  16.  
  17. /**
  18. * @typedef {'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'} RequestMethod
  19. */
  20.  
  21. /**
  22. * @template T
  23. * @typedef {Object} ExtendedResponse
  24. * @property {string} url
  25. * @property {string} text
  26. * @property {T} [json]
  27. * @property {Document} [dom]
  28. */
  29.  
  30. // eslint-disable-next-line
  31. const Requests = (() => {
  32. /**
  33. * @template T
  34. * @param {RequestMethod} method
  35. * @param {string} url
  36. * @param {RequestInit | GM.Request} [options]
  37. * @returns {Promise<ExtendedResponse<T>>}
  38. */
  39. const _sendWithMethod = (method, url, options = {}) => {
  40. return send(url, { ...options, method });
  41. };
  42.  
  43. /**
  44. * @template T
  45. * @param {string} url
  46. * @param {RequestInit | GM.Request} [options]
  47. * @returns {Promise<ExtendedResponse<T>>}
  48. */
  49. const send = (url, options = {}) => {
  50. if (_isInternal(url, options)) {
  51. return _sendInternal(url, options);
  52. }
  53. return _sendExternal(url, options);
  54. };
  55.  
  56. /**
  57. * Checks if the request is internal (uses window.fetch) or external (uses GM.xmlHttpRequest to bypass CORS).
  58. * @param {string} url
  59. * @param {RequestInit | GM.Request} options
  60. * @returns {options is RequestInit}
  61. */
  62. // eslint-disable-next-line
  63. const _isInternal = (url, options) => {
  64. return url.includes(window.location.host);
  65. };
  66.  
  67. /**
  68. * @template T
  69. * @param {string} url
  70. * @param {RequestInit} options
  71. * @returns {Promise<ExtendedResponse<T>>}
  72. */
  73. const _sendInternal = async (url, options) => {
  74. const [internalFetch, internalOptions] = _getInternalObjects(options);
  75. const response = await internalFetch(url, internalOptions);
  76. /** @type {ExtendedResponse<T>} */
  77. const extendedResponse = {
  78. url: response.url,
  79. text: await response.text(),
  80. };
  81. return _processResponse(extendedResponse);
  82. };
  83.  
  84. /**
  85. * Allows the request to be made in the current Firefox container.
  86. * @param {RequestInit} options
  87. * @returns {[Function, RequestInit]}
  88. */
  89. const _getInternalObjects = (options) => {
  90. if ('wrappedJSObject' in window) {
  91. window.wrappedJSObject.options = cloneInto(options, window);
  92. return [window.wrappedJSObject.fetch, window.wrappedJSObject.options];
  93. }
  94. return [window.fetch, options];
  95. };
  96.  
  97. /**
  98. * @template T
  99. * @param {string} url
  100. * @param {Partial<GM.Request>} options
  101. * @returns {Promise<ExtendedResponse<T>>}
  102. */
  103. const _sendExternal = (url, options) => {
  104. return new Promise((resolve) => {
  105. GM.xmlHttpRequest({
  106. url,
  107. method: 'GET',
  108. ...options,
  109. onload: (response) => {
  110. /** @type {ExtendedResponse<T>} */
  111. const extendedResponse = {
  112. url: response.finalUrl,
  113. text: response.responseText,
  114. };
  115. resolve(_processResponse(extendedResponse));
  116. },
  117. });
  118. });
  119. };
  120.  
  121. /**
  122. * @template T
  123. * @param {ExtendedResponse<T>} response
  124. * @returns {ExtendedResponse<T>}
  125. */
  126. const _processResponse = (response) => {
  127. const processedResponse = { ...response };
  128. try {
  129. processedResponse.dom = DOM.parse(response.text);
  130. } catch (err) {
  131. // Response is not a DOM, just ignore.
  132. }
  133. try {
  134. processedResponse.json = JSON.parse(response.text);
  135. } catch (err) {
  136. // Response is not a JSON, just ignore.
  137. }
  138. return processedResponse;
  139. };
  140.  
  141. /**
  142. * Parses a query string into an object.
  143. * @param {string} query The query to parse.
  144. * @returns {Record<string, string>} The parsed object.
  145. */
  146. const parseQuery = (query) => {
  147. /** @type {Record<string, string>} */
  148. const params = {};
  149. const rawQuery = query.startsWith('?') ? query.slice(1) : query;
  150. const parts = rawQuery.split('&').filter(Utils.isSet);
  151. for (const part of parts) {
  152. const [key, value] = part.split('=').filter(Utils.isSet);
  153. params[key] = value;
  154. }
  155. return params;
  156. };
  157.  
  158. /**
  159. * Converts an object to a query string.
  160. * @param {Record<string, unknown>} obj The object to convert.
  161. * @returns {string} The converted query string, without '?'.
  162. */
  163. const convertToQuery = (obj) => {
  164. return Object.entries(obj)
  165. .map((entry) => entry.join('='))
  166. .join('&');
  167. };
  168.  
  169. return {
  170. CONNECT: /** @template T */ (
  171. /** @type [string] | [string, RequestInit | GM.Request] */ ...args
  172. ) => /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('CONNECT', args[0], args[1])),
  173. DELETE: /** @template T */ (
  174. /** @type [string] | [string, RequestInit | GM.Request] */ ...args
  175. ) => /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('DELETE', args[0], args[1])),
  176. GET: /** @template T */ (/** @type [string] | [string, RequestInit | GM.Request] */ ...args) =>
  177. /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('GET', args[0], args[1])),
  178. HEAD: /** @template T */ (/** @type [string] | [string, RequestInit | GM.Request] */ ...args) =>
  179. /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('HEAD', args[0], args[1])),
  180. OPTIONS: /** @template T */ (
  181. /** @type [string] | [string, RequestInit | GM.Request] */ ...args
  182. ) => /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('OPTIONS', args[0], args[1])),
  183. PATCH: /** @template T */ (
  184. /** @type [string] | [string, RequestInit | GM.Request] */ ...args
  185. ) => /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('PATCH', args[0], args[1])),
  186. POST: /** @template T */ (/** @type [string] | [string, RequestInit | GM.Request] */ ...args) =>
  187. /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('POST', args[0], args[1])),
  188. PUT: /** @template T */ (/** @type [string] | [string, RequestInit | GM.Request] */ ...args) =>
  189. /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('PUT', args[0], args[1])),
  190. TRACE: /** @template T */ (
  191. /** @type [string] | [string, RequestInit | GM.Request] */ ...args
  192. ) => /** @type Promise<ExtendedResponse<T>> */ (_sendWithMethod('TRACE', args[0], args[1])),
  193. send,
  194. parseQuery,
  195. convertToQuery,
  196. };
  197. })();