Greasy Fork 还支持 简体中文。

Requests

Useful library for sending requests.

目前為 2020-06-27 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/405822/820811/Requests.js

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