// ==UserScript==
// @name 请求监听过滤器
// @namespace http://tampermonkey.net/
// @version 2024-12-12
// @description 监听所有HTTP和HTTPS请求
// @author 晚风
// @match http://*/*
// @match https://*/*
// @grant GM_getValue
// @license MIT
// @grant GM_setValue
// @run-at document-start
// @downloadURL none
// ==/UserScript==
// 使用立即执行函数确保单例
const RequestMonitor = (function() {
class Monitor {
constructor() {
if (Monitor.instance) {
return Monitor.instance;
}
Monitor.instance = this;
this.logs = [];
this.filterKeyword = '';
this.initialized = false;
this.requestCount = {
total: 0,
xhr: 0,
fetch: 0
};
this.displayMode = 'json';
// 添加重定向规则
this.redirectRules = [
{
pattern: /^https:\/\/cryptbox\.sankuai\.com\/file\/(.+)$/,
replacement: 'https://distribute-platform-pub.sankuai.com/distribute/download/v1/$1'
}
];
this.init();
}
init() {
if (this.initialized) {
return;
}
this.setupListeners();
this.initUI();
this.initialized = true;
console.log('监听器初始化完成');
}
initUI() {
const createUI = () => {
if (document.getElementById('requestMonitor')) {
return;
}
const container = document.createElement('div');
container.id = 'requestMonitor';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 400px;
background: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 999999;
font-family: Arial, sans-serif;
`;
const header = document.createElement('div');
header.style.cssText = `
padding: 8px;
background: #f5f5f5;
border-radius: 4px 4px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
user-select: none;
`;
const title = document.createElement('span');
title.textContent = `${document.title} - 请求监听器`;
title.style.fontWeight = 'bold';
const stats = document.createElement('div');
stats.id = 'requestStats';
stats.style.cssText = `
padding: 4px 8px;
background: #fff;
border-bottom: 1px solid #eee;
font-size: 12px;
`;
stats.innerHTML = `
总请求: 0 |
XHR: 0 |
Fetch: 0
`;
const filterInput = document.createElement('input');
filterInput.placeholder = '搜索...';
filterInput.style.cssText = `
margin: 0 8px;
padding: 4px;
border: 1px solid #ddd;
border-radius: 2px;
flex-grow: 1;
`;
const collapseBtn = document.createElement('span');
collapseBtn.textContent = '+';
collapseBtn.style.cssText = `
cursor: pointer;
padding: 0 8px;
font-size: 16px;
`;
const content = document.createElement('div');
content.style.maxHeight = '600px';
content.style.overflowY = 'auto';
content.style.display = 'none';
const requestList = document.createElement('div');
requestList.id = 'requestList';
header.appendChild(title);
header.appendChild(filterInput);
header.appendChild(collapseBtn);
content.appendChild(requestList);
container.appendChild(header);
container.appendChild(stats);
container.appendChild(content);
document.body.appendChild(container);
// 折叠按钮
collapseBtn.textContent = '+';
collapseBtn.onclick = (e) => {
e.stopPropagation();
content.style.display = content.style.display === 'none' ? 'block' : 'none';
collapseBtn.textContent = content.style.display === 'none' ? '+' : '−';
};
// 拖拽功能
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
header.onmousedown = (e) => {
isDragging = true;
initialX = e.clientX - container.offsetLeft;
initialY = e.clientY - container.offsetTop;
};
document.onmousemove = (e) => {
if (isDragging) {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
container.style.left = currentX + 'px';
container.style.top = currentY + 'px';
container.style.right = 'auto';
}
};
document.onmouseup = () => isDragging = false;
// 过滤功能
filterInput.oninput = () => {
this.filterKeyword = filterInput.value.toLowerCase();
this.updateList();
};
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createUI, { once: true });
} else {
createUI();
}
}
updateList() {
const requestList = document.getElementById('requestList');
if (!requestList) return;
requestList.innerHTML = '';
const filteredLogs = this.filterKeyword ?
this.logs.filter(log => {
const searchStr = this.filterKeyword.toLowerCase();
return (
log.url.toLowerCase().includes(searchStr) ||
(log.response && log.response.toLowerCase().includes(searchStr))
);
}) :
this.logs;
this.updateStats(filteredLogs);
filteredLogs.forEach(log => {
const item = document.createElement('div');
item.style.cssText = `
padding: 8px;
border-bottom: 1px solid #eee;
cursor: pointer;
font-size: 12px;
`;
item.innerHTML = `
${log.method} ${log.status || ''}
${log.url}
${new Date().toLocaleTimeString()}
`;
item.ondblclick = () => {
console.group('请求详情');
console.log('URL:', log.url);
console.log('方法:', log.method);
console.log('状态:', log.status);
// 显示URL参数
if (log.urlParams) {
console.group('URL参数:');
console.log(log.urlParams);
console.groupEnd();
}
// 显示请求数据
if (log.requestData) {
console.group('请求数据:');
try {
// 如果是字符串,尝试解析为对象
if (typeof log.requestData === 'string') {
console.log(JSON.parse(log.requestData));
} else {
console.log(log.requestData);
}
} catch (e) {
// 如果解析失败,直接显示原始数据
console.log(log.requestData);
}
console.groupEnd();
}
// 显示响应数据
if (log.response) {
console.group('响应数据:');
try {
console.log(JSON.parse(log.response));
} catch (e) {
console.log(log.response);
}
console.groupEnd();
}
console.groupEnd();
};
requestList.appendChild(item);
});
}
updateStats(filteredLogs = null) {
const totalEl = document.getElementById('totalCount');
const xhrEl = document.getElementById('xhrCount');
const fetchEl = document.getElementById('fetchCount');
if (filteredLogs) {
// 如果提供了过滤后的日志,计算过滤后的统计
const stats = {
total: filteredLogs.length,
xhr: filteredLogs.filter(log => log.type === 'xhr').length,
fetch: filteredLogs.filter(log => log.type === 'fetch').length
};
if (totalEl) totalEl.textContent = stats.total;
if (xhrEl) xhrEl.textContent = stats.xhr;
if (fetchEl) fetchEl.textContent = stats.fetch;
} else {
// 否则显示总统计
if (totalEl) totalEl.textContent = this.requestCount.total;
if (xhrEl) xhrEl.textContent = this.requestCount.xhr;
if (fetchEl) fetchEl.textContent = this.requestCount.fetch;
}
}
setupListeners() {
// 拦截原生XHR
const XHR = XMLHttpRequest.prototype;
const originalOpen = XHR.open;
const originalSend = XHR.send;
XHR.open = function(method, url) {
// 检查是否需要拦截
if (url.includes('cryptbox.sankuai.com/api_sdk/file_download/url_generate?current_url=') ||
url.includes('cryptbox.sankuai.com/api_sdk/wenshu_url/judge_and_generate?current_url=')) {
// 需要拦截的请求,直接拦截
return;
}
this._requestData = {
method,
url: url instanceof URL ? url.href : url,
status: null,
response: null,
type: 'xhr',
requestData: null,
urlParams: Monitor.instance.getUrlParams(url)
};
return originalOpen.apply(this, arguments);
};
XHR.send = function(data) {
if (this._requestData) {
// 保存请求数据
if (data) {
try {
this._requestData.requestData = typeof data === 'string' ?
JSON.parse(data) : data;
} catch (e) {
this._requestData.requestData = data;
}
}
const xhr = this;
xhr.addEventListener('load', () => {
xhr._requestData.status = xhr.status;
try {
xhr._requestData.response = xhr.responseText;
} catch (e) {
xhr._requestData.response = '[无法读取响应内容]';
}
Monitor.instance.logs.push(xhr._requestData);
Monitor.instance.requestCount.total++;
Monitor.instance.requestCount.xhr++;
Monitor.instance.updateStats();
Monitor.instance.updateList();
});
}
return originalSend.apply(this, arguments);
};
// 拦截Fetch
const originalFetch = window.fetch;
window.fetch = async function(input, init = {}) {
const url = input instanceof Request ? input.url : input;
// 检查是否需要拦截
if (url.toString().includes('cryptbox.sankuai.com/api_sdk/file_download/url_generate?current_url=') ||
url.toString().includes('cryptbox.sankuai.com/api_sdk/wenshu_url/judge_and_generate?current_url=')) {
// 如果是需要拦截的请求,直接拦截
return;
}
const method = init.method || (input instanceof Request ? input.method : 'GET');
const logEntry = {
method,
url: url instanceof URL ? url.href : url,
status: null,
response: null,
type: 'fetch',
requestData: init.body || null,
urlParams: Monitor.instance.getUrlParams(url)
};
try {
const response = await originalFetch.apply(this, arguments);
const clone = response.clone();
logEntry.status = clone.status;
try {
const responseText = await clone.text();
logEntry.response = responseText;
} catch (e) {
logEntry.response = '[无法读取响应内容]';
}
Monitor.instance.logs.push(logEntry);
Monitor.instance.requestCount.total++;
Monitor.instance.requestCount.fetch++;
Monitor.instance.updateStats();
Monitor.instance.updateList();
return response;
} catch (error) {
logEntry.status = 'ERROR';
logEntry.response = error.message;
Monitor.instance.logs.push(logEntry);
Monitor.instance.updateStats();
Monitor.instance.updateList();
throw error;
}
};
}
getUrlParams(url) {
try {
const urlObj = new URL(url);
const params = {};
for (const [key, value] of urlObj.searchParams) {
params[key] = value;
}
return Object.keys(params).length > 0 ? params : null;
} catch (e) {
return null;
}
}
// 添加 handleRedirect 方法
handleRedirect(url) {
for (const rule of this.redirectRules) {
if (rule.pattern.test(url)) {
const newUrl = url.replace(rule.pattern, rule.replacement);
// 新窗口打开
window.open(newUrl, '_blank');
return true;
}
}
return false; // 没有匹配的重定向规则
}
}
// 创建单例并保存到全局
window._RequestMonitor = new Monitor();
return window._RequestMonitor;
})();