// ==UserScript==
// @name OSS JSON Viewer Pro
// @namespace http://your-namespace.com
// @version 5.1
// @description 专业级OSS文件查看器
// @match *://*.aliyuncs.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/528956/OSS%20JSON%20Viewer%20Pro.user.js
// @updateURL https://update.greasyfork.icu/scripts/528956/OSS%20JSON%20Viewer%20Pro.meta.js
// ==/UserScript==
(function() {
'use strict';
// 立即停止原始加载
window.stop();
document.documentElement.innerHTML = '';
// 创建容器
const container = document.createElement('div');
container.id = 'json-viewer-pro';
document.documentElement.appendChild(container);
// 加载动画
container.innerHTML = `
`;
// 样式注入
GM_addStyle(`
#json-viewer-pro {
padding: 20px;
font-family: 'Consolas', monospace;
background: #1e1e1e;
color: #d4d4d4;
min-height: 100vh;
}
.json-key {
color: #9cdcfe;
font-weight: bold;
}
.json-string {
color: #ce9178;
background: rgba(206,145,120,0.1);
padding: 2px 4px;
border-radius: 3px;
}
.json-number {
color: #b5cea8;
font-weight: bold;
}
.json-boolean {
color: #569cd6;
font-style: italic;
}
.json-null {
color: #808080;
font-style: italic;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.json-item {
margin: 4px 0;
padding-left: 20px;
position: relative;
border-left: 1px solid rgba(255,255,255,0.1);
}
.json-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 12px;
height: 100%;
border-left: 1px solid rgba(255,255,255,0.1);
}
.json-item:hover {
background: rgba(255,255,255,0.03);
}
.json-collapse {
cursor: pointer;
margin-right: 5px;
}
.json-collapse::after {
content: '▼';
font-size: 10px;
color: #9cdcfe;
margin-right: 5px;
}
.json-collapse.collapsed::after {
content: '▶';
}
.json-count {
color: #888;
font-size: 0.9em;
margin-left: 5px;
}
.json-image {
max-width: 100%;
max-height: 200px;
margin: 5px 0;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.json-text {
white-space: pre-wrap;
background: rgba(255,255,255,0.05);
padding: 5px;
border-radius: 3px;
margin: 5px 0;
}
.json-content {
margin-left: 8px;
padding-left: 12px;
border-left: 1px dashed rgba(255,255,255,0.1);
}
button:hover {
background: #1177bb !important;
}
button:active {
background: #0e5389 !important;
}
`);
// 获取数据
GM_xmlhttpRequest({
method: 'GET',
url: location.href,
responseType: 'arraybuffer',
onload: (res) => {
container.innerHTML = '';
try {
const decoder = new TextDecoder('utf-8');
const jsonData = JSON.parse(decoder.decode(res.response));
renderJSON(jsonData);
} catch(e) {
container.innerHTML = `数据解析失败: ${e.message}
`;
}
},
onerror: (err) => {
container.innerHTML = `请求失败: ${err.statusText}
`;
}
});
function addControlButtons(container, data) {
const buttonStyle = `
padding: 6px 12px;
margin-right: 10px;
background: #0e639c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`;
const buttonContainer = document.createElement('div');
buttonContainer.style.marginBottom = '20px';
// 复制按钮
const copyButton = document.createElement('button');
copyButton.textContent = '复制 JSON';
copyButton.style.cssText = buttonStyle;
copyButton.onclick = () => {
navigator.clipboard.writeText(JSON.stringify(data, null, 2))
.then(() => {
copyButton.textContent = '已复制!';
setTimeout(() => copyButton.textContent = '复制 JSON', 2000);
})
.catch(err => alert('复制失败: ' + err));
};
// 展开全部按钮
const expandButton = document.createElement('button');
expandButton.textContent = '展开全部';
expandButton.style.cssText = buttonStyle;
expandButton.onclick = () => {
container.querySelectorAll('.json-content').forEach(content => {
content.style.display = '';
});
container.querySelectorAll('.json-collapse').forEach(collapse => {
collapse.classList.remove('collapsed');
});
};
// 折叠全部按钮
const collapseButton = document.createElement('button');
collapseButton.textContent = '折叠全部';
collapseButton.style.cssText = buttonStyle;
collapseButton.onclick = () => {
container.querySelectorAll('.json-content').forEach(content => {
content.style.display = 'none';
});
container.querySelectorAll('.json-collapse').forEach(collapse => {
collapse.classList.add('collapsed');
});
};
buttonContainer.appendChild(copyButton);
buttonContainer.appendChild(expandButton);
buttonContainer.appendChild(collapseButton);
return buttonContainer;
}
function renderJSON(data) {
const countChildren = (obj) => {
if (!obj || typeof obj !== 'object') return 0;
return Object.keys(obj).length;
};
const render = (obj, indent = 0) => {
if (Array.isArray(obj)) {
return obj.map(item => {
if (typeof item === 'object' && item !== null) {
return `
{
${renderObject(item, indent + 1)}
}
`;
}
return `
${formatValue(item)}
`;
}).join('');
}
return renderObject(obj, indent);
};
const renderObject = (obj, indent = 0) => {
return Object.entries(obj).map(([key, value]) => {
const indentStr = ' '.repeat(indent * 4);
const count = countChildren(value);
if (typeof value === 'object' && value !== null) {
const isArray = Array.isArray(value);
return `
"${key}":
${isArray ?
`[
${count} items` :
`{
${count} keys`}
${render(value, indent + 1)}
${isArray ? ']' : '}'},
`;
}
// Handle image URLs
if (typeof value === 'string' &&
(value.match(/\.(jpeg|jpg|gif|png)$/) ||
value.startsWith('data:image/'))) {
return `
"${key}":
`;
}
// Handle multi-line text
if (typeof value === 'string' && value.includes('\n')) {
return `
`;
}
return `
"${key}":
${formatValue(value)},
`;
}).join('');
};
const formatValue = (value) => {
if (typeof value === 'string') {
// Skip formatting if it's an image URL or multi-line text
if (value.match(/\.(jpeg|jpg|gif|png)$/) ||
value.startsWith('data:image/') ||
value.includes('\n')) {
return '';
}
return `"${value}"`;
}
if (typeof value === 'number') return `${value}`;
if (typeof value === 'boolean') return `${value}`;
if (value === null) return `null`;
return '';
};
container.innerHTML = '';
container.appendChild(addControlButtons(container, data));
const jsonContainer = document.createElement('div');
jsonContainer.style.marginTop = '20px';
jsonContainer.innerHTML = render(data);
container.appendChild(jsonContainer);
// 修改折叠功能
document.querySelectorAll('.json-collapse').forEach(collapse => {
collapse.addEventListener('click', function() {
const parent = this.parentElement;
const content = parent.querySelector('.json-content');
const countSpan = parent.querySelector('.json-count');
const jsonData = JSON.parse(parent.getAttribute('data-json'));
const isArray = Array.isArray(jsonData);
if (content.style.display === 'none') {
content.style.display = '';
countSpan.textContent = isArray ?
`${jsonData.length} items` :
`${Object.keys(jsonData).length} keys`;
} else {
content.style.display = 'none';
countSpan.textContent = isArray ?
`${jsonData.length} items` :
`${Object.keys(jsonData).length} keys`;
}
this.classList.toggle('collapsed');
});
});
}
})();