// ==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; })();