您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Capture and save JSON responses from web requests with URL filtering
// ==UserScript== // @name JSON Response Capture // @namespace http://tampermonkey.net/ // @version 1.3 // @description Capture and save JSON responses from web requests with URL filtering // @author nickm8 // @license MIT // @match *://*/* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js // ==/UserScript== (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` :root { --bg-primary: #ffffff; --bg-secondary: #f9fafb; --text-primary: #1f2937; --text-secondary: #4b5563; --text-tertiary: #6b7280; --border-color: #e5e7eb; --shadow-color: rgba(0, 0, 0, 0.1); --hover-color: #2563eb; --danger-color: #dc2626; --tag-bg: #f3f4f6; } @media (prefers-color-scheme: dark) { :root { --bg-primary: #1f2937; --bg-secondary: #374151; --text-primary: #f9fafb; --text-secondary: #d1d5db; --text-tertiary: #9ca3af; --border-color: #4b5563; --shadow-color: rgba(0, 0, 0, 0.3); --hover-color: #60a5fa; --danger-color: #ef4444; --tag-bg: #374151; } } .json-capture-panel { position: fixed; bottom: 1rem; right: 1rem; background: var(--bg-primary); color: var(--text-primary); border-radius: 0.5rem; box-shadow: 0 4px 6px -1px var(--shadow-color); transition: all 200ms; z-index: 10000; } .json-capture-panel.minimized { width: 3rem; } .json-capture-panel.expanded { width: 24rem; } .json-capture-header { display: flex; align-items: center; justify-content: space-between; padding: 0.5rem; border-bottom: 1px solid var(--border-color); background: var(--bg-secondary); border-top-left-radius: 0.5rem; border-top-right-radius: 0.5rem; } .json-capture-content { max-height: 24rem; overflow: auto; padding: 1rem; } .json-capture-item { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 0.375rem; } .json-capture-url { font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 0.25rem; word-break: break-all; } .json-capture-timestamp { font-size: 0.75rem; color: var(--text-tertiary); margin-bottom: 0.5rem; } .json-capture-json { font-size: 0.75rem; background: var(--bg-secondary); padding: 0.5rem; border-radius: 0.375rem; overflow: auto; white-space: pre-wrap; max-height: 12rem; color: var(--text-primary); } .json-capture-button { padding: 0.25rem 0.5rem; background: none; border: none; cursor: pointer; color: var(--text-secondary); } .json-capture-button:hover { color: var(--hover-color); } .json-capture-button.delete:hover { color: var(--danger-color); } .config-section { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 0.375rem; } .config-title { font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary); } .config-input { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; } .config-input input { flex: 1; padding: 0.25rem 0.5rem; border: 1px solid var(--border-color); border-radius: 0.25rem; background: var(--bg-primary); color: var(--text-primary); } .config-list { display: flex; flex-wrap: wrap; gap: 0.25rem; } .config-tag { background: var(--tag-bg); color: var(--text-primary); padding: 0.25rem 0.5rem; border-radius: 0.25rem; display: flex; align-items: center; gap: 0.25rem; } .config-tag button { padding: 0; background: none; border: none; cursor: pointer; color: var(--text-tertiary); } .tabs { display: flex; border-bottom: 1px solid var(--border-color); margin-bottom: 1rem; } .tab { padding: 0.5rem 1rem; cursor: pointer; border-bottom: 2px solid transparent; color: var(--text-secondary); } .tab.active { border-bottom-color: var(--hover-color); color: var(--hover-color); } `; document.head.appendChild(style); const { useState, useEffect } = React; // Configuration component function ConfigSection({ matches, ignores, onUpdateMatches, onUpdateIgnores }) { const [newMatch, setNewMatch] = useState(''); const [newIgnore, setNewIgnore] = useState(''); const addMatch = () => { if (newMatch && !matches.includes(newMatch)) { onUpdateMatches([...matches, newMatch]); setNewMatch(''); } }; const addIgnore = () => { if (newIgnore && !ignores.includes(newIgnore)) { onUpdateIgnores([...ignores, newIgnore]); setNewIgnore(''); } }; const removeMatch = (match) => { onUpdateMatches(matches.filter(m => m !== match)); }; const removeIgnore = (ignore) => { onUpdateIgnores(ignores.filter(i => i !== ignore)); }; return React.createElement('div', { className: 'config-content' }, [ React.createElement('div', { key: 'matches', className: 'config-section' }, [ React.createElement('div', { className: 'config-title' }, 'URL Matches'), React.createElement('div', { className: 'config-input' }, [ React.createElement('input', { value: newMatch, onChange: (e) => setNewMatch(e.target.value), placeholder: 'Enter URL keyword to match', onKeyPress: (e) => e.key === 'Enter' && addMatch() }), React.createElement('button', { className: 'json-capture-button', onClick: addMatch }, '+') ]), React.createElement('div', { className: 'config-list' }, matches.map(match => React.createElement('span', { key: match, className: 'config-tag' }, [ match, React.createElement('button', { onClick: () => removeMatch(match) }, '×') ]) ) ) ]), React.createElement('div', { key: 'ignores', className: 'config-section' }, [ React.createElement('div', { className: 'config-title' }, 'URL Ignores'), React.createElement('div', { className: 'config-input' }, [ React.createElement('input', { value: newIgnore, onChange: (e) => setNewIgnore(e.target.value), placeholder: 'Enter URL keyword to ignore', onKeyPress: (e) => e.key === 'Enter' && addIgnore() }), React.createElement('button', { className: 'json-capture-button', onClick: addIgnore }, '+') ]), React.createElement('div', { className: 'config-list' }, ignores.map(ignore => React.createElement('span', { key: ignore, className: 'config-tag' }, [ ignore, React.createElement('button', { onClick: () => removeIgnore(ignore) }, '×') ]) ) ) ]) ]); } function JsonCapturePanel() { const [captures, setCaptures] = useState([]); const [isMinimized, setIsMinimized] = useState(true); const [activeTab, setActiveTab] = useState('captures'); const [matches, setMatches] = useState([]); const [ignores, setIgnores] = useState([]); const domain = window.location.hostname; // Load configuration from localStorage useEffect(() => { const config = JSON.parse(localStorage.getItem(`jsonCapture_${domain}`) || '{"matches":[],"ignores":[]}'); setMatches(config.matches); setIgnores(config.ignores); }, []); // Save configuration to localStorage useEffect(() => { localStorage.setItem(`jsonCapture_${domain}`, JSON.stringify({ matches, ignores })); }, [matches, ignores]); const shouldCaptureUrl = (url) => { if (matches.length === 0) return false; return matches.some(match => url.includes(match)) && !ignores.some(ignore => url.includes(ignore)); }; useEffect(() => { // Intercept fetch const originalFetch = window.fetch; window.fetch = async function(...args) { const response = await originalFetch.apply(this, args); const clone = response.clone(); try { const contentType = clone.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { const url = typeof args[0] === 'string' ? args[0] : args[0].url; if (shouldCaptureUrl(url)) { const json = await clone.json(); setCaptures(prev => [...prev, { timestamp: new Date().toISOString(), url, data: json }]); } } } catch (err) { console.error('Error processing response:', err); } return response; }; // Intercept XHR const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(...args) { this._url = args[1]; return originalXHROpen.apply(this, args); }; XMLHttpRequest.prototype.send = function(...args) { this.addEventListener('load', function() { try { const contentType = this.getResponseHeader('content-type'); if (contentType && contentType.includes('application/json')) { if (shouldCaptureUrl(this._url)) { const json = JSON.parse(this.responseText); setCaptures(prev => [...prev, { timestamp: new Date().toISOString(), url: this._url, data: json }]); } } } catch (err) { console.error('Error processing XHR response:', err); } }); return originalXHRSend.apply(this, args); }; }, [matches, ignores]); const handleSave = () => { const blob = new Blob([JSON.stringify(captures, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `json-captures-${domain}-${new Date().toISOString()}.json`; a.click(); URL.revokeObjectURL(url); }; const handleClear = () => { setCaptures([]); }; return React.createElement('div', { className: `json-capture-panel ${isMinimized ? 'minimized' : 'expanded'}` }, [ // Header React.createElement('div', { key: 'header', className: 'json-capture-header' }, [ !isMinimized && React.createElement('div', { key: 'title' }, `JSON Captures (${captures.length})`), React.createElement('div', { key: 'controls', style: { display: 'flex', gap: '0.5rem' } }, [ !isMinimized && React.createElement('button', { key: 'save', className: 'json-capture-button', onClick: handleSave, title: 'Save captures' }, '💾'), React.createElement('button', { key: 'toggle', className: 'json-capture-button', onClick: () => setIsMinimized(!isMinimized), title: isMinimized ? 'Expand' : 'Minimize' }, isMinimized ? '⤢' : '⤡'), !isMinimized && React.createElement('button', { key: 'clear', className: 'json-capture-button delete', onClick: handleClear, title: 'Clear captures' }, '✕') ]) ]), // Content !isMinimized && React.createElement('div', { key: 'content' }, [ // Tabs React.createElement('div', { key: 'tabs', className: 'tabs' }, [ React.createElement('div', { className: `tab ${activeTab === 'captures' ? 'active' : ''}`, onClick: () => setActiveTab('captures') }, 'Captures'), React.createElement('div', { className: `tab ${activeTab === 'config' ? 'active' : ''}`, onClick: () => setActiveTab('config') }, 'Configuration') ]), // Tab content activeTab === 'captures' ? React.createElement('div', { key: 'captures', className: 'json-capture-content' }, captures.length === 0 ? React.createElement('div', { style: { textAlign: 'center', color: '#6b7280' } }, matches.length === 0 ? 'Configure URL matches to start capturing' : 'No JSON responses captured yet') : captures.map((capture, index) => React.createElement('div', { key: index, className: 'json-capture-item' }, [ React.createElement('div', { key: 'url', className: 'json-capture-url' }, capture.url), React.createElement('div', { key: 'timestamp', className: 'json-capture-timestamp' }, capture.timestamp), React.createElement('pre', { key: 'json', className: 'json-capture-json' }, JSON.stringify(capture.data, null, 2)) ]) ) ) : React.createElement(ConfigSection, { key: 'config', matches, ignores, onUpdateMatches: setMatches, onUpdateIgnores: setIgnores }) ]) ]); } const container = document.createElement('div'); document.body.appendChild(container); ReactDOM.render(React.createElement(JsonCapturePanel), container); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址