GM Fetch

A fetch API of GM_xmlhttpRequest

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

  1. // ==UserScript==
  2. // @name GM Fetch
  3. // @namespace https://github.com/Sec-ant
  4. // @version 1.2.3
  5. // @author Ze-Zheng Wu
  6. // @description A fetch API for GM_xmlhttpRequest / GM.xmlHttpRequest
  7. // @license MIT
  8. // @homepage https://github.com/Sec-ant/gm-fetch
  9. // @homepageURL https://github.com/Sec-ant/gm-fetch
  10. // @source https://github.com/Sec-ant/gm-fetch.git
  11. // @supportURL https://github.com/Sec-ant/gm-fetch/issues
  12. // @downloadURL https://fastly.jsdelivr.net/npm/@sec-ant/gm-fetch@latest/dist/gm-fetch.user.js
  13. // @updateURL https://fastly.jsdelivr.net/npm/@sec-ant/gm-fetch@latest/dist/gm-fetch.meta.js
  14. // @match *://*/*
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM_xmlhttpRequest
  17. // ==/UserScript==
  18.  
  19. (function () {
  20. 'use strict';
  21.  
  22. (function(global, factory) {
  23. typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.gmFetch = factory());
  24. })(undefined, function() {
  25. var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)();
  26. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  27. function parseHeaders(rawHeaders) {
  28. var _a;
  29. const headers = new Headers();
  30. const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
  31. for (const line of preProcessedHeaders.split(/\r?\n/)) {
  32. const parts = line.split(":");
  33. const key = (_a = parts.shift()) == null ? void 0 : _a.trim();
  34. if (key) {
  35. const value = parts.join(":").trim();
  36. try {
  37. headers.append(key, value);
  38. } catch (error) {
  39. console.warn(`Response ${error.message}`);
  40. }
  41. }
  42. }
  43. return headers;
  44. }
  45. const gmFetch = async (input, init) => {
  46. const gmXhr = _GM_xmlhttpRequest || _GM.xmlHttpRequest;
  47. if (typeof gmXhr !== "function") {
  48. throw new DOMException(
  49. "GM_xmlhttpRequest or GM.xmlHttpRequest is not granted.",
  50. "NotFoundError"
  51. );
  52. }
  53. const request = new Request(input, init);
  54. if (request.signal.aborted) {
  55. throw new DOMException("Network request aborted.", "AbortError");
  56. }
  57. const data = await request.blob();
  58. const headers = Object.fromEntries(request.headers);
  59. new Headers(init == null ? void 0 : init.headers).forEach((value, key) => {
  60. headers[key] = value;
  61. });
  62. return new Promise((resolve, reject) => {
  63. let settled = false;
  64. const responseBlobPromise = new Promise((resolveBlob) => {
  65. const { abort } = gmXhr({
  66. method: request.method.toUpperCase(),
  67. url: request.url || location.href,
  68. headers,
  69. data: data.size ? data : void 0,
  70. redirect: request.redirect,
  71. binary: true,
  72. nocache: request.cache === "no-store",
  73. revalidate: request.cache === "reload",
  74. timeout: 3e5,
  75. responseType: gmXhr.RESPONSE_TYPE_STREAM ?? "blob",
  76. overrideMimeType: request.headers.get("Content-Type") ?? void 0,
  77. anonymous: request.credentials === "omit",
  78. onload: ({ response: responseBody }) => {
  79. if (settled) {
  80. resolveBlob(null);
  81. return;
  82. }
  83. resolveBlob(responseBody);
  84. },
  85. async onreadystatechange({
  86. readyState,
  87. responseHeaders,
  88. status,
  89. statusText,
  90. finalUrl,
  91. response: responseBody
  92. }) {
  93. if (readyState === XMLHttpRequest.DONE) {
  94. request.signal.removeEventListener("abort", abort);
  95. } else if (readyState !== XMLHttpRequest.HEADERS_RECEIVED) {
  96. return;
  97. }
  98. if (settled) {
  99. resolveBlob(null);
  100. return;
  101. }
  102. const parsedHeaders = parseHeaders(responseHeaders);
  103. const redirected = request.url !== finalUrl;
  104. const response = new Response(
  105. responseBody instanceof ReadableStream ? responseBody : await responseBlobPromise,
  106. {
  107. headers: parsedHeaders,
  108. status,
  109. statusText
  110. }
  111. );
  112. Object.defineProperties(response, {
  113. url: {
  114. value: finalUrl
  115. },
  116. type: {
  117. value: "basic"
  118. },
  119. ...response.redirected !== redirected ? {
  120. redirected: {
  121. value: redirected
  122. }
  123. } : {},
  124. // https://fetch.spec.whatwg.org/#forbidden-response-header-name
  125. ...parsedHeaders.has("set-cookie") || parsedHeaders.has("set-cookie2") ? {
  126. headers: {
  127. value: parsedHeaders
  128. }
  129. } : {}
  130. });
  131. resolve(response);
  132. settled = true;
  133. },
  134. onerror: ({ statusText, error }) => {
  135. reject(
  136. new TypeError(statusText || error || "Network request failed.")
  137. );
  138. resolveBlob(null);
  139. },
  140. ontimeout() {
  141. reject(new TypeError("Network request timeout."));
  142. resolveBlob(null);
  143. },
  144. onabort() {
  145. reject(new DOMException("Network request aborted.", "AbortError"));
  146. resolveBlob(null);
  147. }
  148. });
  149. request.signal.addEventListener("abort", abort);
  150. });
  151. });
  152. };
  153. return gmFetch;
  154. });
  155.  
  156. })();