// ==UserScript== // @name 请求监听过滤器 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 监听所有HTTP和HTTPS请求 // @author 晚风 // @match http://*/* // @match https://*/* // @grant GM_getValue // @grant GM_setValue // @run-at document-start // @inject-into page // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/520573/%E8%AF%B7%E6%B1%82%E7%9B%91%E5%90%AC%E8%BF%87%E6%BB%A4%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/520573/%E8%AF%B7%E6%B1%82%E7%9B%91%E5%90%AC%E8%BF%87%E6%BB%A4%E5%99%A8.meta.js // ==/UserScript== // 立即执行重定向检查,在任何其他代码执行之前 (function() { if (window.location.href.includes('cryptbox.sankuai.com/file/')) { // 阻止页面继续加载 window.stop(); const currentUrl = window.location.href; const newUrl = currentUrl.replace( /^https:\/\/cryptbox\.sankuai\.com\/file\/(.+)$/, 'https://distribute-platform-pub.sankuai.com/distribute/download/v1/$1' ); if (currentUrl !== newUrl) { // 创建一个隐藏的 iframe 来触发下载 const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = newUrl; document.body.appendChild(iframe); // 等待一段时间后关闭页面 setTimeout(() => { window.close(); }, 1000); // 给予1秒时间让下载开始 return; } } })(); class RequestMonitor { constructor() { if (window._RequestMonitor) { return window._RequestMonitor; } window._RequestMonitor = this; // 初始化状态 this.logs = []; this.filterKeyword = ''; this.initialized = false; this.requestCount = { total: 0, xhr: 0, fetch: 0 }; // 重定向规则 this.redirectRules = [ { pattern: /^https:\/\/cryptbox\.sankuai\.com\/file\/(.+)$/, replacement: 'https://distribute-platform-pub.sankuai.com/distribute/download/v1/$1' } ]; // SVG 图标 this.icons = { copy: ` `, success: ` ` }; // 设置默认状态为折叠 this.isPanelExpanded = false; this.init(); } init() { if (this.initialized) return; this.setupRequestInterceptors(); this.createUI(); this.initialized = true; } // UI 相关方法 createUI() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.renderUI(), { once: true }); } else { this.renderUI(); } } renderUI() { // 创建主容器 const container = this.createMainContainer(); // 创建头部 const header = this.createHeader(); container.appendChild(header); // 创建统计栏 const stats = this.createStatsBar(); container.appendChild(stats); // 创建请求列表 const content = this.createRequestList(); container.appendChild(content); // 创建详情面板 this.createDetailPanel(); // 添加到页面 document.body.appendChild(container); } // 创建主容器 createMainContainer() { const container = document.createElement('div'); container.id = 'requestMonitor'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; width: ${this.isPanelExpanded ? '400px' : '40px'}; height: ${this.isPanelExpanded ? '90vh' : '40px'}; background: rgba(255, 255, 255, 0.95); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; backdrop-filter: blur(10px); border: 1px solid rgba(0, 0, 0, 0.1); transition: all 0.3s ease; overflow: hidden; transform-origin: right center; display: ${this.isPanelExpanded ? 'flex' : 'block'}; flex-direction: column; `; // 创建折叠按钮 const toggleButton = document.createElement('div'); toggleButton.style.cssText = ` position: ${this.isPanelExpanded ? 'absolute' : 'relative'}; top: ${this.isPanelExpanded ? '12px' : '0'}; right: ${this.isPanelExpanded ? '12px' : '0'}; width: ${this.isPanelExpanded ? '24px' : '40px'}; height: ${this.isPanelExpanded ? '24px' : '40px'}; background: ${this.isPanelExpanded ? 'rgba(24, 144, 255, 0.1)' : '#1890ff'}; border-radius: ${this.isPanelExpanded ? '4px' : '8px'}; cursor: pointer; display: flex; align-items: center; justify-content: center; color: ${this.isPanelExpanded ? '#1890ff' : 'white'}; font-size: 18px; transition: all 0.3s; z-index: 1; `; toggleButton.innerHTML = this.isPanelExpanded ? '−' : '+'; toggleButton.onmouseover = () => { toggleButton.style.background = this.isPanelExpanded ? 'rgba(24, 144, 255, 0.2)' : 'rgba(24, 144, 255, 0.8)'; }; toggleButton.onmouseout = () => { toggleButton.style.background = this.isPanelExpanded ? 'rgba(24, 144, 255, 0.1)' : '#1890ff'; }; toggleButton.onclick = (e) => { e.stopPropagation(); this.togglePanel(!this.isPanelExpanded); }; container.appendChild(toggleButton); return container; } // 创建头部 createHeader() { const header = document.createElement('div'); header.style.cssText = ` padding: 12px 16px; padding-right: 48px; background: rgba(245, 245, 245, 0.95); border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; border-bottom: 1px solid rgba(0, 0, 0, 0.05); `; // 标题 const title = document.createElement('span'); title.textContent = `${document.title} - 请求监听器`; title.style.cssText = ` font-weight: 600; color: #333; font-size: 14px; `; // 搜索 const filterInput = document.createElement('input'); filterInput.placeholder = '搜索请求...'; filterInput.style.cssText = ` margin-left: 12px; padding: 6px 12px; border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 4px; flex-grow: 1; font-size: 12px; outline: none; transition: all 0.3s; background: rgba(255, 255, 255, 0.9); `; // 添加拖拽功能 this.setupDrag(header); // 添加事件监听 filterInput.oninput = () => { this.filterKeyword = filterInput.value.toLowerCase(); this.updateList(); }; header.appendChild(title); header.appendChild(filterInput); return header; } // 创建统计栏 createStatsBar() { const stats = document.createElement('div'); stats.id = 'requestStats'; stats.style.cssText = ` padding: 8px 16px; background: rgba(250, 250, 250, 0.95); border-bottom: 1px solid rgba(0, 0, 0, 0.05); font-size: 12px; color: #666; display: flex; gap: 16px; `; stats.innerHTML = ` 总请求: 0 XHR: 0 Fetch: 0 `; return stats; } // 创建请求列表 createRequestList() { const content = document.createElement('div'); content.style.cssText = ` flex: 1; overflow-y: auto; display: ${this.isPanelExpanded ? 'block' : 'none'}; background: rgba(255, 255, 255, 0.95); padding: 0; `; const requestList = document.createElement('div'); requestList.id = 'requestList'; content.appendChild(requestList); return content; } // 创建详情面板 createDetailPanel() { const detailPanel = document.createElement('div'); detailPanel.id = 'requestDetail'; detailPanel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 800px; max-height: 80vh; background: rgba(255, 255, 255, 0.98); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 1000000; display: none; overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.1); backdrop-filter: blur(10px); `; const header = document.createElement('div'); header.style.cssText = ` padding: 16px; background: rgba(245, 245, 245, 0.95); border-bottom: 1px solid rgba(0, 0, 0, 0.05); display: flex; justify-content: space-between; align-items: center; `; const title = document.createElement('h3'); title.style.cssText = ` margin: 0; font-size: 16px; color: #333; `; title.textContent = '请求详情'; const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 24px; color: #999; cursor: pointer; padding: 0 8px; line-height: 1; `; const content = document.createElement('div'); content.style.cssText = ` padding: 16px; overflow-y: auto; max-height: calc(80vh - 60px); `; closeBtn.onclick = () => detailPanel.style.display = 'none'; header.appendChild(title); header.appendChild(closeBtn); detailPanel.appendChild(header); detailPanel.appendChild(content); document.body.appendChild(detailPanel); } // 设置拖拽功能 setupDrag(header) { let isDragging = false; let currentX; let currentY; let initialX; let initialY; header.onmousedown = (e) => { isDragging = true; const container = document.getElementById('requestMonitor'); const rect = container.getBoundingClientRect(); initialX = e.clientX - rect.left; initialY = e.clientY - rect.top; }; document.onmousemove = (e) => { if (isDragging) { const container = document.getElementById('requestMonitor'); const viewportWidth = window.innerWidth; // 算新位置 currentX = e.clientX - initialX; currentY = e.clientY - initialY; // 计算距离右侧的距离 const rightDistance = viewportWidth - (currentX + container.offsetWidth); // 更新位置,使用 right 而不是 left container.style.right = `${rightDistance}px`; container.style.top = `${currentY}px`; container.style.left = 'auto'; } }; document.onmouseup = () => isDragging = false; } // 请求拦截相关方法 setupRequestInterceptors() { this.interceptXHR(); this.interceptFetch(); } // XHR拦截实现 interceptXHR() { const originalXHR = XMLHttpRequest.prototype; const originalOpen = originalXHR.open; const originalSend = originalXHR.send; const self = this; originalXHR.open = function(method, url) { if (self.shouldSkipRequest(url)) return; this._requestData = { method, url: url instanceof URL ? url.href : url, status: null, response: null, type: 'xhr', requestData: null, urlParams: self.getUrlParams(url), timestamp: new Date().toLocaleTimeString() }; return originalOpen.apply(this, arguments); }; originalXHR.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; } } this.addEventListener('load', () => { this._requestData.status = this.status; try { this._requestData.response = this.responseText; } catch (e) { this._requestData.response = '[无法读取响应内容]'; } self.logs.push(this._requestData); self.requestCount.total++; self.requestCount.xhr++; self.updateStats(); self.updateList(); }); } return originalSend.apply(this, arguments); }; } // Fetch拦截实现 interceptFetch() { const originalFetch = window.fetch; const self = this; window.fetch = async function(input, init = {}) { const url = input instanceof Request ? input.url : input; if (self.shouldSkipRequest(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: self.getUrlParams(url), timestamp: new Date().toLocaleTimeString() }; 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 = '[无法读取响应内容]'; } self.logs.push(logEntry); self.requestCount.total++; self.requestCount.fetch++; self.updateStats(); self.updateList(); return response; } catch (error) { logEntry.status = 'ERROR'; logEntry.response = error.message; self.logs.push(logEntry); self.updateStats(); self.updateList(); throw error; } }; } // 更新统计信息 updateStats() { const totalEl = document.getElementById('totalCount'); const xhrEl = document.getElementById('xhrCount'); const fetchEl = document.getElementById('fetchCount'); if (totalEl) totalEl.textContent = this.requestCount.total; if (xhrEl) xhrEl.textContent = this.requestCount.xhr; if (fetchEl) fetchEl.textContent = this.requestCount.fetch; } // 更新请求列表 updateList() { const requestList = document.getElementById('requestList'); if (!requestList) return; 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; requestList.innerHTML = ''; filteredLogs.forEach(log => this.createRequestItem(log, requestList)); } // 创建请求列表项 createRequestItem(log, container) { const item = document.createElement('div'); item.style.cssText = ` padding: 12px 16px; border-bottom: 1px solid rgba(0, 0, 0, 0.05); cursor: pointer; font-size: 12px; transition: all 0.2s; position: relative; background: rgba(255, 255, 255, 0.95); `; item.onmouseover = () => { item.style.backgroundColor = 'rgba(24, 144, 255, 0.05)'; }; item.onmouseout = () => { item.style.backgroundColor = 'rgba(255, 255, 255, 0.95)'; }; const statusColor = log.status >= 200 && log.status < 300 ? '#52c41a' : '#f5222d'; item.innerHTML = `
${log.method} ${log.status || 'pending'} ${log.timestamp}
${log.url}
`; item.onclick = () => this.showRequestDetail(log); container.appendChild(item); } // 工具方法 shouldSkipRequest(url) { return 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='); } 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(url) { for (const rule of this.redirectRules) { if (rule.pattern.test(url)) { const newUrl = url.replace(rule.pattern, rule.replacement); console.log('重定向到:', newUrl); window.location.replace(newUrl); return true; } } return false; } // 显示请求详情 showRequestDetail(log) { const detailPanel = document.getElementById('requestDetail'); const detailContent = detailPanel.querySelector('div:last-child'); // 创建复制按钮 const createCopyButton = (text) => { const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` position: absolute; right: 8px; top: 8px; `; const button = document.createElement('button'); button.innerHTML = this.icons.copy; button.style.cssText = ` padding: 4px 8px; background: rgba(24, 144, 255, 0.1); border: 1px solid rgba(24, 144, 255, 0.2); border-radius: 4px; color: #1890ff; cursor: pointer; font-size: 14px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; opacity: 0.8; `; button.onmouseover = () => { button.style.background = 'rgba(24, 144, 255, 0.2)'; button.style.opacity = '1'; }; button.onmouseout = () => { button.style.background = 'rgba(24, 144, 255, 0.1)'; button.style.opacity = '0.8'; }; button.onclick = (e) => { e.stopPropagation(); navigator.clipboard.writeText(text).then(() => { button.innerHTML = this.icons.success; setTimeout(() => button.innerHTML = this.icons.copy, 1000); }); }; buttonContainer.appendChild(button); return buttonContainer; }; // 格式化数据显示 const formatData = (data, type = '') => { try { if (!data) return ''; let formattedData = data; if (typeof data === 'string') { try { formattedData = JSON.stringify(JSON.parse(data), null, 2); } catch { if (data.trim().startsWith('<')) { formattedData = data.replace(//g, '>'); } } } else { formattedData = JSON.stringify(data, null, 2); } const container = document.createElement('div'); container.style.cssText = 'margin-bottom: 16px; position: relative;'; const title = document.createElement('div'); title.style.cssText = 'font-weight: 600; color: #1890ff; margin-bottom: 4px;'; title.textContent = `${type}:`; const pre = document.createElement('pre'); pre.style.cssText = 'margin: 0; padding: 8px; background: rgba(0,0,0,0.02); border-radius: 4px; overflow-x: auto; max-height: 400px;'; pre.textContent = formattedData; const copyButton = createCopyButton(formattedData); container.appendChild(title); container.appendChild(pre); pre.appendChild(copyButton); return container; } catch (e) { console.warn(`格式化${type}失败:`, e); return document.createElement('div'); } }; // 清空现内容 detailContent.innerHTML = ''; // 创建基本信息区 const basicInfo = document.createElement('div'); basicInfo.style.cssText = 'margin-bottom: 16px;'; basicInfo.innerHTML = `
方法: ${log.method}
状态: ${log.status || 'pending'}
`; // URL 部分 const urlContainer = document.createElement('div'); urlContainer.style.cssText = 'margin-bottom: 8px; position: relative;'; const urlTitle = document.createElement('div'); urlTitle.style.cssText = 'font-weight: 600; color: #1890ff; margin-bottom: 4px;'; urlTitle.textContent = 'URL:'; const urlContent = document.createElement('div'); urlContent.style.cssText = 'word-break: break-all; padding: 8px; background: rgba(0,0,0,0.02); border-radius: 4px;'; urlContent.textContent = log.url; urlContainer.appendChild(urlTitle); urlContainer.appendChild(urlContent); urlContent.appendChild(createCopyButton(log.url)); // 添加所有内容 detailContent.appendChild(basicInfo); detailContent.appendChild(urlContainer); // URL参数 if (log.urlParams) { detailContent.appendChild(formatData(log.urlParams, 'URL参数')); } // 请求数据 if (log.requestData) { detailContent.appendChild(formatData(log.requestData, '请求数据')); } // 响应数据 if (log.response) { detailContent.appendChild(formatData(log.response, '响应数据')); } detailPanel.style.display = 'block'; } // 修改 togglePanel 方法 togglePanel(expand) { this.isPanelExpanded = expand; const container = document.getElementById('requestMonitor'); const toggleButton = container.querySelector('div:first-child'); const content = container.querySelector('#requestList')?.parentElement; const stats = container.querySelector('#requestStats'); const header = container.querySelector('div:nth-child(2)'); // 更新容器样式 container.style.width = expand ? '400px' : '40px'; container.style.height = expand ? '90vh' : '40px'; container.style.display = expand ? 'flex' : 'block'; // 更新按钮样式 toggleButton.style.position = expand ? 'absolute' : 'relative'; toggleButton.style.top = expand ? '12px' : '0'; toggleButton.style.right = expand ? '12px' : '0'; toggleButton.style.width = expand ? '24px' : '40px'; toggleButton.style.height = expand ? '24px' : '40px'; toggleButton.style.background = expand ? 'rgba(24, 144, 255, 0.1)' : '#1890ff'; toggleButton.style.borderRadius = expand ? '4px' : '8px'; toggleButton.style.color = expand ? '#1890ff' : 'white'; toggleButton.innerHTML = expand ? '−' : '+'; // 显示/隐藏其他元素 if (header) header.style.display = expand ? 'flex' : 'none'; if (stats) stats.style.display = expand ? 'flex' : 'none'; if (content) content.style.display = expand ? 'block' : 'none'; // 展开时重新计算内容高度 if (expand) { // 触发内容重新渲染 this.updateList(); } } } // 只有在不需要重定向时才初始化监控器 if (!window.location.href.includes('cryptbox.sankuai.com/file/')) { new RequestMonitor(); }