Ovu skriptu ne treba izravno instalirati. To je biblioteka za druge skripte koje se uključuju u meta direktivu // @require https://update.greasyfork.org/scripts/483208/1377351/ajaxHooker_myaijarvis.js
- // ==UserScript==
- // @name ajaxHooker_myaijarvis
- // @description ajax劫持库,支持xhr和fetch劫持。
- // @author cxxjackie
- // @version 1.4.1
- // @supportURL https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
- // ==/UserScript==
-
- var ajaxHooker = function() {
- 'use strict';
- const version = '1.4.1';
- const hookInst = {
- hookFns: [],
- filters: []
- };
- const win = window.unsafeWindow || document.defaultView || window;
- let winAh = win.__ajaxHooker;
- const resProto = win.Response.prototype;
- const xhrResponses = ['response', 'responseText', 'responseXML'];
- const fetchResponses = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
- const fetchInitProps = ['method', 'headers', 'body', 'mode', 'credentials', 'cache', 'redirect',
- 'referrer', 'referrerPolicy', 'integrity', 'keepalive', 'signal', 'priority'];
- const xhrAsyncEvents = ['readystatechange', 'load', 'loadend'];
- const getType = ({}).toString.call.bind(({}).toString);
- const getDescriptor = Object.getOwnPropertyDescriptor.bind(Object);
- const emptyFn = () => {};
- const errorFn = e => console.error(e);
- function isThenable(obj) {
- return obj && ['object', 'function'].includes(typeof obj) && typeof obj.then === 'function';
- }
- function catchError(fn, ...args) {
- try {
- const result = fn(...args);
- if (isThenable(result)) return result.then(null, errorFn);
- return result;
- } catch (err) {
- console.error(err);
- }
- }
- function defineProp(obj, prop, getter, setter) {
- Object.defineProperty(obj, prop, {
- configurable: true,
- enumerable: true,
- get: getter,
- set: setter
- });
- }
- function readonly(obj, prop, value = obj[prop]) {
- defineProp(obj, prop, () => value, emptyFn);
- }
- function writable(obj, prop, value = obj[prop]) {
- Object.defineProperty(obj, prop, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: value
- });
- }
- function parseHeaders(obj) {
- const headers = {};
- switch (getType(obj)) {
- case '[object String]':
- for (const line of obj.trim().split(/[\r\n]+/)) {
- const [header, value] = line.split(/\s*:\s*/);
- if (!header) break;
- const lheader = header.toLowerCase();
- headers[lheader] = lheader in headers ? `${headers[lheader]}, ${value}` : value;
- }
- break;
- case '[object Headers]':
- for (const [key, val] of obj) {
- headers[key] = val;
- }
- break;
- case '[object Object]':
- return {...obj};
- }
- return headers;
- }
- function stopImmediatePropagation() {
- this.ajaxHooker_isStopped = true;
- }
- class SyncThenable {
- then(fn) {
- fn && fn();
- return new SyncThenable();
- }
- }
- class AHRequest {
- constructor(request) {
- this.request = request;
- this.requestClone = {...this.request};
- }
- shouldFilter(filters) {
- const {type, url, method, async} = this.request;
- return filters.length && !filters.find(obj => {
- switch (true) {
- case obj.type && obj.type !== type:
- case getType(obj.url) === '[object String]' && !url.includes(obj.url):
- case getType(obj.url) === '[object RegExp]' && !obj.url.test(url):
- case obj.method && obj.method.toUpperCase() !== method.toUpperCase():
- case 'async' in obj && obj.async !== async:
- return false;
- }
- return true;
- });
- }
- waitForRequestKeys() {
- const requestKeys = ['url', 'method', 'abort', 'headers', 'data'];
- if (!this.request.async) {
- win.__ajaxHooker.hookInsts.forEach(({hookFns, filters}) => {
- if (this.shouldFilter(filters)) return;
- hookFns.forEach(fn => {
- if (getType(fn) === '[object Function]') catchError(fn, this.request);
- });
- requestKeys.forEach(key => {
- if (isThenable(this.request[key])) this.request[key] = this.requestClone[key];
- });
- });
- return new SyncThenable();
- }
- const promises = [];
- win.__ajaxHooker.hookInsts.forEach(({hookFns, filters}) => {
- if (this.shouldFilter(filters)) return;
- promises.push(Promise.all(hookFns.map(fn => catchError(fn, this.request))).then(() =>
- Promise.all(requestKeys.map(key => Promise.resolve(this.request[key]).then(
- val => this.request[key] = val,
- () => this.request[key] = this.requestClone[key]
- )))
- ));
- });
- return Promise.all(promises);
- }
- waitForResponseKeys(response) {
- const responseKeys = this.request.type === 'xhr' ? xhrResponses : fetchResponses;
- if (!this.request.async) {
- if (getType(this.request.response) === '[object Function]') {
- catchError(this.request.response, response);
- responseKeys.forEach(key => {
- if ('get' in getDescriptor(response, key) || isThenable(response[key])) {
- delete response[key];
- }
- });
- }
- return new SyncThenable();
- }
- return Promise.resolve(catchError(this.request.response, response)).then(() =>
- Promise.all(responseKeys.map(key => {
- const descriptor = getDescriptor(response, key);
- if (descriptor && 'value' in descriptor) {
- return Promise.resolve(descriptor.value).then(
- val => response[key] = val,
- () => delete response[key]
- );
- } else {
- delete response[key];
- }
- }))
- );
- }
- }
- const proxyHandler = {
- get(target, prop) {
- const descriptor = getDescriptor(target, prop);
- if (descriptor && !descriptor.configurable && !descriptor.writable && !descriptor.get) return target[prop];
- const ah = target.__ajaxHooker;
- if (ah && ah.proxyProps) {
- if (prop in ah.proxyProps) {
- const pDescriptor = ah.proxyProps[prop];
- if ('get' in pDescriptor) return pDescriptor.get();
- if (typeof pDescriptor.value === 'function') return pDescriptor.value.bind(ah);
- return pDescriptor.value;
- }
- if (typeof target[prop] === 'function') return target[prop].bind(target);
- }
- return target[prop];
- },
- set(target, prop, value) {
- const descriptor = getDescriptor(target, prop);
- if (descriptor && !descriptor.configurable && !descriptor.writable && !descriptor.set) return true;
- const ah = target.__ajaxHooker;
- if (ah && ah.proxyProps && prop in ah.proxyProps) {
- const pDescriptor = ah.proxyProps[prop];
- pDescriptor.set ? pDescriptor.set(value) : (pDescriptor.value = value);
- } else {
- target[prop] = value;
- }
- return true;
- }
- };
- class XhrHooker {
- constructor(xhr) {
- const ah = this;
- Object.assign(ah, {
- originalXhr: xhr,
- proxyXhr: new Proxy(xhr, proxyHandler),
- resThenable: new SyncThenable(),
- proxyProps: {},
- proxyEvents: {}
- });
- xhr.addEventListener('readystatechange', e => {
- if (ah.proxyXhr.readyState === 4 && ah.request && typeof ah.request.response === 'function') {
- const response = {
- finalUrl: ah.proxyXhr.responseURL,
- status: ah.proxyXhr.status,
- responseHeaders: parseHeaders(ah.proxyXhr.getAllResponseHeaders())
- };
- const tempValues = {};
- for (const key of xhrResponses) {
- try {
- tempValues[key] = ah.originalXhr[key];
- } catch (err) {}
- defineProp(response, key, () => {
- return response[key] = tempValues[key];
- }, val => {
- delete response[key];
- response[key] = val;
- });
- }
- ah.resThenable = new AHRequest(ah.request).waitForResponseKeys(response).then(() => {
- for (const key of xhrResponses) {
- ah.proxyProps[key] = {get: () => {
- if (!(key in response)) response[key] = tempValues[key];
- return response[key];
- }};
- }
- });
- }
- ah.dispatchEvent(e);
- });
- xhr.addEventListener('load', e => ah.dispatchEvent(e));
- xhr.addEventListener('loadend', e => ah.dispatchEvent(e));
- for (const evt of xhrAsyncEvents) {
- const onEvt = 'on' + evt;
- ah.proxyProps[onEvt] = {
- get: () => ah.proxyEvents[onEvt] || null,
- set: val => ah.addEvent(onEvt, val)
- };
- }
- for (const method of ['setRequestHeader', 'addEventListener', 'removeEventListener', 'open', 'send']) {
- ah.proxyProps[method] = {value: ah[method]};
- }
- }
- toJSON() {} // Converting circular structure to JSON
- addEvent(type, event) {
- if (type.startsWith('on')) {
- this.proxyEvents[type] = typeof event === 'function' ? event : null;
- } else {
- if (typeof event === 'object' && event !== null) event = event.handleEvent;
- if (typeof event !== 'function') return;
- this.proxyEvents[type] = this.proxyEvents[type] || new Set();
- this.proxyEvents[type].add(event);
- }
- }
- removeEvent(type, event) {
- if (type.startsWith('on')) {
- this.proxyEvents[type] = null;
- } else {
- if (typeof event === 'object' && event !== null) event = event.handleEvent;
- this.proxyEvents[type] && this.proxyEvents[type].delete(event);
- }
- }
- dispatchEvent(e) {
- e.stopImmediatePropagation = stopImmediatePropagation;
- defineProp(e, 'target', () => this.proxyXhr);
- this.proxyEvents[e.type] && this.proxyEvents[e.type].forEach(fn => {
- this.resThenable.then(() => !e.ajaxHooker_isStopped && fn.call(this.proxyXhr, e));
- });
- if (e.ajaxHooker_isStopped) return;
- const onEvent = this.proxyEvents['on' + e.type];
- onEvent && this.resThenable.then(onEvent.bind(this.proxyXhr, e));
- }
- setRequestHeader(header, value) {
- this.originalXhr.setRequestHeader(header, value);
- if (this.originalXhr.readyState !== 1) return;
- const headers = this.request.headers;
- headers[header] = header in headers ? `${headers[header]}, ${value}` : value;
- }
- addEventListener(...args) {
- if (xhrAsyncEvents.includes(args[0])) {
- this.addEvent(args[0], args[1]);
- } else {
- this.originalXhr.addEventListener(...args);
- }
- }
- removeEventListener(...args) {
- if (xhrAsyncEvents.includes(args[0])) {
- this.removeEvent(args[0], args[1]);
- } else {
- this.originalXhr.removeEventListener(...args);
- }
- }
- open(method, url, async = true, ...args) {
- this.request = {
- type: 'xhr',
- url: url.toString(),
- method: method.toUpperCase(),
- abort: false,
- headers: {},
- data: null,
- response: null,
- async: !!async
- };
- this.openArgs = args;
- this.resThenable = new SyncThenable();
- ['responseURL', 'readyState', 'status', 'statusText', ...xhrResponses].forEach(key => {
- delete this.proxyProps[key];
- });
- return this.originalXhr.open(method, url, async, ...args);
- }
- send(data) {
- const ah = this;
- const xhr = ah.originalXhr;
- const request = ah.request;
- if (!request) return xhr.send(data);
- request.data = data;
- new AHRequest(request).waitForRequestKeys().then(() => {
- if (request.abort) {
- if (typeof request.response === 'function') {
- Object.assign(ah.proxyProps, {
- responseURL: {value: request.url},
- readyState: {value: 4},
- status: {value: 200},
- statusText: {value: 'OK'}
- });
- xhrAsyncEvents.forEach(evt => xhr.dispatchEvent(new Event(evt)));
- }
- } else {
- xhr.open(request.method, request.url, request.async, ...ah.openArgs);
- for (const header in request.headers) {
- xhr.setRequestHeader(header, request.headers[header]);
- }
- xhr.send(request.data);
- }
- });
- }
- }
- function fakeXHR() {
- const xhr = new winAh.realXHR();
- if ('__ajaxHooker' in xhr) console.warn('检测到不同版本的ajaxHooker,可能发生冲突!');
- xhr.__ajaxHooker = new XhrHooker(xhr);
- return xhr.__ajaxHooker.proxyXhr;
- }
- fakeXHR.prototype = win.XMLHttpRequest.prototype;
- Object.keys(win.XMLHttpRequest).forEach(key => fakeXHR[key] = win.XMLHttpRequest[key]);
- function fakeFetch(url, options = {}) {
- if (!url) return winAh.realFetch.call(win, url, options);
- const init = {};
- if (getType(url) === '[object Request]') {
- for (const prop of fetchInitProps) init[prop] = url[prop];
- url = url.url;
- }
- url = url.toString();
- Object.assign(init, options);
- init.method = init.method || 'GET';
- init.headers = init.headers || {};
- const request = {
- type: 'fetch',
- url: url,
- method: init.method.toUpperCase(),
- abort: false,
- headers: parseHeaders(init.headers),
- data: init.body,
- response: null,
- async: true
- };
- const req = new AHRequest(request);
- return new Promise((resolve, reject) => {
- req.waitForRequestKeys().then(() => {
- if (request.abort) {
- if (typeof request.response === 'function') {
- const response = {
- finalUrl: request.url,
- status: 200,
- responseHeaders: {}
- };
- req.waitForResponseKeys(response).then(() => {
- const key = fetchResponses.find(k => k in response);
- let val = response[key];
- if (key === 'json' && typeof val === 'object') {
- val = catchError(JSON.stringify.bind(JSON), val);
- }
- const res = new Response(val, {
- status: 200,
- statusText: 'OK'
- });
- defineProp(res, 'type', () => 'basic');
- defineProp(res, 'url', () => request.url);
- resolve(res);
- });
- } else {
- reject(new DOMException('aborted', 'AbortError'));
- }
- return;
- }
- init.method = request.method;
- init.headers = request.headers;
- init.body = request.data;
- winAh.realFetch.call(win, request.url, init).then(res => {
- if (typeof request.response === 'function') {
- const response = {
- finalUrl: res.url,
- status: res.status,
- responseHeaders: parseHeaders(res.headers)
- };
- fetchResponses.forEach(key => res[key] = function() {
- if (key in response) return Promise.resolve(response[key]);
- return resProto[key].call(this).then(val => {
- response[key] = val;
- return req.waitForResponseKeys(response).then(() => key in response ? response[key] : val);
- });
- });
- }
- resolve(res);
- }, reject);
- }).catch(err => {
- console.error(err);
- resolve(winAh.realFetch.call(win, url, init));
- });
- });
- }
- function fakeFetchClone() {
- const descriptors = Object.getOwnPropertyDescriptors(this);
- const res = winAh.realFetchClone.call(this);
- Object.defineProperties(res, descriptors);
- return res;
- }
- winAh = win.__ajaxHooker = winAh || {
- version, fakeXHR, fakeFetch, fakeFetchClone,
- realXHR: win.XMLHttpRequest,
- realFetch: win.fetch,
- realFetchClone: resProto.clone,
- hookInsts: new Set()
- };
- if (winAh.version !== version) console.warn('检测到不同版本的ajaxHooker,可能发生冲突!');
- win.XMLHttpRequest = winAh.fakeXHR;
- win.fetch = winAh.fakeFetch;
- resProto.clone = winAh.fakeFetchClone;
- winAh.hookInsts.add(hookInst);
- return {
- hook: fn => hookInst.hookFns.push(fn),
- filter: arr => {
- if (Array.isArray(arr)) hookInst.filters = arr;
- },
- protect: () => {
- readonly(win, 'XMLHttpRequest', winAh.fakeXHR);
- readonly(win, 'fetch', winAh.fakeFetch);
- readonly(resProto, 'clone', winAh.fakeFetchClone);
- },
- unhook: () => {
- winAh.hookInsts.delete(hookInst);
- if (!winAh.hookInsts.size) {
- writable(win, 'XMLHttpRequest', winAh.realXHR);
- writable(win, 'fetch', winAh.realFetch);
- writable(resProto, 'clone', winAh.realFetchClone);
- delete win.__ajaxHooker;
- }
- }
- };
- }();