HttpRequest

HttpRequest for any type of request and HttpRequestHTML to request webpage. Supports caching of responses to handle status 304.

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

  1. // ==UserScript==
  2. // @name HttpRequest Library
  3. // @namespace hoehleg.userscripts.private
  4. // @version 0.7.1
  5. // @description HttpRequest for any type of request and HttpRequestHTML to request webpage. Supports caching of responses for a given period and paging.
  6. // @author Gerrit Höhle
  7. //
  8. // @grant GM_xmlhttpRequest
  9. //
  10. // ==/UserScript==
  11.  
  12. // https://greasyfork.org/de/scripts/405144
  13.  
  14. /* jslint esversion: 9 */
  15. class HttpRequest {
  16. constructor({
  17. method,
  18. url,
  19. params = {},
  20. headers = {},
  21. data = '',
  22. contentType = 'application/x-www-form-urlencoded; charset=UTF-8',
  23. responseType = 'text',
  24. } = {}) {
  25. /** @type {string} */
  26. this.method = method.toUpperCase();
  27.  
  28. /** @type {string} */
  29. this.url = url;
  30.  
  31. /** @type {Object<string, string|number>|string} */
  32. this.params = params;
  33.  
  34. /** @type {object} */
  35. this.headers = headers;
  36.  
  37. /** @type {string} */
  38. this.data = data;
  39.  
  40. /** @type {string} */
  41. this.contentType = contentType;
  42.  
  43. /** @type {string} */
  44. this.responseType = responseType;
  45. }
  46.  
  47. async send() {
  48. if (!this.method || !this.url) {
  49. return await Promise.reject("invalid request");
  50. }
  51.  
  52. const { method, responseType } = this;
  53. const params = typeof this.params === "string" ? this.params : Object.entries(this.params).map(([key, value]) => key + '=' + value).join('&');
  54.  
  55. let url = this.url;
  56. if (params.length) {
  57. url += '?' + params;
  58. }
  59. url = encodeURI(url);
  60.  
  61. const headers = Object.assign({ 'Content-Type': this.contentType }, this.headers);
  62. const data = typeof this.data === 'string' ? this.data : JSON.stringify(this.data);
  63.  
  64. return await new Promise((resolve, reject) => {
  65. const onload = (response) => {
  66. resolve(response);
  67. };
  68. const onerror = (errorEvent) => {
  69. console.log(errorEvent);
  70. reject("network error");
  71. };
  72.  
  73. GM_xmlhttpRequest({ method, url, onload, onerror, headers, data, responseType });
  74. });
  75. }
  76.  
  77. static async send(...args) {
  78. return await new HttpRequest(...args).send();
  79. }
  80. }
  81.  
  82. class HttpRequestHtml extends HttpRequest {
  83.  
  84. /**
  85. * @param {HttpRequestHtmlParams} param0
  86. */
  87. constructor({
  88. url,
  89. params = {},
  90. pageNr = 0,
  91. pagesMaxCount = 1,
  92. resultTransformer = (resp, _httpRequestHtml) => resp,
  93. hasNextPage = (_resp, _httpRequestHtml, _lastResult) => false,
  94. urlConfiguratorForPageNr = (url, _pageNr) => url,
  95. paramsConfiguratorForPageNr = (params, _pageNr) => params,
  96. } = {}) {
  97. super({ method: 'GET', url, params });
  98.  
  99. Object.assign(this, {
  100. pageNr,
  101. pagesMaxCount: Math.max(0, pagesMaxCount),
  102. resultTransformer,
  103. hasNextPage,
  104. urlConfiguratorForPageNr,
  105. paramsConfiguratorForPageNr,
  106. });
  107. }
  108.  
  109. /**
  110. * @returns {Promise<HttpRequestHtmlResponse|object|Array<object>}
  111. */
  112. async send() {
  113. const results = [];
  114.  
  115. let response = null, requestForPage = null;
  116.  
  117. for (let pageNr = this.pageNr; pageNr < this.pageNr + this.pagesMaxCount; pageNr++) {
  118.  
  119. requestForPage = new HttpRequestHtml({
  120. ...this, ...{
  121. url: this.urlConfiguratorForPageNr(this.url, pageNr),
  122. params: this.paramsConfiguratorForPageNr(typeof this.params === 'string' ? this.params : { ...this.params }, pageNr)
  123. }
  124. });
  125.  
  126. response = await HttpRequest.prototype.send.call(requestForPage);
  127. if (response.status == 200 || response.status == 304) {
  128. response.html = new DOMParser().parseFromString(response.responseText, 'text/html');
  129. }
  130.  
  131. const resultForPage = this.resultTransformer(response, requestForPage);
  132. results.push(resultForPage);
  133.  
  134. if (!this.hasNextPage(response, requestForPage, resultForPage)) {
  135. break;
  136. }
  137. }
  138.  
  139. return this.pagesMaxCount > 1 ? results : results[0];
  140. }
  141.  
  142. /**
  143. * @param {HttpRequestHtmlParams} param0
  144. * @returns {Promise<HttpRequestHtmlResponse|object|Array<object>}
  145. */
  146. static async send(...args) {
  147. return await new HttpRequestHtml(...args).send();
  148. }
  149. }
  150.  
  151. class HttpRequestJSON extends HttpRequest {
  152. /** @param {HttpRequestJSONParams} param0 */
  153. constructor({
  154. method = 'GET',
  155. url,
  156. headers,
  157. data,
  158. params = {},
  159. fallbackResult = null,
  160. contentType = 'application/json',
  161. }) {
  162. super({ method, url, headers, data, params, contentType, responseType: "json" });
  163. this.fallbackResult = fallbackResult;
  164. }
  165.  
  166. /** @returns {Promise<any>} */
  167. async send() {
  168. const response = await super.send();
  169. if (!response || !response.responseText) {
  170. return this.fallbackResult;
  171. }
  172.  
  173. try {
  174. return JSON.parse(response.responseText);
  175. } catch (error) {
  176. console.log(error);
  177. return this.fallbackResult;
  178. }
  179. }
  180.  
  181. /**
  182. *
  183. * @param {HttpRequestJSONParams} param0
  184. * @returns {Promise<any>}
  185. */
  186. static async send(param0) {
  187. return await new HttpRequestJSON(param0).send();
  188. }
  189. }
  190.  
  191. class HttpRequestBlob extends HttpRequest {
  192. constructor({
  193. method = 'GET',
  194. url,
  195. headers,
  196. data,
  197. params = {},
  198. contentType,
  199. }) {
  200. super({ method, url, headers, data, params, contentType, responseType: 'blob' });
  201. }
  202.  
  203. async send() {
  204. const response = await super.send();
  205.  
  206. /** @type {Blob} */
  207. const blob = response.response;
  208. const data = await new Promise((resolve, reject) => {
  209. const reader = Object.assign(new FileReader(), {
  210.  
  211. onload: (load) => {
  212. resolve(load.target.result);
  213. },
  214.  
  215. onerror: (error) => {
  216. console.log(error);
  217. reject(reader.result);
  218. }
  219.  
  220. });
  221. reader.readAsDataURL(blob);
  222. });
  223. return data;
  224. }
  225.  
  226. static async send(param0) {
  227. return await new HttpRequestBlob(param0).send();
  228. }
  229. }