您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
CORS Bypass script
// ==UserScript== // @name GM_fetch // @author xihale // @description CORS Bypass script // @namespace xihale.top // @license GPL version 3 // @grant GM_xmlhttpRequest // @grant unsafeWindow // @connect * // @match localhost // @match q.xihale.top // @run-at document-start // @version 0.0.2 // ==/UserScript== // reference: https://github.com/Tampermonkey/tampermonkey/issues/1278#issuecomment-1004568936 ;(function () { 'use strict' const PREFIX = '[GMFetch]' const nativeFetch = typeof unsafeWindow.fetch === 'function' ? unsafeWindow.fetch.bind(unsafeWindow) : null const RESPONSE_TYPE = GM_xmlhttpRequest?.RESPONSE_TYPE_STREAM ? 'stream' : 'arraybuffer' function toPlainHeaders(headersInit = {}) { try { return Object.fromEntries(new Headers(headersInit).entries()) } catch (error) { console.warn(`${PREFIX} 无法解析请求头`, headersInit, error) return {} } } function parseHeaders(raw) { const headers = new Headers() if (!raw) { return headers } raw .trim() .split(/\r?\n/) .forEach((line) => { if (!line) return const index = line.indexOf(':') if (index === -1) return const key = line.slice(0, index).trim() const value = line.slice(index + 1).trim() try { headers.append(key, value) } catch (err) { console.warn(`${PREFIX} 无法解析响应头`, line, err) } }) return headers } const toBody = (body, binary = false) => ({ body, binary }) const emptyBody = () => toBody() const binaryBody = (body) => toBody(body, true) const matchAsync = async (value, cases, fallback) => { for (const [predicate, resolver] of cases) { if (await predicate(value)) { return resolver(value) } } return fallback(value) } const isInstance = (Ctor) => (value) => typeof Ctor !== 'undefined' && value instanceof Ctor async function normalizeBody(body) { return matchAsync( body, [ [(value) => value == null, () => emptyBody()], [(value) => typeof value === 'string', (value) => toBody(value)], [isInstance(URLSearchParams), (value) => toBody(value.toString())], [isInstance(FormData), (value) => toBody(value)], [isInstance(Blob), async (value) => binaryBody(await value.arrayBuffer())], [isInstance(ArrayBuffer), (value) => binaryBody(value)], [ArrayBuffer.isView, (value) => binaryBody(value.buffer)], ], (value) => toBody(value), ) } async function resolveBody(request, init = {}) { if (init.body !== undefined) { return normalizeBody(init.body) } if (!(request instanceof Request)) { return emptyBody() } const method = request.method?.toUpperCase?.() ?? 'GET' if (method === 'GET' || method === 'HEAD') { return emptyBody() } const readers = [ async () => { const text = await request.clone().text() return text ? toBody(text) : null }, async () => { const buffer = await request.clone().arrayBuffer() return buffer && buffer.byteLength > 0 ? binaryBody(buffer) : null }, ] for (const read of readers) { try { const result = await read() if (result) { return result } } catch {} } return emptyBody() } function buildResponse(gmResponse) { const headers = parseHeaders(gmResponse.responseHeaders) const status = gmResponse.status || 0 const init = { headers, status: status === 0 ? 200 : status, statusText: gmResponse.statusText || 'OK', } if (status === 0) { console.warn(`${PREFIX} 接收到状态码 0,自动回退为 200`, gmResponse) } const body = gmResponse.response ?? null return new Response(body, init) } function toRequest(input, init) { if (input instanceof Request) { return new Request(input, init) } return new Request(String(input), init) } async function gmFetch(input, fetchInit = {}) { const request = toRequest(input, fetchInit) const requestUrl = request.url if (!requestUrl) { throw new TypeError('Failed to execute fetch: URL is required') } if (typeof GM_xmlhttpRequest !== 'function') { if (nativeFetch) { return nativeFetch(input, fetchInit) } throw new ReferenceError('GM_xmlhttpRequest is not available') } const headers = toPlainHeaders(fetchInit.headers ?? request.headers) const { body, binary } = await resolveBody(request, fetchInit) return new Promise((resolve, reject) => { let settled = false const settleWith = (factory) => (response) => { if (settled) return settled = true try { resolve(factory(response)) } catch (error) { reject(error) } } const finalize = settleWith(buildResponse) const config = { url: requestUrl, method: (fetchInit.method ?? request.method ?? 'GET').toUpperCase(), headers, data: body, binary: Boolean(binary), responseType: RESPONSE_TYPE, onload: finalize, onerror: (error) => { if (settled) return settled = true reject(error?.error || error || new Error('GM_xmlhttpRequest failed')) }, } if (RESPONSE_TYPE === 'stream') { config.onreadystatechange = (response) => { if (response.readyState === 2 && !settled) { finalize(response) } } } GM_xmlhttpRequest(config) }) } Object.defineProperty(gmFetch, 'native', { value: nativeFetch, writable: false, enumerable: false, configurable: false, }) unsafeWindow.gm_fetch = gmFetch console.info(`${PREFIX} Tampermonkey 通用 CORS 代理已启用`) })()