文章目录
1. 脚本元信息(Metadata)
// ==UserScript==
// @name 租租车
// @namespace tacatman@gmail.com/scripts
// @version 1.0
// @description 监听所有 Ajax 请求和响应,并在屏幕上渲染一个按钮,允许下载(按请求分组)。以 Mockserver 格式导出。
// @author newbieFPF
// @license GPL
// @match *://w.zuzuche.com/*
// @grant none
// @run-at document-start
// @icon https://global.zuzuche.com/assets/images/common/zzc-logo-0119.png?1677569026
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js
// ==/UserScript==
- @name: 脚本的名称,这里是“租租车”。
- @namespace: 脚本的命名空间,用于唯一标识脚本。
- @version: 脚本的版本号。
- @description: 脚本的功能描述。该脚本监听所有 Ajax 请求和响应,并在页面上渲染一个按钮,允许用户下载请求数据(按请求分组),并以 Mockserver 格式导出。
- @author: 脚本的作者。
- @license: 脚本的许可证,这里是 GPL。
- @match: 脚本生效的 URL 模式。这里匹配
w.zuzuche.com
域名下的所有页面。 - @grant: 声明脚本需要的特殊权限。这里没有特殊权限需求,因此为
none
。 - @run-at: 脚本的运行时机。
document-start
表示在文档加载的早期阶段运行。 - @icon: 脚本的图标 URL。
- @require: 脚本依赖的外部库。这里引入了
FileSaver.js
,用于实现文件下载功能。
2. 初始化变量
window.requestSnifferForMock = {};
window.requestSnifferForMockSaveAs = saveAs;
var isRequestInProgress = false;
var anotherVariable = [];
var intervalRequest = null;
window.requestSnifferForMock
: 用于存储所有捕获的请求和响应数据。window.requestSnifferForMockSaveAs
: 将FileSaver.js
的saveAs
方法挂载到全局对象,方便后续调用。isRequestInProgress
: 标志位,用于判断当前是否有请求正在进行。anotherVariable
: 用于存储额外的请求数据。intervalRequest
: 用于存储定时器的 ID,方便后续清除。
3. startup
函数
startup
是脚本的核心逻辑,负责监听 Ajax 请求并处理响应。
3.1 exportFile
函数
var exportFile = function (key) {
var blob = new Blob([JSON.stringify(window.requestSnifferForMock[key], null, 2)], {type : 'application/json'});
requestSnifferForMockSaveAs(blob, key + ".json");
};
- 功能:将捕获的请求数据导出为 JSON 文件。
- 参数:
key
是请求的路径(如/list_new_json_5.php
)。 - 实现:
- 使用
JSON.stringify
将数据转换为 JSON 字符串。 - 使用
Blob
创建一个文件对象。 - 调用
FileSaver.js
的saveAs
方法将文件保存到本地。
- 使用
3.2 addButton
函数
var addButton = function(text, func) {
var div = document.createElement('div');
div.setAttribute('style', 'padding: 10px; display: flex;');
var button = document.createElement('button');
button.setAttribute('style', 'flex: 1 0 auto;width: 90px;height: 30px;line-height: 30px;border: none;color: #fff;background-color: #4e7cdd;border-radius: 3px;text-align: center;cursor: pointer;' );
button.addEventListener('click', func, false);
button.innerText = text;
div.appendChild(button);
return div;
};
- 功能:创建一个按钮,并绑定点击事件。
- 参数:
text
: 按钮显示的文本。func
: 按钮点击时触发的函数。
- 实现:
- 创建一个
div
容器和一个button
元素。 - 设置按钮的样式和点击事件。
- 将按钮添加到
div
中并返回。
- 创建一个
3.3 updateDownloaderDiv
函数
var updateDownloaderDiv = function() {
const element = document.getElementById('requestSnifferForMockDiv');
if (Object.keys(window.requestSnifferForMock).length > 0) {
element.innerHTML = "";
Object.keys(window.requestSnifferForMock).forEach((t) => {
element.appendChild(addButton(' 发送请求('+ window.requestSnifferForMock[t].length +') ', exportFile.bind(undefined, t)));
});
} else {
element.innerHTML = "loading...";
}
};
- 功能:更新页面上的按钮容器,显示捕获的请求数据。
- 实现:
- 获取按钮容器的 DOM 元素。
- 如果捕获到请求数据,为每个请求路径创建一个按钮,并绑定导出功能。
- 如果没有数据,显示“loading…”。
3.4 removeHtmlFromJson
函数
var removeHtmlFromJson = function (jsonData) {
function removeHtml(text) {
var doc = new DOMParser().parseFromString(text, 'text/html');
return doc.body.textContent || "";
}
function removeHtmlRecursive(obj) {
if (typeof obj === 'string') {
return removeHtml(obj);
} else if (Array.isArray(obj)) {
return obj.map(item => removeHtmlRecursive(item));
} else if (typeof obj === 'object' && obj !== null) {
var newObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = removeHtmlRecursive(obj[key]);
}
}
return newObj;
} else {
return obj;
}
}
return removeHtmlRecursive(jsonData);
}
- 功能:递归地移除 JSON 数据中的 HTML 标签。
- 实现:
- 使用
DOMParser
解析字符串并提取纯文本。 - 递归处理数组和对象,确保所有字符串都被清理。
- 使用
3.5 重写 XMLHttpRequest.prototype.open
XMLHttpRequest.prototype.open = function() {
const request = {
method: arguments['0']
};
const url = arguments['1'];
if (url.includes('/list_new_json_5.php')) {
isRequestInProgress = true;
const index = url.indexOf('?');
if (index >= 0) {
request.path = url.substring(0, index);
request.queryStringParameters = url.substring(url.indexOf('?') + 1).split('&').map((t) => t.split('=')).reduce((agg, t) => ({ ...agg, [t[0]] : [t[1]]}) , {});
} else {
request.path = url;
}
this.addEventListener("load", () => {
if (window.requestSnifferForMock[request.path] == null) {
window.requestSnifferForMock[request.path] = [];
}
window.requestSnifferForMock[request.path].push({
httpRequest: request,
httpResponse: {
statusCode: this.status,
body: this.responseType === 'json' ? JSON.stringify(this.response) : this.responseText,
},
});
fetch('https://w.zuzuche.com'+ url, {
method: 'get',
})
.then(response => response.json())
.then(res => {
anotherVariable.push(res);
isRequestInProgress = false;
resetInterval();
})
.catch(error => {
clearInterval(intervalRequest);
console.error(error);
});
updateDownloaderDiv();
});
}
XMLHttpRequestOpen.apply(this, arguments);
};
- 功能:重写
XMLHttpRequest
的open
方法,监听特定请求并捕获响应数据。 - 实现:
- 解析请求的 URL 和方法。
- 如果 URL 包含
/list_new_json_5.php
,则监听请求的load
事件。 - 将请求和响应数据存储到
window.requestSnifferForMock
中。 - 发送一个额外的请求以获取更多数据,并更新按钮容器。
4. 其他功能
4.1 checkRequestStatus
函数
var checkRequestStatus = function() {
if (isRequestInProgress) {
console.log('请求仍在进行中');
} else {
console.log('请求已完成');
if(window.requestSnifferForMock['/list_new_json_5.php'].length > 1){
sendRequest();
}else{
console.error("请求错误");
}
clearInterval(intervalRequest);
}
}
- 功能:检查请求状态,如果请求完成则调用
sendRequest
。
4.2 sendRequest
函数
var sendRequest = function() {
const requestData = anotherVariable[anotherVariable.length-1];
fetch('https://avisapi.56zhiyun.com/export', {
method: 'post',
headers: {
'Content-Type': 'text/plain',
},
body: JSON.stringify(requestData),
})
.then(response => {
console.log('接收响应数据:', response);
if (!response.ok) {
throw new Error('请求出错');
}
return response.text();
})
.then(data => {
const url = data;
if (url) {
window.location.href = url;
} else {
console.error('未找到有效的数据URL');
}
})
.catch(error => {
console.error('接收的数据请求出错:', error);
});
};
- 功能:将捕获的数据发送到指定 API,并处理响应。
4.3 定时器和初始化
var intervalId = setInterval(function () {
if (document.body) {
clearInterval(intervalId);
var div = document.createElement('div');
div.setAttribute('id', 'requestSnifferForMockDiv');
div.setAttribute('style', 'text-align:center;color:#4e7cdd;background-color: yellow;padding:12px 24px;border-radius: 8px; position: fixed; z-index: 9999; top: 120px;');
document.body.appendChild(div);
startup();
}
}, 100);
function resetInterval() {
clearInterval(intervalRequest);
intervalRequest = setInterval(checkRequestStatus, 6000);
}
- 功能:在页面加载完成后初始化脚本,并设置定时器检查请求状态。
总结
该脚本的核心功能是监听特定 Ajax 请求,捕获请求和响应数据,并将其以 Mockserver 格式导出。通过重写 XMLHttpRequest
的 open
方法,实现了对请求的拦截和处理。同时,脚本在页面上动态创建按钮,方便用户下载数据。