Greasy Fork

请求监听过滤器

监听所有HTTP和HTTPS请求

目前为 2024-12-13 提交的版本。查看 最新版本

// ==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
// ==/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 = `
                    总请求: <span id="totalCount">0</span> |
                    XHR: <span id="xhrCount">0</span> |
                    Fetch: <span id="fetchCount">0</span>
                `;

                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 = `
                    <div style="font-weight: bold;">${log.method} ${log.status || ''}</div>
                    <div style="word-break: break-all;">${log.url}</div>
                    <div style="color: #666;">${new Date().toLocaleTimeString()}</div>
                `;

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