Tools
// ==UserScript==
// @name Tools
// @namespace pl816098
// @description paul Tools
// @version 2.2.8.0
// @match *://*/*
// @author paul
// @license MIT
// @grant GM_getValue
// @require https://scriptcat.org/lib/637/%5E1.3.3/ajaxHooker.js
// @run-at document-start
// @grant unsafeWindow
// @supportURL https://github.com/Paul-16098/vs_code/issues/
// @homepageURL https://github.com/Paul-16098/vs_code/blob/main/js/userjs/README.md
// ==/UserScript==
/* ==UserConfig==
公開接口:
ajaxHooker:
title: 公開ajaxHooker接口
description: 公開ajaxHooker接口
type: checkbox
default: false
==/UserConfig== */
const _unsafeWindow =
typeof unsafeWindow === "undefined" ? window : unsafeWindow; //兼容 ios userscripts 的寫法
const ajaxHooker_on = GM_getValue("公開接口.ajaxHooker", false) || false;
let new_run = true,
b_list = GM_getValue("logger.b_list", []) || [];
_unsafeWindow.debug = (type = "log", ...str) => {
if (
!(typeof console[type] === "function") ||
b_list.includes(type.toLocaleLowerCase())
) {
type = "log";
}
try {
console[type](
`%c 【${type}】:\n`,
"font-size: large;font-weight: bold;",
...str
);
} catch (e) {
b_list.push(type);
}
};
/*
https://scriptcat.org/zh-TW/script-show-page/637/
ajaxHooker.hook
核心方法,通過一個回調函數進行劫持,每次請求發生時自動調用回調函數。可以將所有劫持放在同一回調函數中,也可以多次調用hook方法。示例:
ajaxHooker.hook(request => {
console.log(request);
});
參數request是一個對象,其包含以下屬性:
type
只讀屬性。一個字符串,表明請求類型是xhr還是fetch。
async
只讀屬性。異步請求為true,同步請求為false,異步特性無法作用於同步請求。
url
method
請求的url和method,可以直接修改。
abort
是否取消請求,設置為true即可取消本次請求。
headers
請求頭,可以直接修改。
data
請求攜帶的數據,可以直接修改。
response
響應內容,必須通過一個回調函數進行讀取和修改。響應內容為一個對象,包含finalUrl、status、responseHeaders和被讀取的響應數據,除響應數據可修改,其他屬性是隻讀的。響應數據是哪個屬性取決於哪個屬性被讀取,xhr可能的屬性為response、responseText、responseXML,fetch可能的屬性為arrayBuffer、blob、formData、json、text。在控制枱輸出時,xhr響應將包含所有屬性,但只有被讀取過的屬性具有明確的值。修改對應屬性即可影響讀取結果,進而實現響應數據的修改。示例:
ajaxHooker.hook(request => {
if (request.url === 'https://www.example.com/') {
request.response = res => {
console.log(res);
res.responseText += 'test';
};
}
});
異步特性
注意:異步特性無法作用於同步請求,但同步修改仍然有效。
你可以將以上所有可修改屬性賦值為Promise,原請求將被阻塞直至Promise完成(若發生reject,數據將不會被修改),此特性可用於異步劫持。以下是一個異步修改響應數據的例子:
ajaxHooker.hook(request => {
request.response = res => {
const responseText = res.responseText; // 注意保存原數據
res.responseText = new Promise(resolve => {
setTimeout(() => {
resolve(responseText + 'test');
}, 3000);
});
};
});
也可以傳入async回調函數以實現異步:
ajaxHooker.hook(async request => {
request.data = await modifyData(request.data);
request.response = async res => {
res.responseText = await modifyResponse(res.responseText);
};
});
ajaxHooker.filter
應於hook方法之前執行,此方法若儘早執行,有助於提升性能。
為hook方法設置過濾規則,只有符合規則的請求才會觸發hook。過濾規則是一個對象數組,參考下例:
ajaxHooker.filter([
{type: 'xhr', url: 'www.example.com', method: 'GET', async: true},
{url: /^http/},
]);
type 可選,應是xhr或fetch。
url 可選,字符串或正則表達式,無需完全匹配。
method 可選,不區分大小寫。
async 可選,布爾值。
ajaxHooker.protect
如果庫劫持失敗,可能是其他代碼對xhr/fetch進行了二次劫持,protect方法會嘗試阻止xhr和fetch被改寫。應於document-start階段儘早執行,部分網頁下可能引發錯誤,謹慎使用。示例:
ajaxHooker.protect();
ajaxHooker.unhook
將xhr和fetch恢復至劫持前的狀態,調用此方法後,hook方法不再生效。示例:
ajaxHooker.unhook();
*/
if (ajaxHooker_on === true) {
_unsafeWindow.ajaxHooker = ajaxHooker;
} else {
ajaxHooker.unhook();
}
function set_gm() {
set_gm: {
var _GM_xmlhttpRequest,
_GM_registerMenuCommand,
_GM_notification,
_GM_addStyle,
_GM_openInTab,
_GM_info,
_GM_setClipboard;
GM_xmlhttpRequest: {
if (typeof GM_xmlhttpRequest !== "undefined") {
_GM_xmlhttpRequest = GM_xmlhttpRequest;
} else if (
typeof GM !== "undefined" &&
typeof GM.xmlHttpRequest !== "undefined"
) {
_GM_xmlhttpRequest = GM.xmlHttpRequest;
} else {
_GM_xmlhttpRequest = (f) => {
fetch(f.url, {
method: f.method || "GET",
body: f.data,
headers: f.headers,
})
.then((response) => response.text())
.then((data) => {
f.onload && f.onload({ response: data });
})
.catch(f.onerror && f.onerror());
};
}
}
GM_registerMenuCommand: {
if (typeof GM_registerMenuCommand !== "undefined") {
_GM_registerMenuCommand = GM_registerMenuCommand;
} else if (
typeof GM !== "undefined" &&
typeof GM.registerMenuCommand !== "undefined"
) {
_GM_registerMenuCommand = GM.registerMenuCommand;
} else {
_GM_registerMenuCommand = (s, f) => {
debug(s);
debug(f);
};
}
}
GM_info: {
if (typeof GM_info !== "undefined") {
_GM_info = GM_info;
} else if (typeof GM !== "undefined" && typeof GM.info !== "undefined") {
_GM_info = GM.info;
} else {
_GM_info = { script: {} };
}
}
GM_notification: {
if (typeof GM_notification !== "undefined") {
_GM_notification = GM_notification;
} else if (
typeof GM !== "undefined" &&
typeof GM.notification !== "undefined"
) {
_GM_notification = GM.notification;
} else {
_GM_notification = (s) => {
showTips(String(s.text || s));
};
}
}
GM_openInTab: {
if (typeof GM_openInTab !== "undefined") {
_GM_openInTab = GM_openInTab;
} else if (
typeof GM !== "undefined" &&
typeof GM.openInTab !== "undefined"
) {
_GM_openInTab = GM.openInTab;
} else {
_GM_openInTab = (s, t) => {
window.open(s);
debug(t);
};
}
}
GM_addStyle: {
if (typeof GM_addStyle !== "undefined") {
_GM_addStyle = GM_addStyle;
} else if (
typeof GM !== "undefined" &&
typeof GM.addStyle !== "undefined"
) {
_GM_addStyle = GM.addStyle;
} else {
_GM_addStyle = (cssStr = null) => {
let styleEle = document.createElement("style");
styleEle.classList.add("_GM_addStyle");
styleEle.innerHTML = cssStr;
document.head.appendChild(styleEle);
return styleEle;
};
}
}
GM_setClipboard: {
if (typeof GM_setClipboard !== "undefined") {
_GM_setClipboard = GM_setClipboard;
} else if (
typeof GM !== "undefined" &&
typeof GM.setClipboard !== "undefined"
) {
_GM_setClipboard = GM.setClipboard;
} else {
_GM_setClipboard = (s, i) => {
debug(s);
debug(i);
};
}
}
}
}