JSON Viewer

格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径

当前为 2024-09-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name JSON Viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.4.8
  6. // @note v0.4.8 代码优化
  7. // @note v0.4.7 增加对JSONP的判断,代码优化
  8. // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
  9. // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
  10. // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
  11. // @author Feny
  12. // @match *://*/*
  13. // @grant GM_addStyle
  14. // @grant GM_getResourceText
  15. // @grant GM_setClipboard
  16. // @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9wvjF8bLX4ZrHZx+XNqlwnmKjH5YUyRvb6kEAd8H058d1b47XV5Ir3eoN++bagaXYrN6KOBn2FfPPx5/aEutX8a+KNWhIubhruZLSNj8u1WMcIOP4QoXOOwNS/wDBM79lDTfjh8YNe+IXj63TxRdeHfKjsE1KMTxtdSbmMmxvlAiVV2IBsUybgAyKR9ZTy6lhsM69Xovm2+iPmKmOqYjEKjT6v5WXU9e8beKrjxZpp/s/xFrfhjWIwWs9X0q42z2kmPlZo2zDcxjqYZ0eNv7obDDf/YL/AG8L745eJNc+G3xCttP0n4qeEWZZ2sVZNP8AENsu0rd2ysS0ZKPG7RMSQsisCcukWd+3x4RtfhpqGk+JNPjS0g1mV7a8iT5UM4XesgHYsofdjglQepJPxwPE1x4V/au8LePtNkeK40ma0lmdDgyorvHMhPo9uxjP+y1bUcHRxmGbS1adn1TXT0/4fcipiqmExCi3pfVdGn19f+GLni/w7ceGf2wPFXga+Vo5rW/vDbI3WWI/v4HA/wBqBg3tk+lfWX7AXia1+F/iDV9B1CRbWHXjFLayyHannpuUxk+rqy4zxlMdWAPQft7fsL33x91nQfiD4DutP0r4oeDXVrT7cWWx1y3UsTZ3LKCyAh5FEigkLLIpHzBk5vwj4JuvE2kLJfeHdY8N6lGAl5pepwBZrOT+JRIuYp0ByBNCzxPg4bIICqYyljMKot62Sa6prr6P/gBDC1MLiHJLS90+jT6ev/Dnmn/BVP8Aaw0nxv4/8P8Aw18KXK69qmk3MlxqEdiRMwuivlpbrtPLopkMnZNy5IIYLyPwa/Z8vvEmv+HtHu4/Ovr+5iS6KDcqAtukwe6om7nuEJxX0XoH7PK/bJG0vRoY5rr/AFslvbLGZf8AfcAf+PGvafgv8CbX4byNqFyI5tWmXYCoytsh6qvqx7t+A4yWiWYUsLhlRpbr72318kVHA1MTiHVqbP7kl0P/2Q==
  17. // @require https://code.jquery.com/jquery-3.4.1.min.js
  18. // @require https://unpkg.com/layer-src@3.5.1/dist/layer.js
  19. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js
  20. // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css
  21. // @resource layerStyle https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css
  22. // ==/UserScript==
  23.  
  24.  
  25. /*随机字符串*/
  26. function randomString(e) {
  27. var e = e || 32,
  28. t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
  29. a = t.length,
  30. n = "";
  31. for (i = 0; i < e; i++){
  32. n += t.charAt(Math.floor(Math.random() * a));
  33. }
  34. return n
  35. }
  36. /*检查是否是图片链接*/
  37. function isImg(pathImg) {
  38. var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
  39. return regexp.test(pathImg);
  40. }
  41. /** 检验内容是否是json格式的内容*/
  42. function isJSON(str) {
  43. if (typeof str == 'string') {
  44. try {
  45. var obj = JSON.parse(str);
  46. if(typeof obj == 'object' && obj ){
  47. console.log("is json")
  48. return true;
  49. }else{
  50. console.log("is not json")
  51. return false;
  52. }
  53.  
  54. } catch(e) {
  55. console.log("is not json", e)
  56. return false;
  57. }
  58. }
  59. }
  60.  
  61. // jquery.json-viewer 插件 开始
  62. // 解决和原网页jquery版本冲突
  63. var jq = jQuery.noConflict(true);
  64. (function(jq){
  65. /**
  66. * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
  67. */
  68. function isCollapsable(arg) {
  69. return arg instanceof Object && Object.keys(arg).length > 0;
  70. }
  71.  
  72. /**
  73. * 检查字符串是否为URL
  74. */
  75. function isUrl(string) {
  76. var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  77. return regexp.test(string);
  78. }
  79.  
  80. /**
  81. * 将 JSON 对象转换为 HTML 表示形式
  82. * @return string
  83. */
  84. function json2html(json) {
  85. var html = '';
  86. if (typeof json === 'string') {
  87. /* Escape tags */
  88. json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  89. if (isUrl(json)){
  90. html += `<a href="${json}" class="json-string">"${json}"</a>`;
  91. }
  92. else{
  93. html += `<span class="json-string">"${json}"</span>`;
  94. }
  95. }
  96. else if (typeof json === 'number') {
  97. html += `<span class="json-number ">${json}</span>`;
  98. }
  99. else if (typeof json === 'boolean') {
  100. html += `<span class="json-bool ">${json}</span>`;
  101. }
  102. else if (json === null) {
  103. html += '<span class="json-null">null</span>';
  104. }
  105. else if (json instanceof Array) {
  106. if (json.length > 0) {
  107. html += '<span class="b">[</span><ol class="json-array">';
  108. for (var i = 0; i < json.length; ++i) {
  109. html += '<li>';
  110. /* Add toggle button if item is collapsable */
  111. if (isCollapsable(json[i])) {
  112. html += '<a href class="json-toggle"></a>';
  113. }
  114. html += json2html(json[i]);
  115. /* Add comma if item is not last */
  116. if (i < json.length - 1) {
  117. html += ',';
  118. }
  119. html += '</li>';
  120. }
  121. html += '</ol><span class="b">]</span>';
  122. }
  123. else {
  124. html += '[]';
  125. }
  126. }
  127. else if (typeof json === 'object') {
  128. var key_count = Object.keys(json).length;
  129. if (key_count > 0) {
  130. html += '<span class="b">{</span><ul class="json-dict">';
  131. for (var key in json) {
  132. if (json.hasOwnProperty(key)) {
  133. html += '<li>';
  134. /* Add toggle button if item is collapsable */
  135. if (isCollapsable(json[key])) {
  136. html += '<a href class="json-toggle"></a>';
  137. }
  138. html += `<span class="json-key">"${key}"</span>: ${json2html(json[key])}`;
  139. /* Add comma if item is not last */
  140. if (--key_count > 0){
  141. html += ',';
  142. }
  143. html += '</li>';
  144. }
  145. }
  146. html += '</ul><span class="b">}</span>';
  147. }
  148. else {
  149. html += '{}';
  150. }
  151. }
  152. return html;
  153. }
  154.  
  155. jq.fn.jsonViewer = function(json, jsonpFunctionName) {
  156. return this.each(function() {
  157. /* Transform to HTML */
  158. var html = json2html(json);
  159. /** is JSONP */
  160. if(jsonpFunctionName !== undefined && jsonpFunctionName !== null){
  161. html = `<div class="jsonp">${jsonpFunctionName}(</div>${html}<div class="jsonp">)</div>`
  162. }
  163. /* Insert HTML in target DOM element */
  164. jq(this).html(html);
  165.  
  166. /* Bind click on toggle buttons */
  167. jq(this).off('click');
  168. jq(this).on('click', 'a.json-toggle', function() {
  169. var target = jq(this).toggleClass('collapsed').siblings('ul.json-dict, ol.json-array');
  170. target.toggle();
  171. if (target.is(':visible')) {
  172. target.siblings('.json-placeholder').remove();
  173. }
  174. else {
  175. var count = target.children('li').length;
  176. var placeholder = count + (count > 1 ? ' items' : ' item');
  177. target.after('<a href class="json-placeholder">' + placeholder + '</a>');
  178. }
  179. return false;
  180. });
  181.  
  182. /* Simulate click on toggle button when placeholder is clicked */
  183. jq(this).on('click', 'a.json-placeholder', function() {
  184. jq(this).siblings('a.json-toggle').click();
  185. jq(this).siblings('a.json-placeholder').remove();
  186. return false;
  187. });
  188. });
  189. };
  190. })(jq);
  191. // jquery.json-viewer 插件 结束
  192.  
  193. (function() {
  194. 'use strict';
  195.  
  196. var source = jq('pre[style="word-wrap: break-word; white-space: pre-wrap;"]').first();
  197. // 根据上面这一点没办法确定是需要添加json格式化工具,再加上对内容进行判断是不是json格式的内容
  198. let rawText = source.html()
  199. if(!rawText){
  200. return
  201. }
  202.  
  203. // 判断是否为jsonp格式
  204. let tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/),
  205. jsonpFunctionName = null;
  206. if (tokens && tokens[1] && tokens[2]) {
  207. jsonpFunctionName = tokens[1]
  208. rawText= tokens[2]
  209. }
  210.  
  211. // 如果是直接打开的json接口地址才需要格式化插件
  212. if(source.length == 0 || !isJSON(rawText)){
  213. return
  214. }
  215.  
  216. // 随机rgb颜色
  217. let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}`
  218. // 添加样式
  219. GM_addStyle(GM_getResourceText('swalStyle'))
  220. GM_addStyle(GM_getResourceText('layerStyle'))
  221. GM_addStyle(`
  222. #json-renderer {
  223. line-height: 1.5;
  224. font-size: 14px;
  225. display: block;
  226. font-family: monospace;
  227. margin: 15px 30px;
  228. }
  229. .btnGroup, .jmBtnGroup{
  230. position: fixed;
  231. top: 30px;
  232. right: 30px;
  233. }
  234. .btn {
  235. border: 1px solid rgb(218, 220, 224);
  236. box-sizing: border-box;
  237. color: rgb(26, 115, 232);
  238. cursor: pointer;
  239. line-height: 28px;
  240. float: left;
  241. display: inherit;
  242. padding: 0 10px;
  243. }
  244. .btn:hover {
  245. background-color: rgb(210, 227, 252);
  246. }
  247. ul.json-dict,
  248. ol.json-array {
  249. list-style-type: none;
  250. margin: 0 0 0 2px;
  251. border-left: 1px dotted #5D6D7E;
  252. padding-left: 24px;
  253. }
  254. .b {
  255. font-weight: 700;
  256. }
  257. .jsonp{
  258. margin-left: -30px;
  259. }
  260. .json-key {
  261. /* color: #A31515;*/
  262. color: #910F93;
  263. }
  264. .json-string {
  265. /* color: #0b7500;*/
  266. color: #4B8A4C;
  267. }
  268. .json-number {
  269. /* color: #164FF0;*/
  270. color: #1a01cc;
  271. font-weight: 600;
  272. }
  273. .json-bool{
  274. color: #905;
  275. font-weight: 600;
  276. }
  277. .json-null {
  278. /* color: #F1592A;*/
  279. color: #0031BC;
  280. font-weight: 600;
  281. }
  282. a.json-toggle {
  283. position: relative;
  284. color: inherit;
  285. opacity: 0.2;
  286. text-decoration: none;
  287. }
  288. a.json-toggle:hover {
  289. opacity: 0.35;
  290. }
  291. a.json-toggle:active {
  292. opacity: 0.5;
  293. }
  294. a.json-toggle:focus {
  295. outline: none;
  296. }
  297. a.json-toggle:before {
  298. top: 2.5px;
  299. left: -15px;
  300. position: absolute;
  301. content: "";
  302. display: block;
  303. width: 0;
  304. height: 0;
  305. border-style: solid;
  306. border-width: 5px 0 5px 8px;
  307. border-color: transparent transparent transparent currentColor;
  308. transform: rotate(90deg);
  309. }
  310. a.json-toggle.collapsed:before {
  311. transform: rotate(0deg);
  312. }
  313. a.json-placeholder {
  314. color: #aaa;
  315. font-size: 13px;
  316. padding: 0 1em;
  317. text-decoration: none;
  318. }
  319. a.json-placeholder:hover {
  320. text-decoration: underline;
  321. }
  322. #jsmind_container{
  323. position: fixed;
  324. z-index: 999;
  325. top: 0;
  326. left: 0;
  327. display: none;
  328. width: 100vw;
  329. height: 100%;
  330. background:#F7F7F7
  331. }
  332. .jmBtnGroup{
  333. z-index: 9999;
  334. display: none;
  335. }
  336.  
  337. /**脑图自定义样式*/
  338. jmnode{
  339. display: flex;
  340. align-items: center;
  341. padding: 0 7px 0 22px;
  342. }
  343. jmnode{
  344. color: #475872 !important;
  345. box-shadow: none !important;
  346. background-color: transparent !important;
  347. }
  348. jmnode:hover{
  349. text-shadow: 1px 1px 1px currentColor;
  350. }
  351. jmnode.root {
  352. padding: 0;
  353. color: transparent !important;
  354. }
  355. jmnode:not(.root)::before, jmnode.root::before{
  356. content: " ";
  357. top: 50%;
  358. position: absolute;
  359. border-radius: 50%;
  360. transform: translateY(-50%);
  361. }
  362. jmnode:not(.root)::before{
  363. left: 0;
  364. width: 15px;
  365. height: 15px;
  366. background: rgba(${rgbaColor}, 0.5);
  367. }
  368. jmnode.root::before{
  369. left: 50%;
  370. width: 18px;
  371. height: 18px;
  372. transform: translate(-18px, -50%);
  373. background: rgba(${rgbaColor}, 0.7);
  374. }
  375. jmexpander{
  376. margin-top: 1px;
  377. line-height: 9px;
  378. }
  379.  
  380. .layui-layer-tips{
  381. width: auto !important;
  382. }
  383.  
  384. .mind-array{
  385. opacity: 0.5;
  386. font-size: 12px;
  387. padding-left: 5px;
  388. }
  389. `)
  390.  
  391. source.attr("id", "json-source").hide()
  392. // 将内容用eval函数处理下
  393. var jsonObject = eval('(' + rawText + ')');
  394. // 添加一个格式化显示的per元素
  395. jq("body").append('<div id="json-renderer"></div>')
  396. .append(`<div class="btnGroup"><input class="btn" type="button" value="复制" id="copyJson"/>
  397. <input class="btn" type="button" value="折叠全部" id="collapseJson"/>
  398. <input class="btn" type="button" value="JSON脑图" id="showMind"/>
  399. <input class="btn" type="button" value="原文本" id="switchRawText"/></div>`)
  400. // JSON脑图相关
  401. .append(`<div id="jsmind_container"></div>
  402. <div class="jmBtnGroup"><input class="btn" type="button" value="收起节点" id="collapseNode"/>
  403. <input class="btn" type="button" value="展开节点" id="expandNode"/>
  404. <input class="btn" type="button" value="返回" id="closeMind"/></div>`);
  405.  
  406. // 调用格式化方法
  407. jq('#json-renderer').jsonViewer(jsonObject, jsonpFunctionName);
  408.  
  409. let btnEvent = {
  410. // 复制JSON文本内容
  411. copyJson: function(){
  412. GM_setClipboard(JSON.stringify(jsonObject))
  413. layer.msg('复制成功', {time: 1500})
  414. },
  415. // 折叠全部的JSON结构
  416. collapseJson: function(e){
  417. var that = jq(e), v = that.val();
  418. if(v === "折叠全部"){
  419. jq('.json-toggle').not('.collapsed').click()
  420. }else{
  421. jq('a.json-placeholder').click().remove();
  422. }
  423.  
  424. that.val(v === "折叠全部" ? "展开全部" : "折叠全部")
  425. },
  426. // 查看原始/格式化JSON内容
  427. switchRawText: function(e){
  428. var that = jq(e), v = that.val();
  429. that.val(v === '原文本' ? "格式化" : "原文本")
  430. jq('#json-source, #json-renderer').toggle();
  431. },
  432. // 显示JSON脑图
  433. showMind: function(){
  434. let isArr = false;
  435. if(Array.isArray(jsonObject)){
  436. if(typeof jsonObject[0] !== 'object'){
  437. layer.msg('数据结构无法生成脑图', {time: 1000})
  438. return
  439. }
  440. isArr = true
  441. jsonObject = jsonObject[0]
  442. }
  443.  
  444. jq('.jmBtnGroup').show()
  445. jq('#jsmind_container').fadeToggle(200);
  446. document.documentElement.style.overflow='hidden';
  447. if(!window.jm){
  448. window.jm = new jsMind({
  449. mode :'side',
  450. editable: false,
  451. container:'jsmind_container',
  452. view: {
  453. hmargin: 50, // 思维导图距容器外框的最小水平距离
  454. vmargin: 50, // 思维导图距容器外框的最小垂直距离
  455. engine: 'svg', // 思维导图各节点之间线条的绘制引擎
  456. draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
  457. support_html : false,
  458. line_color: '#C4C9D0',
  459. },
  460. layout: {
  461. vspace: 7, // 节点之间的垂直间距
  462. hspace: 150, // 节点之间的水平空间
  463. },
  464. });
  465. jm.show({
  466. "meta":{
  467. "name":"JSON脑图",
  468. "author":"1220301855@qq.com",
  469. "version":"1.0"
  470. },
  471. "format":"node_tree",
  472. /* 数据内容 */
  473. "data": {
  474. "id": "root",
  475. "topic": 'Response',
  476. "direction": "left",
  477. "children": convertToMind(jsonObject),
  478. "chain": isArr ? 'Response[i]' : 'Response'
  479. }
  480. });
  481.  
  482. // 脑图节点事件
  483. jq("jmnode").on('dblclick mouseover mouseout', function(event){
  484. let that = jq(this),
  485. node = jm.get_node(that.attr('nodeid'))
  486. if(!node.parent){
  487. return
  488. }
  489.  
  490. switch(event.type){
  491. case 'dblclick':
  492. GM_setClipboard(mindChain(node))
  493. layer.msg('节点路径复制成功', {time: 1500})
  494. break;
  495. case 'mouseover':
  496. let s = `<b>节点路径(双击复制)</b><br/>${mindChain(node)}`
  497. layer.tips(s, that, {
  498. time: 0,
  499. tips: [2, '#1e2732']
  500. });
  501. break;
  502. default:
  503. layer.closeAll()
  504. break;
  505. }
  506. })
  507. }
  508. },
  509. // 收起节点
  510. collapseNode: () => jm.collapse_all(),
  511. // 展开节点
  512. expandNode: () => jm.expand_all(),
  513. // 关闭JSON脑图
  514. closeMind: function(){
  515. jq('.jmBtnGroup').hide()
  516. jq('#jsmind_container').fadeToggle(200);
  517. document.documentElement.style.overflow='';
  518. },
  519. }
  520. // 按钮点击事件
  521. jq('.btn').click(e => btnEvent[e.target.id](e.target))
  522.  
  523. // 所有a标签,看是否是图片,是图片生成预览图
  524. jq("a.json-string").hover(function(){
  525. var that = jq(this), href = that.attr('href');
  526. if(isImg(href)){
  527. layer.tips(`<img src="${href}" />`, that, {
  528. time: 0,
  529. anim: 5,
  530. maxWidth: 500,
  531. tips: [2, '#d9d9d9']
  532. });
  533. }
  534. }, () => layer.closeAll())
  535. })();
  536.  
  537.  
  538. /** JSON数据转换为jsMind所需要的数据结构 */
  539. function convertToMind(json){
  540. let children = []
  541. if(typeof json === 'object'){
  542. for (let i = 0, keys = Object.keys(json); i < keys.length; i++){
  543. let val = json[keys[i]];
  544. if(val === null || ['string', 'number', 'boolean', 'undefined'].includes(typeof val)){
  545. children.push({
  546. id: keys[i] + '-' + randomString(10),
  547. topic: `${keys[i]}`,
  548. chain: keys[i]
  549. })
  550. } else if(Array.isArray(val)){
  551. children.push({
  552. id: keys[i] + '-' + randomString(10),
  553. topic: `${keys[i]}<span class="mind-array">[${val.length}]</span>`,
  554. chain: keys[i],
  555. isArray: true,
  556. children: convertToMind(val[0], keys[i])
  557. })
  558. } else if(typeof val === 'object'){
  559. children.push({
  560. id: keys[i] + '-' + randomString(10),
  561. topic: `${keys[i]}`,
  562. chain: keys[i],
  563. children: convertToMind(val, keys[i])
  564. })
  565. }
  566. }
  567. }
  568. return children;
  569. }
  570. // 脑图节点调用链
  571. function mindChain(node){
  572. let s = node.data.chain
  573. if(!node.parent){
  574. return s
  575. }
  576.  
  577. let p = node.parent, r = mindChain(p)
  578. s = p.data.isArray ? `${r}[i].${s}` : `${r}.${s}`
  579. return s
  580. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址