您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
专为Apifox设计的API接口信息批量提取工具,支持自动监听、数据预览和批量导出
当前为
// ==UserScript== // @name API信息批量提取器 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 专为Apifox设计的API接口信息批量提取工具,支持自动监听、数据预览和批量导出 // @description:en Batch API information extractor tool designed for Apifox, supports auto-monitoring, data preview and batch export // @author xiaoma // @license MIT // @homepage https://github.com/api-extractor/userscript // @supportURL https://github.com/api-extractor/userscript/issues // @match https://app.apifox.com/* // @match https://*.apifox.com/* // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBmaWxsPSIjNDA5RUZGIi8+Cjwvc3ZnPgo= // @grant none // @run-at document-end // @noframes // ==/UserScript== (function() { 'use strict'; // 全局常量定义 const STORAGE_KEY = 'api_extractor_data'; const POSITION_STORAGE_KEY = 'api_extractor_panel_position'; const UI_CONTAINER_ID = 'api-extractor-control-panel'; // 全局状态管理 let extractedApiDataCollection = []; let isAutoExtracting = false; let currentApiUrl = ''; let apiUrlObserver = null; let existDialog = false; /** * 初始化脚本 */ function initScript() { try { loadSavedData(); createUIControlPanel(); console.log('API提取器已启动'); } catch (errorInfo) { console.error('脚本初始化失败:', errorInfo); } } /** * 加载本地存储的数据 */ function loadSavedData() { try { const storedData = localStorage.getItem(STORAGE_KEY); extractedApiDataCollection = storedData ? JSON.parse(storedData) : []; } catch (errorInfo) { console.warn('加载存储数据失败:', errorInfo); extractedApiDataCollection = []; } } /** * 保存数据到本地存储 */ function saveDataToLocalStorage() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(extractedApiDataCollection)); } catch (errorInfo) { console.error('保存数据失败:', errorInfo); } } /** * 创建UI控制面板 */ function createUIControlPanel() { // 检查是否已存在控制面板 if (document.getElementById(UI_CONTAINER_ID)) return; // 获取保存的位置 const savedPosition = getSavedPanelPosition(); const controlPanelContainer = document.createElement('div'); controlPanelContainer.id = UI_CONTAINER_ID; controlPanelContainer.style.cssText = ` position: fixed; top: ${savedPosition.top}px; left: ${savedPosition.left}px; z-index: 9999; background: #fafbfc; border: 1px solid #e1e8ed; border-radius: 6px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; width: 300px; cursor: move; user-select: none; transition: opacity 0.2s; `; // 标题栏 const titleBar = document.createElement('div'); titleBar.style.cssText = ` font-weight: 500; color: #536471; margin-bottom: 8px; text-align: center; font-size: 13px; padding-bottom: 6px; `; titleBar.textContent = 'API提取器'; // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; flex-direction: column; gap: 6px;'; // 提取当前API按钮 const extractButton = createButton('提取当前API', '#9373ee', handleExtractCurrentApi); // 下载全部按钮 const downloadButton = createButton(`下载全部(${extractedApiDataCollection.length})`, '#9373ee', handleDownloadAllData); // 预览数据按钮 const previewButton = createButton('预览数据', '#ef6820', handlePreviewData); // 清空数据按钮 const clearButton = createButton('清空数据', '#dc3545', handleClearAllData); // 自动提取按钮 const autoExtractButton = createButton('自动提取', '#1890ff', handleAutoExtract); // 组装UI buttonContainer.appendChild(extractButton); buttonContainer.appendChild(downloadButton); buttonContainer.appendChild(previewButton); buttonContainer.appendChild(autoExtractButton); buttonContainer.appendChild(clearButton); // 存储按钮引用 window.autoExtractButtonRef = autoExtractButton; // 信息显示区域 const infoArea = document.createElement('div'); infoArea.id = 'api-extractor-info'; infoArea.style.cssText = ` background: #f8f9fa; border: 1px solid #e1e8ed; border-radius: 4px; padding: 8px; margin-top: 8px; margin-bottom: 8px; font-size: 12px; color: #536471; min-height: 20px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; line-height: 1.3; white-space: pre-wrap; word-wrap: break-word; `; infoArea.textContent = '就绪'; controlPanelContainer.appendChild(titleBar); controlPanelContainer.appendChild(infoArea); controlPanelContainer.appendChild(buttonContainer); document.body.appendChild(controlPanelContainer); // 存储按钮引用以便后续更新 window.downloadButtonRef = downloadButton; // 添加拖拽功能 makePanelDraggable(controlPanelContainer); } /** * 创建通用按钮 */ function createButton(buttonText, backgroundColor, clickHandler) { const buttonElement = document.createElement('button'); buttonElement.textContent = buttonText; buttonElement.style.cssText = ` background: ${backgroundColor}; color: white; border: none; border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 12px; font-weight: 400; transition: all 0.2s ease; line-height: 1.2; `; // 鼠标悬停效果 buttonElement.addEventListener('mouseenter', () => { buttonElement.style.transform = 'translateY(-1px)'; buttonElement.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; }); buttonElement.addEventListener('mouseleave', () => { buttonElement.style.transform = 'translateY(0)'; buttonElement.style.boxShadow = 'none'; }); buttonElement.addEventListener('click', clickHandler); return buttonElement; } /** * 获取保存的面板位置 */ function getSavedPanelPosition() { try { const savedPosition = localStorage.getItem(POSITION_STORAGE_KEY); if (savedPosition) { return JSON.parse(savedPosition); } } catch (error) { console.warn('读取面板位置失败:', error); } // 默认位置:视口正上方居中 return { top: 20, left: Math.max(20, (window.innerWidth - 300) / 2) }; } /** * 保存面板位置 */ function savePanelPosition(top, left) { try { const position = { top, left }; localStorage.setItem(POSITION_STORAGE_KEY, JSON.stringify(position)); } catch (error) { console.warn('保存面板位置失败:', error); } } /** * 使面板可拖拽 */ function makePanelDraggable(panel) { let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; panel.addEventListener('mousedown', function(e) { // 防止在按钮上开始拖拽 if (e.target.tagName === 'BUTTON') return; isDragging = true; // 设置拖拽时的半透明效果 panel.style.opacity = '0.7'; // 计算鼠标相对于面板的偏移 const rect = panel.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; // 阻止文本选择 e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; // 计算新位置 let newLeft = e.clientX - dragOffsetX; let newTop = e.clientY - dragOffsetY; // 边界检查,确保面板不会超出视口 const maxLeft = window.innerWidth - panel.offsetWidth; const maxTop = window.innerHeight - panel.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxLeft)); newTop = Math.max(0, Math.min(newTop, maxTop)); // 更新面板位置 panel.style.left = newLeft + 'px'; panel.style.top = newTop + 'px'; }); document.addEventListener('mouseup', function() { if (!isDragging) return; isDragging = false; // 恢复不透明度 panel.style.opacity = '1'; // 保存当前位置 const rect = panel.getBoundingClientRect(); savePanelPosition(rect.top, rect.left); }); } /** * 处理提取当前API */ function handleExtractCurrentApi() { try { const apiData = extractCurrentPageApiInfo(); if (!apiData.apiName) { showInfoInPanel('未检测到有效的API接口信息'); return; } // 检查是否已存在相同API const existingIndex = extractedApiDataCollection.findIndex(item => item.apiName === apiData.apiName && item.apiUrl === apiData.apiUrl ); if (existingIndex !== -1) { // 更新现有数据 extractedApiDataCollection[existingIndex] = apiData; showInfoInPanel(`已更新: ${apiData.apiName}`); } else { // 添加新数据 extractedApiDataCollection.push(apiData); showInfoInPanel(`已提取: ${apiData.apiName}`); } saveDataToLocalStorage(); updateDownloadButtonText(); } catch (errorInfo) { console.error('提取API信息失败:', errorInfo); showInfoInPanel('提取失败: ' + errorInfo.message); } } /** * 提取当前页面的API信息 */ function extractCurrentPageApiInfo() { const apiInfo = { apiName: '', apiUrl: '', requestMethod: '', requestParams: [], responseInfo: '暂无响应信息' }; // 提取接口名称 apiInfo.apiName = safeExtractText('.name-text-nO_wGQ .copyable-NYoI4L'); // 提取请求方式和URL const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn'); if (pathInfoContainer) { apiInfo.requestMethod = safeExtractText('.base-info-path-lbh3Yn code', pathInfoContainer); apiInfo.apiUrl = safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer); } // 提取请求参数 apiInfo.requestParams = extractRequestParamsStructure(); return apiInfo; } /** * 安全提取文本内容 */ function safeExtractText(selector, parentElement = document) { try { const element = parentElement.querySelector(selector); return element ? element.textContent.trim() : ''; } catch (error) { console.warn(`提取文本失败 ${selector}:`, error); return ''; } } /** * 提取请求参数结构 */ function extractRequestParamsStructure() { const paramList = []; try { // 查找所有参数节点 const paramNodeList = document.querySelectorAll('.JsonSchemaViewer .index_node__G6-Qx'); paramNodeList.forEach(node => { const paramInfo = parseParamNode(node); if (paramInfo.paramName) { paramList.push(paramInfo); } }); } catch (error) { console.warn('提取参数结构失败:', error); } return paramList; } /** * 解析单个参数节点 */ function parseParamNode(node) { const paramInfo = { paramName: '', paramType: '', isRequired: false, paramDescription: '' }; try { // 获取参数名 const paramNameElement = node.querySelector('.propertyName-Zh4tse .copyable-NYoI4L'); paramInfo.paramName = paramNameElement ? paramNameElement.textContent.trim() : ''; // 获取参数类型 const typeElement = node.querySelector('.sl-type span'); paramInfo.paramType = typeElement ? typeElement.textContent.trim() : ''; // 判断是否必填 const optionalFlag = node.querySelector('.index_optional__O33wK'); paramInfo.isRequired = !optionalFlag; // 获取参数描述 const descriptionElement = node.querySelector('.json-schema-viewer__description p'); paramInfo.paramDescription = descriptionElement ? descriptionElement.textContent.trim() : ''; } catch (error) { console.warn('解析参数节点失败:', error); } return paramInfo; } /** * 处理下载全部数据 */ function handleDownloadAllData() { if (extractedApiDataCollection.length === 0) { showInfoInPanel('暂无数据可下载'); return; } try { const exportData = { exportTime: new Date().toLocaleString('zh-CN'), dataCount: extractedApiDataCollection.length, apiList: extractedApiDataCollection }; const dataString = JSON.stringify(exportData, null, 2); const fileName = `API接口数据_${new Date().toISOString().slice(0,10)}.json`; downloadJsonFile(dataString, fileName); showInfoInPanel(`已下载 ${extractedApiDataCollection.length} 条API数据`); } catch (errorInfo) { console.error('下载数据失败:', errorInfo); showInfoInPanel('下载失败: ' + errorInfo.message); } } /** * 下载JSON文件 */ function downloadJsonFile(dataContent, fileName) { const dataUrl = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataContent); const downloadLink = document.createElement('a'); downloadLink.href = dataUrl; downloadLink.download = fileName; downloadLink.style.display = 'none'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } /** * 处理清空所有数据 */ function handleClearAllData() { if (extractedApiDataCollection.length === 0) { showInfoInPanel('暂无数据需要清空'); return; } if (confirm(`确定要清空所有 ${extractedApiDataCollection.length} 条API数据吗?`)) { const clearedCount = extractedApiDataCollection.length; extractedApiDataCollection = []; saveDataToLocalStorage(); updateDownloadButtonText(); showInfoInPanel(`已清空 ${clearedCount} 条API数据`); } } /** * 处理预览数据 */ function handlePreviewData() { if (extractedApiDataCollection.length === 0) { showInfoInPanel('暂无数据可预览'); return; } const previewData = { exportTime: new Date().toLocaleString('zh-CN'), dataCount: extractedApiDataCollection.length, apiList: extractedApiDataCollection }; showJsonPreviewModal(previewData); } /** * 处理自动提取 */ function handleAutoExtract() { if (!isAutoExtracting) { // 开始自动提取 startAutoExtract(); } else { // 停止自动提取 stopAutoExtract(); } } /** * 开始自动提取 */ function startAutoExtract() { isAutoExtracting = true; // 获取当前接口URL const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn'); currentApiUrl = pathInfoContainer ? safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer) : ''; // 更新按钮状态 updateAutoExtractButton(); // 先提取当前页面 setTimeout(() => { handleExtractCurrentApi(); }, 500); // 开始监听接口URL变化 startApiUrlMonitoring(); showInfoInPanel('自动提取已开启,正在监听接口变化'); } /** * 停止自动提取 */ function stopAutoExtract() { isAutoExtracting = false; // 更新按钮状态 updateAutoExtractButton(); // 停止接口URL监听 stopApiUrlMonitoring(); showInfoInPanel('自动提取已停止'); } /** * 更新自动提取按钮状态 */ function updateAutoExtractButton() { if (window.autoExtractButtonRef) { if (isAutoExtracting) { window.autoExtractButtonRef.textContent = '停止提取'; window.autoExtractButtonRef.style.background = '#dc3545'; } else { window.autoExtractButtonRef.textContent = '自动提取'; window.autoExtractButtonRef.style.background = '#1890ff'; } } } /** * 开始监听接口URL变化 */ function startApiUrlMonitoring() { const targetContainer = document.querySelector('.base-info-path-lbh3Yn'); if (!targetContainer) { showInfoInPanel('未找到接口信息容器,无法开启自动提取'); stopAutoExtract(); return; } // 使用MutationObserver监听DOM变化 apiUrlObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList' || mutation.type === 'characterData' || mutation.type === 'subtree') { handleApiUrlChange(); } }); }); // 开始观察目标容器及其子元素 apiUrlObserver.observe(targetContainer, { childList: true, subtree: true, characterData: true, attributes: false }); // 备用定时检查 window.apiUrlCheckInterval = setInterval(() => { if (isAutoExtracting) { handleApiUrlChange(); } }, 100); } /** * 停止监听接口URL变化 */ function stopApiUrlMonitoring() { // 停止MutationObserver if (apiUrlObserver) { apiUrlObserver.disconnect(); apiUrlObserver = null; } // 清除定时器 if (window.apiUrlCheckInterval) { clearInterval(window.apiUrlCheckInterval); window.apiUrlCheckInterval = null; } } /** * 处理接口URL变化 */ function handleApiUrlChange() { if (!isAutoExtracting) return; const pathInfoContainer = document.querySelector('.base-info-path-lbh3Yn'); if (!pathInfoContainer) return; const newApiUrl = safeExtractText('.base-info-path-lbh3Yn .copyable-NYoI4L', pathInfoContainer); if (newApiUrl && newApiUrl !== currentApiUrl) { currentApiUrl = newApiUrl; // 延迟提取,确保页面内容完全更新 setTimeout(() => { if (isAutoExtracting) { console.log('检测到接口变化:', newApiUrl); handleExtractCurrentApi(); } }, 800); } } /** * 更新下载按钮文本 */ function updateDownloadButtonText() { if (window.downloadButtonRef) { window.downloadButtonRef.textContent = `下载全部(${extractedApiDataCollection.length})`; } } /** * 在面板中显示信息 */ function showInfoInPanel(content) { const infoArea = document.getElementById('api-extractor-info'); if (!infoArea) return; // 更新内容 infoArea.textContent = content; } /** * 清除所有模态框 */ function clearAllModals() { // 移除所有可能的模态框 const modalSelectors = [ '#json-preview-modal', '[id$="-modal"]', '[class*="modal"]', '[style*="z-index: 10001"]' ]; modalSelectors.forEach(selector => { const modals = document.querySelectorAll(selector); modals.forEach(modal => modal.remove()); }); // 重置弹窗状态 existDialog = false; } /** * 显示JSON预览模态框 */ function showJsonPreviewModal(data) { // 如果已存在弹窗,先销毁 if (existDialog) { clearAllModals(); } // 设置弹窗存在标记 existDialog = true; // 创建模态框容器 const modalOverlay = document.createElement('div'); modalOverlay.id = 'json-preview-modal'; modalOverlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 10001; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(2px); `; // 创建模态框内容 const modalContent = document.createElement('div'); modalContent.style.cssText = ` background: #ffffff; border-radius: 8px; padding: 20px; max-width: 90%; max-height: 80%; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; min-width: 600px; `; // 标题栏 const modalHeader = document.createElement('div'); modalHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #e9ecef; `; modalHeader.innerHTML = ` <h3 style="margin: 0; color: #536471; font-size: 15px; font-weight: 500;">JSON数据预览 (${data.dataCount}条)</h3> <div> <button id="copy-json-btn" style=" background: #17b26a; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 12px; margin-right: 6px; font-weight: 400; ">复制</button> <button id="close-modal-btn" style=" background: #9aa0a6; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 12px; font-weight: 400; ">关闭</button> </div> `; // JSON内容区域 const jsonContent = document.createElement('pre'); jsonContent.style.cssText = ` background: #f8f9fa; border: 1px solid #e1e8ed; border-radius: 4px; padding: 12px; margin: 0; overflow: auto; flex: 1; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-size: 12px; line-height: 1.4; color: #536471; white-space: pre-wrap; word-wrap: break-word; `; jsonContent.textContent = JSON.stringify(data, null, 2); // 组装模态框 modalContent.appendChild(modalHeader); modalContent.appendChild(jsonContent); modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); // 绑定事件 document.getElementById('close-modal-btn').addEventListener('click', () => { modalOverlay.remove(); existDialog = false; }); document.getElementById('copy-json-btn').addEventListener('click', () => { navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => { showInfoInPanel('JSON数据已复制到剪贴板'); }).catch(() => { showInfoInPanel('复制失败,请手动选择复制'); }); }); // 点击背景关闭 modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { modalOverlay.remove(); existDialog = false; } }); // ESC键关闭 const escHandler = (e) => { if (e.key === 'Escape') { modalOverlay.remove(); existDialog = false; document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initScript); } else { initScript(); } })();