ajaxHooker_2

ajax劫持库,支持xhr和fetch劫持。

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/476730/1259979/ajaxHooker_2.js

  1. // ==UserScript==
  2. // @name ajaxHooker_2
  3. // @namespace http://tampermonkey.net/
  4. // @description ajax劫持库,支持xhr和fetch劫持。
  5. // @author cxxjackie
  6. // @version 1.3.3
  7. // @license MIT
  8. // @supportURL https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
  9. // ==/UserScript==
  10.  
  11. var ajaxHooker = function() {
  12. 'use strict';
  13. const win = window.unsafeWindow || document.defaultView || window;
  14. const toString = Object.prototype.toString;
  15. const getDescriptor = Object.getOwnPropertyDescriptor;
  16. const hookFns = [];
  17. const realXhr = win.XMLHttpRequest;
  18. const realFetch = win.fetch;
  19. const resProto = win.Response.prototype;
  20. const xhrResponses = ['response', 'responseText', 'responseXML'];
  21. const fetchResponses = ['arrayBuffer', 'blob', 'formData', 'json', 'text'];
  22. const fetchInitProps = ['method', 'headers', 'body', 'mode', 'credentials', 'cache', 'redirect',
  23. 'referrer', 'referrerPolicy', 'integrity', 'keepalive', 'signal', 'priority'];
  24. const xhrAsyncEvents = ['readystatechange', 'load', 'loadend'];
  25. let filter;
  26. function emptyFn() {}
  27. function errorFn(err) {
  28. console.error(err);
  29. }
  30. function defineProp(obj, prop, getter, setter) {
  31. Object.defineProperty(obj, prop, {
  32. configurable: true,
  33. enumerable: true,
  34. get: getter,
  35. set: setter
  36. });
  37. }
  38. function readonly(obj, prop, value = obj[prop]) {
  39. defineProp(obj, prop, () => value, emptyFn);
  40. }
  41. function writable(obj, prop, value = obj[prop]) {
  42. Object.defineProperty(obj, prop, {
  43. configurable: true,
  44. enumerable: true,
  45. writable: true,
  46. value: value
  47. });
  48. }
  49. function shouldFilter(type, url, method, async) {
  50. return filter && !filter.find(obj => {
  51. switch (true) {
  52. case obj.type && obj.type !== type:
  53. case toString.call(obj.url) === '[object String]' && !url.includes(obj.url):
  54. case toString.call(obj.url) === '[object RegExp]' && !obj.url.test(url):
  55. case obj.method && obj.method.toUpperCase() !== method.toUpperCase():
  56. case 'async' in obj && obj.async !== async:
  57. return false;
  58. }
  59. return true;
  60. });
  61. }
  62. function parseHeaders(obj) {
  63. const headers = {};
  64. switch (toString.call(obj)) {
  65. case '[object String]':
  66. for (const line of obj.trim().split(/[\r\n]+/)) {
  67. const parts = line.split(/\s*:\s*/);
  68. if (parts.length !== 2) continue;
  69. const lheader = parts[0].toLowerCase();
  70. if (lheader in headers) {
  71. headers[lheader] += ', ' + parts[1];
  72. } else {
  73. headers[lheader] = parts[1];
  74. }
  75. }
  76. return headers;
  77. case '[object Headers]':
  78. for (const [key, val] of obj) {
  79. headers[key] = val;
  80. }
  81. return headers;
  82. case '[object Object]':
  83. return {...obj};
  84. default:
  85. return headers;
  86. }
  87. }
  88. class AHRequest {
  89. constructor(request) {
  90. this.request = request;
  91. this.requestClone = {...this.request};
  92. this.response = {};
  93. }
  94. waitForHookFns() {
  95. return Promise.all(hookFns.map(fn => {
  96. try {
  97. return Promise.resolve(fn(this.request)).then(emptyFn, errorFn);
  98. } catch (err) {
  99. console.error(err);
  100. }
  101. }));
  102. }
  103. waitForResponseFn() {
  104. try {
  105. return Promise.resolve(this.request.response(this.response)).then(emptyFn, errorFn);
  106. } catch (err) {
  107. console.error(err);
  108. return Promise.resolve();
  109. }
  110. }
  111. waitForRequestKeys() {
  112. if (this.reqPromise) return this.reqPromise;
  113. const requestKeys = ['url', 'method', 'abort', 'headers', 'data'];
  114. return this.reqPromise = this.waitForHookFns().then(() => Promise.all(
  115. requestKeys.map(key => Promise.resolve(this.request[key]).then(
  116. val => this.request[key] = val,
  117. e => this.request[key] = this.requestClone[key]
  118. ))
  119. ));
  120. }
  121. waitForResponseKeys() {
  122. if (this.resPromise) return this.resPromise;
  123. const responseKeys = this.request.type === 'xhr' ? xhrResponses : fetchResponses;
  124. return this.resPromise = this.waitForResponseFn().then(() => Promise.all(
  125. responseKeys.map(key => {
  126. const descriptor = getDescriptor(this.response, key);
  127. if (descriptor && 'value' in descriptor) {
  128. return Promise.resolve(descriptor.value).then(
  129. val => this.response[key] = val,
  130. e => delete this.response[key]
  131. );
  132. } else {
  133. delete this.response[key];
  134. }
  135. })
  136. ));
  137. }
  138. }
  139. class XhrEvents {
  140. constructor() {
  141. this.events = {};
  142. }
  143. add(type, event) {
  144. if (type.startsWith('on')) {
  145. this.events[type] = typeof event === 'function' ? event : null;
  146. } else {
  147. this.events[type] = this.events[type] || new Set();
  148. this.events[type].add(event);
  149. }
  150. }
  151. remove(type, event) {
  152. if (type.startsWith('on')) {
  153. this.events[type] = null;
  154. } else {
  155. this.events[type] && this.events[type].delete(event);
  156. }
  157. }
  158. _sIP() {
  159. this.ajaxHooker_isStopped = true;
  160. }
  161. trigger(e) {
  162. if (e.ajaxHooker_isTriggered || e.ajaxHooker_isStopped) return;
  163. e.stopImmediatePropagation = this._sIP;
  164. this.events[e.type] && this.events[e.type].forEach(fn => {
  165. !e.ajaxHooker_isStopped && fn.call(e.target, e);
  166. });
  167. this.events['on' + e.type] && this.events['on' + e.type].call(e.target, e);
  168. e.ajaxHooker_isTriggered = true;
  169. }
  170. clone() {
  171. const eventsClone = new XhrEvents();
  172. for (const type in this.events) {
  173. if (type.startsWith('on')) {
  174. eventsClone.events[type] = this.events[type];
  175. } else {
  176. eventsClone.events[type] = new Set([...this.events[type]]);
  177. }
  178. }
  179. return eventsClone;
  180. }
  181. }
  182. const xhrMethods = {
  183. readyStateChange(e) {
  184. if (e.target.readyState === 4) {
  185. e.target.dispatchEvent(new CustomEvent('ajaxHooker_responseReady', {detail: e}));
  186. } else {
  187. e.target.__ajaxHooker.eventTrigger(e);
  188. }
  189. },
  190. asyncListener(e) {
  191. e.target.__ajaxHooker.eventTrigger(e);
  192. },
  193. setRequestHeader(header, value) {
  194. const ah = this.__ajaxHooker;
  195. ah.originalXhr.setRequestHeader(header, value);
  196. if (this.readyState !== 1) return;
  197. if (header in ah.headers) {
  198. ah.headers[header] += ', ' + value;
  199. } else {
  200. ah.headers[header] = value;
  201. }
  202. },
  203. addEventListener(...args) {
  204. const ah = this.__ajaxHooker;
  205. if (xhrAsyncEvents.includes(args[0])) {
  206. ah.proxyEvents.add(args[0], args[1]);
  207. } else {
  208. ah.originalXhr.addEventListener(...args);
  209. }
  210. },
  211. removeEventListener(...args) {
  212. const ah = this.__ajaxHooker;
  213. if (xhrAsyncEvents.includes(args[0])) {
  214. ah.proxyEvents.remove(args[0], args[1]);
  215. } else {
  216. ah.originalXhr.removeEventListener(...args);
  217. }
  218. },
  219. open(method, url, async = true, ...args) {
  220. const ah = this.__ajaxHooker;
  221. ah.url = url.toString();
  222. ah.method = method.toUpperCase();
  223. ah.async = !!async;
  224. ah.openArgs = args;
  225. ah.headers = {};
  226. for (const key of xhrResponses) {
  227. ah.proxyProps[key] = {
  228. get: () => {
  229. const val = ah.originalXhr[key];
  230. ah.originalXhr.dispatchEvent(new CustomEvent('ajaxHooker_readResponse', {
  231. detail: {key, val}
  232. }));
  233. return val;
  234. }
  235. };
  236. }
  237. return ah.originalXhr.open(method, url, ...args);
  238. },
  239. sendFactory(realSend) {
  240. return function(data) {
  241. const ah = this.__ajaxHooker;
  242. const xhr = ah.originalXhr;
  243. if (xhr.readyState !== 1) return realSend.call(xhr, data);
  244. ah.eventTrigger = e => ah.proxyEvents.trigger(e);
  245. if (shouldFilter('xhr', ah.url, ah.method, ah.async)) {
  246. xhr.addEventListener('ajaxHooker_responseReady', e => {
  247. ah.eventTrigger(e.detail);
  248. }, {once: true});
  249. return realSend.call(xhr, data);
  250. }
  251. const request = {
  252. type: 'xhr',
  253. url: ah.url,
  254. method: ah.method,
  255. abort: false,
  256. headers: ah.headers,
  257. data: data,
  258. response: null,
  259. async: ah.async
  260. };
  261. if (!ah.async) {
  262. const requestClone = {...request};
  263. hookFns.forEach(fn => {
  264. try {
  265. toString.call(fn) === '[object Function]' && fn(request);
  266. } catch (err) {
  267. console.error(err);
  268. }
  269. });
  270. for (const key in request) {
  271. if (toString.call(request[key]) === '[object Promise]') {
  272. request[key] = requestClone[key];
  273. }
  274. }
  275. xhr.open(request.method, request.url, ah.async, ...ah.openArgs);
  276. for (const header in request.headers) {
  277. xhr.setRequestHeader(header, request.headers[header]);
  278. }
  279. data = request.data;
  280. xhr.addEventListener('ajaxHooker_responseReady', e => {
  281. ah.eventTrigger(e.detail);
  282. }, {once: true});
  283. realSend.call(xhr, data);
  284. if (toString.call(request.response) === '[object Function]') {
  285. const response = {
  286. finalUrl: xhr.responseURL,
  287. status: xhr.status,
  288. responseHeaders: parseHeaders(xhr.getAllResponseHeaders())
  289. };
  290. for (const key of xhrResponses) {
  291. defineProp(response, key, () => {
  292. return response[key] = ah.originalXhr[key];
  293. }, val => {
  294. if (toString.call(val) !== '[object Promise]') {
  295. delete response[key];
  296. response[key] = val;
  297. }
  298. });
  299. }
  300. try {
  301. request.response(response);
  302. } catch (err) {
  303. console.error(err);
  304. }
  305. for (const key of xhrResponses) {
  306. ah.proxyProps[key] = {get: () => response[key]};
  307. };
  308. }
  309. return;
  310. }
  311. const req = new AHRequest(request);
  312. req.waitForRequestKeys().then(() => {
  313. if (request.abort) return;
  314. xhr.open(request.method, request.url, ...ah.openArgs);
  315. for (const header in request.headers) {
  316. xhr.setRequestHeader(header, request.headers[header]);
  317. }
  318. data = request.data;
  319. xhr.addEventListener('ajaxHooker_responseReady', e => {
  320. if (typeof request.response !== 'function') return ah.eventTrigger(e.detail);
  321. req.response = {
  322. finalUrl: xhr.responseURL,
  323. status: xhr.status,
  324. responseHeaders: parseHeaders(xhr.getAllResponseHeaders())
  325. };
  326. for (const key of xhrResponses) {
  327. defineProp(req.response, key, () => {
  328. return req.response[key] = ah.originalXhr[key];
  329. }, val => {
  330. delete req.response[key];
  331. req.response[key] = val;
  332. });
  333. }
  334. const resPromise = req.waitForResponseKeys().then(() => {
  335. for (const key of xhrResponses) {
  336. if (!(key in req.response)) continue;
  337. ah.proxyProps[key] = {
  338. get: () => {
  339. const val = req.response[key];
  340. xhr.dispatchEvent(new CustomEvent('ajaxHooker_readResponse', {
  341. detail: {key, val}
  342. }));
  343. return val;
  344. }
  345. };
  346. }
  347. });
  348. xhr.addEventListener('ajaxHooker_readResponse', e => {
  349. const descriptor = getDescriptor(req.response, e.detail.key);
  350. if (!descriptor || 'get' in descriptor) {
  351. req.response[e.detail.key] = e.detail.val;
  352. }
  353. });
  354. const eventsClone = ah.proxyEvents.clone();
  355. ah.eventTrigger = event => resPromise.then(() => eventsClone.trigger(event));
  356. ah.eventTrigger(e.detail);
  357. }, {once: true});
  358. realSend.call(xhr, data);
  359. });
  360. };
  361. }
  362. };
  363. function fakeXhr() {
  364. const xhr = new realXhr();
  365. let ah = xhr.__ajaxHooker;
  366. let xhrProxy = xhr;
  367. if (!ah) {
  368. const proxyEvents = new XhrEvents();
  369. ah = xhr.__ajaxHooker = {
  370. headers: {},
  371. originalXhr: xhr,
  372. proxyProps: {},
  373. proxyEvents: proxyEvents,
  374. eventTrigger: e => proxyEvents.trigger(e),
  375. toJSON: emptyFn // Converting circular structure to JSON
  376. };
  377. xhrProxy = new Proxy(xhr, {
  378. get(target, prop) {
  379. try {
  380. if (target === xhr) {
  381. if (prop in ah.proxyProps) {
  382. const descriptor = ah.proxyProps[prop];
  383. return descriptor.get ? descriptor.get() : descriptor.value;
  384. }
  385. if (typeof xhr[prop] === 'function') return xhr[prop].bind(xhr);
  386. }
  387. } catch (err) {
  388. console.error(err);
  389. }
  390. return target[prop];
  391. },
  392. set(target, prop, value) {
  393. try {
  394. if (target === xhr && prop in ah.proxyProps) {
  395. const descriptor = ah.proxyProps[prop];
  396. descriptor.set ? descriptor.set(value) : (descriptor.value = value);
  397. } else {
  398. target[prop] = value;
  399. }
  400. } catch (err) {
  401. console.error(err);
  402. }
  403. return true;
  404. }
  405. });
  406. xhr.addEventListener('readystatechange', xhrMethods.readyStateChange);
  407. xhr.addEventListener('load', xhrMethods.asyncListener);
  408. xhr.addEventListener('loadend', xhrMethods.asyncListener);
  409. for (const evt of xhrAsyncEvents) {
  410. const onEvt = 'on' + evt;
  411. ah.proxyProps[onEvt] = {
  412. get: () => proxyEvents.events[onEvt] || null,
  413. set: val => proxyEvents.add(onEvt, val)
  414. };
  415. }
  416. for (const method of ['setRequestHeader', 'addEventListener', 'removeEventListener', 'open']) {
  417. ah.proxyProps[method] = { value: xhrMethods[method] };
  418. }
  419. }
  420. ah.proxyProps.send = { value: xhrMethods.sendFactory(xhr.send) };
  421. return xhrProxy;
  422. }
  423. function hookFetchResponse(response, req) {
  424. for (const key of fetchResponses) {
  425. response[key] = () => new Promise((resolve, reject) => {
  426. if (key in req.response) return resolve(req.response[key]);
  427. resProto[key].call(response).then(res => {
  428. req.response[key] = res;
  429. req.waitForResponseKeys().then(() => {
  430. resolve(key in req.response ? req.response[key] : res);
  431. });
  432. }, reject);
  433. });
  434. }
  435. }
  436. function fakeFetch(url, options = {}) {
  437. if (!url) return realFetch.call(win, url, options);
  438. let init = {...options};
  439. if (toString.call(url) === '[object Request]') {
  440. init = {};
  441. for (const prop of fetchInitProps) init[prop] = url[prop];
  442. Object.assign(init, options);
  443. url = url.url;
  444. }
  445. url = url.toString();
  446. init.method = init.method || 'GET';
  447. init.headers = init.headers || {};
  448. if (shouldFilter('fetch', url, init.method, true)) return realFetch.call(win, url, init);
  449. const request = {
  450. type: 'fetch',
  451. url: url,
  452. method: init.method.toUpperCase(),
  453. abort: false,
  454. headers: parseHeaders(init.headers),
  455. data: init.body,
  456. response: null,
  457. async: true
  458. };
  459. const req = new AHRequest(request);
  460. return new Promise((resolve, reject) => {
  461. req.waitForRequestKeys().then(() => {
  462. if (request.abort) return reject(new DOMException('aborted', 'AbortError'));
  463. init.method = request.method;
  464. init.headers = request.headers;
  465. init.body = request.data;
  466. realFetch.call(win, request.url, init).then(response => {
  467. if (typeof request.response === 'function') {
  468. req.response = {
  469. finalUrl: response.url,
  470. status: response.status,
  471. responseHeaders: parseHeaders(response.headers)
  472. };
  473. hookFetchResponse(response, req);
  474. response.clone = () => {
  475. const resClone = resProto.clone.call(response);
  476. hookFetchResponse(resClone, req);
  477. return resClone;
  478. };
  479. }
  480. resolve(response);
  481. }, reject);
  482. }).catch(err => {
  483. console.error(err);
  484. resolve(realFetch.call(win, url, init));
  485. });
  486. });
  487. }
  488. win.XMLHttpRequest = fakeXhr;
  489. Object.keys(realXhr).forEach(key => fakeXhr[key] = realXhr[key]);
  490. fakeXhr.prototype = realXhr.prototype;
  491. win.fetch = fakeFetch;
  492. return {
  493. hook: fn => hookFns.push(fn),
  494. filter: arr => {
  495. filter = Array.isArray(arr) && arr;
  496. },
  497. protect: () => {
  498. readonly(win, 'XMLHttpRequest', fakeXhr);
  499. readonly(win, 'fetch', fakeFetch);
  500. },
  501. unhook: () => {
  502. writable(win, 'XMLHttpRequest', realXhr);
  503. writable(win, 'fetch', realFetch);
  504. }
  505. };
  506. }();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址