GM Fetch

A fetch API for GM_xmlhttpRequest / GM.xmlHttpRequest

当前为 2023-09-12 提交的版本,查看 最新版本

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

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