您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ajax hook is a lightweight library for intercepting XMLHttpRequest objects.
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/450907/1090926/Ajax-hook-userscript.js
// ==UserScript== // @name Ajax-hook-userscript // @namespace https://github.com/wendux/Ajax-hook // @version 2.0.3 // @author wendux // @description Ajax hook is a lightweight library for intercepting XMLHttpRequest objects. // @license MIT // @grant unsafeWindow // @run-at document-start // ==/UserScript== const ah = (W => { /* * author: wendux * email: [email protected] * source code: https://github.com/wendux/Ajax-hook * modify by https://github.com/lzghzr */ // Save original XMLHttpRequest as _rxhr var realXhr = "_rxhr" function configEvent(event, xhrProxy) { var e = {}; for (var attr in event) e[attr] = event[attr]; // xhrProxy instead e.target = e.currentTarget = xhrProxy return e; } function hook(proxy) { // Avoid double hookAjax W[realXhr] = W[realXhr] || W.XMLHttpRequest W.XMLHttpRequest = function () { var xhr = new W[realXhr]; // We shouldn't hookAjax XMLHttpRequest.prototype because we can't // guarantee that all attributes are on the prototype。 // Instead, hooking XMLHttpRequest instance can avoid this problem. for (var attr in xhr) { var type = ""; try { type = typeof xhr[attr] // May cause exception on some browser } catch (e) { } if (type === "function") { // hookAjax methods of xhr, such as `open`、`send` ... this[attr] = hookFunction(attr); } else { Object.defineProperty(this, attr, { get: getterFactory(attr), set: setterFactory(attr), enumerable: true }) } } var that = this; xhr.getProxy = function () { return that } this.xhr = xhr; } // Generate getter for attributes of xhr function getterFactory(attr) { return function () { var v = this.hasOwnProperty(attr + "_") ? this[attr + "_"] : this.xhr[attr]; var attrGetterHook = (proxy[attr] || {})["getter"] return attrGetterHook && attrGetterHook(v, this) || v } } // Generate setter for attributes of xhr; by this we have an opportunity // to hookAjax event callbacks (eg: `onload`) of xhr; function setterFactory(attr) { return function (v) { var xhr = this.xhr; var that = this; var hook = proxy[attr]; // hookAjax event callbacks such as `onload`、`onreadystatechange`... if (attr.substring(0, 2) === 'on') { that[attr + "_"] = v; xhr[attr] = function (e) { e = configEvent(e, that) var ret = proxy[attr] && proxy[attr].call(that, xhr, e) ret || v.call(that, e); } } else { //If the attribute isn't writable, generate proxy attribute var attrSetterHook = (hook || {})["setter"]; v = attrSetterHook && attrSetterHook(v, that) || v this[attr + "_"] = v; try { // Not all attributes of xhr are writable(setter may undefined). xhr[attr] = v; } catch (e) { } } } } // Hook methods of xhr. function hookFunction(fun) { return function () { var args = [].slice.call(arguments) if (proxy[fun]) { var ret = proxy[fun].call(this, args, this.xhr) // If the proxy return value exists, return it directly, // otherwise call the function of xhr. if (ret) return ret; } return this.xhr[fun].apply(this.xhr, args); } } // Return the real XMLHttpRequest return W[realXhr]; } function unHook() { if (W[realXhr]) W.XMLHttpRequest = W[realXhr]; W[realXhr] = undefined; } /* * author: wendux * email: [email protected] * source code: https://github.com/wendux/Ajax-hook */ var events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort']; var eventLoad = events[0], eventLoadEnd = events[1], eventTimeout = events[2], eventError = events[3], eventReadyStateChange = events[4], eventAbort = events[5]; var singleton, prototype = 'prototype'; function proxy(proxy) { if (singleton) throw "Proxy already exists"; return singleton = new Proxy(proxy); } function unProxy() { singleton = null unHook() } function trim(str) { return str.replace(/^\s+|\s+$/g, ''); } function getEventTarget(xhr) { return xhr.watcher || (xhr.watcher = document.createElement('a')); } function triggerListener(xhr, name) { var xhrProxy = xhr.getProxy(); var callback = 'on' + name + '_'; var event = configEvent({ type: name }, xhrProxy); xhrProxy[callback] && xhrProxy[callback](event); var evt; if (typeof (Event) === 'function') { evt = new Event(name, { bubbles: false }); } else { // https://stackoverflow.com/questions/27176983/dispatchevent-not-working-in-ie11 evt = document.createEvent('Event'); evt.initEvent(name, false, true); } getEventTarget(xhr).dispatchEvent(evt); } function Handler(xhr) { this.xhr = xhr; this.xhrProxy = xhr.getProxy(); } Handler[prototype] = Object.create({ resolve: function resolve(response) { var xhrProxy = this.xhrProxy; var xhr = this.xhr; xhrProxy.readyState = 4; xhr.resHeader = response.headers; xhrProxy.response = xhrProxy.responseText = response.response; xhrProxy.statusText = response.statusText; xhrProxy.status = response.status; triggerListener(xhr, eventReadyStateChange); triggerListener(xhr, eventLoad); triggerListener(xhr, eventLoadEnd); }, reject: function reject(error) { this.xhrProxy.status = 0; triggerListener(this.xhr, error.type); triggerListener(this.xhr, eventLoadEnd); } }); function makeHandler(next) { function sub(xhr) { Handler.call(this, xhr); } sub[prototype] = Object.create(Handler[prototype]); sub[prototype].next = next; return sub; } var RequestHandler = makeHandler(function (rq) { var xhr = this.xhr; rq = rq || xhr.config; xhr.withCredentials = rq.withCredentials; xhr.open(rq.method, rq.url, rq.async !== false, rq.user, rq.password); for (var key in rq.headers) { xhr.setRequestHeader(key, rq.headers[key]); } xhr.send(rq.body); }); var ResponseHandler = makeHandler(function (response) { this.resolve(response); }); var ErrorHandler = makeHandler(function (error) { this.reject(error); }); function Proxy(proxy) { var onRequest = proxy.onRequest, onResponse = proxy.onResponse, onError = proxy.onError; function handleResponse(xhr, xhrProxy) { var handler = new ResponseHandler(xhr); if (!onResponse) return handler.resolve(); var ret = { response: xhrProxy.response, status: xhrProxy.status, statusText: xhrProxy.statusText, config: xhr.config, headers: xhr.resHeader || xhr.getAllResponseHeaders().split('\r\n').reduce(function (ob, str) { if (str === "") return ob; var m = str.split(":"); ob[m.shift()] = trim(m.join(':')); return ob; }, {}) }; onResponse(ret, handler); } function onerror(xhr, xhrProxy, e) { var handler = new ErrorHandler(xhr); var error = { config: xhr.config, error: e }; if (onError) { onError(error, handler); } else { handler.next(error); } } function preventXhrProxyCallback() { return true; } function errorCallback(xhr, e) { onerror(xhr, this, e); return true; } function stateChangeCallback(xhr, xhrProxy) { if (xhr.readyState === 4 && xhr.status !== 0) { handleResponse(xhr, xhrProxy); } else if (xhr.readyState !== 4) { triggerListener(xhr, eventReadyStateChange); } return true; } return hook({ onload: preventXhrProxyCallback, onloadend: preventXhrProxyCallback, onerror: errorCallback, ontimeout: errorCallback, onabort: errorCallback, onreadystatechange: function (xhr) { return stateChangeCallback(xhr, this); }, open: function open(args, xhr) { var _this = this; var config = xhr.config = { headers: {} }; config.method = args[0]; config.url = args[1]; config.async = args[2]; config.user = args[3]; config.password = args[4]; config.xhr = xhr; var evName = 'on' + eventReadyStateChange; if (!xhr[evName]) { xhr[evName] = function () { return stateChangeCallback(xhr, _this); }; } var defaultErrorHandler = function defaultErrorHandler(e) { onerror(xhr, _this, configEvent(e, _this)); }; [eventError, eventTimeout, eventAbort].forEach(function (e) { var event = 'on' + e; if (!xhr[event]) xhr[event] = defaultErrorHandler; }); // 如果有请求拦截器,则在调用onRequest后再打开链接。因为onRequest最佳调用时机是在send前, // 所以我们在send拦截函数中再手动调用open,因此返回true阻止xhr.open调用。 // // 如果没有请求拦截器,则不用阻断xhr.open调用 if (onRequest) return true; }, send: function (args, xhr) { var config = xhr.config config.withCredentials = xhr.withCredentials config.body = args[0]; if (onRequest) { // In 'onRequest', we may call XHR's event handler, such as `xhr.onload`. // However, XHR's event handler may not be set until xhr.send is called in // the user's code, so we use `setTimeout` to avoid this situation var req = function () { onRequest(config, new RequestHandler(xhr)); } config.async === false ? req() : setTimeout(req) return true; } }, setRequestHeader: function (args, xhr) { // Collect request headers xhr.config.headers[args[0].toLowerCase()] = args[1]; return true; }, addEventListener: function (args, xhr) { var _this = this; if (events.indexOf(args[0]) !== -1) { var handler = args[1]; getEventTarget(xhr).addEventListener(args[0], function (e) { var event = configEvent(e, _this); event.type = args[0]; event.isTrusted = true; handler.call(_this, event); }); return true; } }, getAllResponseHeaders: function (_, xhr) { var headers = xhr.resHeader if (headers) { var header = ""; for (var key in headers) { header += key + ': ' + headers[key] + '\r\n'; } return header; } }, getResponseHeader: function (args, xhr) { var headers = xhr.resHeader if (headers) { return headers[(args[0] || '').toLowerCase()]; } } }); } return { proxy, unProxy, hook, unHook, } })(typeof unsafeWindow === 'undefined' ? window : unsafeWindow);