提取小幺鸡接口文档页面中的接口信息(添加到剪切板)
// ==UserScript==
// @name 小幺鸡接口文档数据提取器
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 提取小幺鸡接口文档页面中的接口信息(添加到剪切板)
// @author Lexin
// @match *://*/*
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
// 添加悬浮按钮样式
GM_addStyle(`
.api-extractor-btn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
background: #409eff;
color: white;
border: none;
border-radius: 50%;
width: 56px;
height: 56px;
font-size: 24px;
cursor: pointer;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.api-extractor-btn:hover {
background: #66b1ff;
transform: scale(1.1);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.api-extractor-result {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 20px;
max-width: 800px;
max-height: 600px;
overflow-y: auto;
z-index: 10000;
font-family: 'Microsoft YaHei', Arial, sans-serif;
}
.api-extractor-result h2 {
margin-top: 0;
color: #303133;
border-bottom: 2px solid #409eff;
padding-bottom: 10px;
}
.api-extractor-result pre {
background: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 15px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
}
.api-extractor-close {
position: absolute;
top: 15px;
right: 20px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #909399;
}
.api-extractor-close:hover {
color: #409eff;
}
.api-extractor-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
}
.api-extractor-toast {
position: fixed;
bottom: 90px;
right: 20px;
z-index: 10001;
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 10px 14px;
border-radius: 6px;
font-size: 14px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
}
`);
// 创建悬浮按钮
function createButton() {
const button = document.createElement('button');
button.className = 'api-extractor-btn';
button.innerHTML = '📋';
button.title = '提取接口文档';
button.addEventListener('click', extractApiData);
document.body.appendChild(button);
}
// 提取表格数据
function extractTableData(tableElement) {
const data = [];
const rows = tableElement.querySelectorAll('.tbody .tblock');
rows.forEach(row => {
const depth = parseInt(row.className.match(/depth(\d+)/)?.[1] || '0');
const cells = row.querySelectorAll('.titem');
if (cells.length >= 5) {
// 提取必填字段 - 检查是否有true/false文本或图标
let required = 'false';
const requiredCell = cells[2];
if (requiredCell) {
// 查找true/false文本
const textContent = requiredCell.textContent?.trim() || '';
if (textContent === 'true' || textContent === 'false') {
required = textContent;
} else {
// 检查是否有图标或其他标识
const icon = requiredCell.querySelector('.iconfont, i');
if (icon) {
required = 'true'; // 有图标通常表示必填
}
}
}
const rowData = {
depth: depth,
name: cells[0]?.textContent?.trim() || '',
type: cells[1]?.textContent?.trim() || '',
required: required,
default: cells[3]?.textContent?.trim() || '',
description: cells[4]?.textContent?.trim() || ''
};
data.push(rowData);
}
});
return data;
}
// 构建树形结构
function buildTreeStructure(data) {
const result = [];
const stack = [];
data.forEach(item => {
const node = {
name: item.name,
type: item.type,
required: item.required === 'true',
default: item.default,
description: item.description,
children: []
};
while (stack.length > item.depth) {
stack.pop();
}
if (stack.length === 0) {
result.push(node);
} else {
stack[stack.length - 1].children.push(node);
}
stack.push(node);
});
return result;
}
function flattenTree(nodes, parentPath = '') {
const out = [];
nodes.forEach(node => {
const base = parentPath ? parentPath + '.' + node.name : node.name;
const isArray = typeof node.type === 'string' && node.type.toLowerCase().includes('array');
const currentPath = isArray ? base + '[]' : base;
out.push({
path: currentPath,
name: node.name,
type: node.type,
required: !!node.required,
default: node.default,
description: node.description
});
if (node.children && node.children.length) {
out.push(...flattenTree(node.children, currentPath));
}
});
return out;
}
function showToast(message) {
const el = document.createElement('div');
el.className = 'api-extractor-toast';
el.textContent = message;
document.body.appendChild(el);
setTimeout(() => {
if (el.parentNode) el.parentNode.removeChild(el);
}, 2000);
}
function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text)
.then(() => {
showToast('已复制到剪切板');
})
.catch(() => {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.top = '-1000px';
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
document.execCommand('copy');
showToast('已复制到剪切板');
} finally {
document.body.removeChild(ta);
}
});
} else {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.top = '-1000px';
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
document.execCommand('copy');
showToast('已复制到剪切板');
} finally {
document.body.removeChild(ta);
}
}
}
// 查找表格的安全方法
function findTableAfterTitle(titleElement) {
if (!titleElement) return null;
// 方法1: 查找标题后面的兄弟元素
let nextElement = titleElement.nextElementSibling;
while (nextElement) {
const table = nextElement.querySelector('.edit-table-container');
if (table) return table;
nextElement = nextElement.nextElementSibling;
}
// 方法2: 查找父元素的兄弟元素
let parentElement = titleElement.parentElement;
while (parentElement) {
nextElement = parentElement.nextElementSibling;
while (nextElement) {
const table = nextElement.querySelector('.edit-table-container');
if (table) return table;
nextElement = nextElement.nextElementSibling;
}
parentElement = parentElement.parentElement;
}
// 方法3: 在整个文档中查找最近的表格
const allTables = document.querySelectorAll('.edit-table-container');
let closestTable = null;
let minDistance = Infinity;
allTables.forEach(table => {
const distance = Math.abs(table.getBoundingClientRect().top - titleElement.getBoundingClientRect().top);
if (distance < minDistance) {
minDistance = distance;
closestTable = table;
}
});
return closestTable;
}
// 提取接口数据
function extractApiData() {
try {
// 获取接口标题
const titleElement = document.querySelector('.doc-title h1');
const title = titleElement ? titleElement.textContent.trim() : '未知接口';
// 获取请求方法和URL
const methodElement = document.querySelector('.tag-method');
const method = (methodElement ? methodElement.textContent.trim() : 'GET').toUpperCase();
const urlElement = document.querySelector('.url-box pre code');
const url = (urlElement ? urlElement.textContent.trim() : '').replace(/^`+|`+$/g, '');
// 获取请求体数据
const requestSection = Array.from(document.querySelectorAll('h2.title')).find(h2 =>
h2.textContent.includes('请求体')
);
let requestData = [];
if (requestSection) {
const requestTable = findTableAfterTitle(requestSection);
if (requestTable) {
const rawData = extractTableData(requestTable);
requestData = buildTreeStructure(rawData);
}
}
// 获取响应数据
const responseSection = Array.from(document.querySelectorAll('h2.title')).find(h2 =>
h2.textContent.includes('响应数据')
);
let responseData = [];
if (responseSection) {
const responseTable = findTableAfterTitle(responseSection);
if (responseTable) {
const rawData = extractTableData(responseTable);
responseData = buildTreeStructure(rawData);
}
}
const result = {
title: title,
method: method,
url: url,
request: requestData,
response: responseData,
extractedAt: new Date().toLocaleString('zh-CN')
};
const aiData = {
version: '1.0',
api: { title: title, method: method, url: url },
request: requestData,
response: responseData
};
showResult(result, aiData);
} catch (error) {
alert('提取数据失败: ' + error.message);
console.error('提取错误:', error);
}
}
// 显示结果
function showResult(data, aiData) {
// 创建遮罩层
const overlay = document.createElement('div');
overlay.className = 'api-extractor-overlay';
// 创建结果容器
const container = document.createElement('div');
container.className = 'api-extractor-result';
// 创建关闭按钮
const closeBtn = document.createElement('button');
closeBtn.className = 'api-extractor-close';
closeBtn.innerHTML = '×';
closeBtn.addEventListener('click', () => {
document.body.removeChild(overlay);
document.body.removeChild(container);
});
// 创建内容
const content = document.createElement('div');
content.innerHTML = `
<h2>完整JSON</h2>
<pre>${JSON.stringify(aiData, null, 2)}</pre>
`;
container.appendChild(closeBtn);
container.appendChild(content);
document.body.appendChild(overlay);
document.body.appendChild(container);
copyToClipboard(JSON.stringify(aiData, null, 2));
// 点击遮罩层关闭
overlay.addEventListener('click', () => {
document.body.removeChild(overlay);
document.body.removeChild(container);
});
}
// 页面加载完成后创建按钮
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createButton);
} else {
createButton();
}
})();