您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет меню фильтров к блокам кода в чате Grok с сохранением настроек
- // ==UserScript==
- // @name Grok Code Filter Menu 1.21.18
- // @namespace http://tampermonkey.net/
- // @version 1.21.18
- // @description Добавляет меню фильтров к блокам кода в чате Grok с сохранением настроек
- // @author tapeavion
- // @license MIT
- // @match https://grok.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=grok.com
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @downloadURL
- // @updateURL
- // ==/UserScript==
- (function() {
- 'use strict';
- // Стили
- const style = document.createElement('style');
- style.textContent = `
- .filter-menu-btn {
- position: absolute;
- top: 4px;
- right: 460px;
- height: 31px !important;
- z-index: 1;
- padding: 4px 8px;
- background: #1d5752;
- color: #b9bcc1;
- border: none;
- border-radius: 8px;
- cursor: pointer;
- font-size: 12px;
- transition: background 0.2s ease, color 0.2s ease;
- }
- .filter-menu-btn:hover {
- background: #4a8983;
- color: #ffffff;
- }
- .filter-menu {
- position: absolute;
- top: 40px;
- right: 10px;
- background: #2d2d2d;
- border: 1px solid #444;
- border-radius: 8px;
- padding: 5px;
- z-index: 9999;
- display: none;
- box-shadow: 0 2px 4px rgba(0,0,0,0.3);
- width: 200px;
- max-height: 550px;
- overflow-y: auto;
- }
- .filter-item {
- display: flex;
- align-items: center;
- padding: 5px 0;
- color: #a0a0a0;
- font-size: 12px;
- }
- .filter-item input[type="checkbox"] {
- margin-right: 5px;
- }
- .filter-item label {
- flex: 1;
- cursor: pointer;
- }
- .filter-slider {
- display: none;
- margin: 5px 0 5px 20px;
- width: calc(100% - 20px);
- }
- .filter-slider-label {
- display: none;
- color: #a0a0a0;
- font-size: 12px;
- margin: 2px 0 2px 20px;
- }
- .language-select {
- width: 100%;
- padding: 5px;
- margin-bottom: 5px;
- background: #3a3a3a;
- color: #a0a0a0;
- border: none;
- border-radius: 4px;
- font-size: 12px;
- }
- .color-picker {
- margin: 5px 0 5px 20px;
- width: calc(100% - 20px);
- }
- .color-picker-label {
- display: block;
- color: #a0a0a0;
- font-size: 12px;
- margin: 2px 0 2px 20px;
- }
- button.inline-flex {
- background-color: #1d5752 !important;
- opacity: 0;
- animation: fadeIn 1s ease-in-out forwards;
- }
- button.inline-flex:hover {
- background-color: #1d5752 !important;
- opacity: 1;
- }
- @keyframes fadeIn {
- 0% { opacity: 0; }
- 100% { opacity: 1; }
- }
- `;
- document.head.appendChild(style);
- // Определение языка пользователя
- const userLang = navigator.language || navigator.languages[0];
- const isRussian = userLang.startsWith('ru');
- const defaultLang = isRussian ? 'ru' : 'en';
- const savedLang = localStorage.getItem('filterMenuLang') || defaultLang;
- // Локализация
- const translations = {
- ru: {
- filtersBtn: 'Фильтры',
- sliderLabel: 'Степень:',
- commentColorLabel: 'Цвет комментариев:',
- filters: [
- { name: 'Негатив', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Сепия', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Ч/Б', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Размытие', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
- { name: 'Контраст', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
- { name: 'Яркость', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
- { name: 'Поворот оттенка', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
- { name: 'Насыщенность', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
- { name: 'Прозрачность', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
- ],
- langSelect: 'Выберите язык:',
- langOptions: [
- { value: 'ru', label: 'Русский' },
- { value: 'en', label: 'English' }
- ]
- },
- en: {
- filtersBtn: 'Filters',
- sliderLabel: 'Level:',
- commentColorLabel: 'Comment color:',
- filters: [
- { name: 'Invert', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Sepia', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Grayscale', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
- { name: 'Blur', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
- { name: 'Contrast', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
- { name: 'Brightness', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
- { name: 'Hue Rotate', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
- { name: 'Saturate', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
- { name: 'Opacity', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
- ],
- langSelect: 'Select language:',
- langOptions: [
- { value: 'ru', label: 'Русский' },
- { value: 'en', label: 'English' }
- ]
- }
- };
- // Глобальная переменная для текущего цвета комментариев
- let currentCommentColor = localStorage.getItem('commentColor') || '#5c6370';
- // Массив для хранения всех пар headerBlock и codeContainer
- const codeBlockRegistry = [];
- // Функция создания меню фильтров
- function addFilterMenu(headerBlock, codeContainer) {
- if (headerBlock.querySelector('.filter-menu-btn')) {
- console.log('Фильтр уже существует для заголовка:', headerBlock);
- return;
- }
- // Сохраняем пару headerBlock и codeContainer
- codeBlockRegistry.push({ headerBlock, codeContainer });
- console.log('Добавлен кодовый блок в реестр:', codeContainer.outerHTML);
- let currentLang = savedLang;
- const filterBtn = document.createElement('button');
- filterBtn.className = 'filter-menu-btn';
- filterBtn.textContent = translations[currentLang].filtersBtn;
- const filterMenu = document.createElement('div');
- filterMenu.className = 'filter-menu';
- // Целевой блок — контейнер кода
- const targetBlock = codeContainer;
- // Загружаем сохраненные настройки
- const savedFilterStates = JSON.parse(localStorage.getItem('codeFilterStates') || '{}');
- const savedFilterValues = JSON.parse(localStorage.getItem('codeFilterValues') || '{}');
- // Инициализируем значения по умолчанию
- const filters = translations[currentLang].filters;
- filters.forEach(filter => {
- if (!(filter.value in savedFilterStates)) {
- savedFilterStates[filter.value] = false;
- }
- if (!(filter.value in savedFilterValues)) {
- savedFilterValues[filter.value] = filter.default;
- }
- });
- // Применяем сохраненные фильтры
- function applyFilters() {
- const activeFilters = filters
- .filter(filter => savedFilterStates[filter.value])
- .map(filter => {
- const unit = filter.unit || '';
- const value = savedFilterValues[filter.value];
- return `${filter.value}(${value}${unit})`;
- });
- targetBlock.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
- console.log('Применены фильтры к контейнеру:', targetBlock, activeFilters);
- }
- // Применяем цвет комментариев
- // Альтернативный подход: применение цвета через глобальный стиль
- function applyGlobalCommentColor() {
- const existingStyle = document.getElementById('custom-comment-style');
- if (existingStyle) existingStyle.remove();
- const style = document.createElement('style');
- style.id = 'custom-comment-style';
- style.textContent = `
- .hljs-comment, span[style*="color: rgb(92, 99, 112)"] {
- color: ${currentCommentColor} !important;
- }
- `;
- document.head.appendChild(style);
- console.log('Применен глобальный стиль для комментариев:', currentCommentColor);
- }
- applyFilters();
- applyGlobalCommentColor();
- // Создаем выпадающий список для выбора языка
- const langSelect = document.createElement('select');
- langSelect.className = 'language-select';
- const langLabel = document.createElement('label');
- langLabel.textContent = translations[currentLang].langSelect;
- langLabel.style.color = '#a0a0a0';
- langLabel.style.fontSize = '12px';
- langLabel.style.marginBottom = '2px';
- langLabel.style.display = 'block';
- translations[currentLang].langOptions.forEach(option => {
- const opt = document.createElement('option');
- opt.value = option.value;
- opt.textContent = option.label;
- if (option.value === currentLang) {
- opt.selected = true;
- }
- langSelect.appendChild(opt);
- });
- // Создаем элемент для выбора цвета комментариев
- const colorPickerLabel = document.createElement('label');
- colorPickerLabel.className = 'color-picker-label';
- colorPickerLabel.textContent = translations[currentLang].commentColorLabel;
- const colorPicker = document.createElement('input');
- colorPicker.type = 'color';
- colorPicker.className = 'color-picker';
- colorPicker.value = currentCommentColor;
- colorPicker.addEventListener('input', () => {
- currentCommentColor = colorPicker.value;
- localStorage.setItem('commentColor', currentCommentColor);
- console.log('Изменен цвет комментариев:', currentCommentColor);
- refreshAllCodeBlocks();
- });
- // Функция обновления интерфейса при смене языка
- function updateLanguage(lang) {
- currentLang = lang;
- localStorage.setItem('filterMenuLang', currentLang);
- filterBtn.textContent = translations[currentLang].filtersBtn;
- langLabel.textContent = translations[currentLang].langSelect;
- colorPickerLabel.textContent = translations[currentLang].commentColorLabel;
- filterMenu.innerHTML = '';
- filterMenu.appendChild(langLabel);
- filterMenu.appendChild(langSelect);
- filterMenu.appendChild(colorPickerLabel);
- filterMenu.appendChild(colorPicker);
- renderFilters();
- }
- // Обработчик смены языка
- langSelect.addEventListener('change', () => {
- updateLanguage(langSelect.value);
- });
- // Рендеринг фильтров
- function renderFilters() {
- const filters = translations[currentLang].filters;
- filters.forEach(filter => {
- const filterItem = document.createElement('div');
- filterItem.className = 'filter-item';
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.checked = savedFilterStates[filter.value];
- checkbox.id = `filter-${filter.value}`;
- const label = document.createElement('label');
- label.htmlFor = `filter-${filter.value}`;
- label.textContent = filter.name;
- const sliderLabel = document.createElement('label');
- sliderLabel.className = 'filter-slider-label';
- sliderLabel.textContent = translations[currentLang].sliderLabel;
- const slider = document.createElement('input');
- slider.type = 'range';
- slider.className = 'filter-slider';
- slider.min = filter.min;
- slider.max = filter.max;
- slider.step = filter.step;
- slider.value = savedFilterValues[filter.value];
- if (checkbox.checked && filter.hasSlider) {
- slider.style.display = 'block';
- sliderLabel.style.display = 'block';
- }
- checkbox.addEventListener('change', () => {
- savedFilterStates[filter.value] = checkbox.checked;
- localStorage.setItem('codeFilterStates', JSON.stringify(savedFilterStates));
- if (filter.hasSlider) {
- slider.style.display = checkbox.checked ? 'block' : 'none';
- sliderLabel.style.display = checkbox.checked ? 'block' : 'none';
- }
- console.log('Изменен фильтр:', filter.value, checkbox.checked);
- refreshAllCodeBlocks();
- });
- slider.addEventListener('input', () => {
- savedFilterValues[filter.value] = slider.value;
- localStorage.setItem('codeFilterValues', JSON.stringify(savedFilterValues));
- console.log('Изменено значение фильтра:', filter.value, slider.value);
- refreshAllCodeBlocks();
- });
- filterItem.appendChild(checkbox);
- filterItem.appendChild(label);
- filterMenu.appendChild(filterItem);
- filterMenu.appendChild(sliderLabel);
- filterMenu.appendChild(slider);
- });
- }
- // Инициализация
- filterMenu.appendChild(langLabel);
- filterMenu.appendChild(langSelect);
- filterMenu.appendChild(colorPickerLabel);
- filterMenu.appendChild(colorPicker);
- renderFilters();
- // Обработчики для кнопки
- filterBtn.addEventListener('click', () => {
- filterMenu.style.display = filterMenu.style.display === 'block' ? 'none' : 'block';
- });
- document.addEventListener('click', (e) => {
- if (!filterBtn.contains(e.target) && !filterMenu.contains(e.target)) {
- filterMenu.style.display = 'none';
- }
- });
- headerBlock.style.position = 'relative';
- headerBlock.appendChild(filterBtn);
- headerBlock.appendChild(filterMenu);
- }
- // Функция логирования структуры DOM для отладки
- function logDomStructure(headerBlock) {
- console.log('Заголовок блока кода:', headerBlock.outerHTML);
- console.log('Следующий элемент (nextElementSibling):', headerBlock.nextElementSibling?.outerHTML || 'Не найден');
- console.log('Родительский элемент:', headerBlock.parentElement.outerHTML);
- console.log('Все <code> в родителе:', Array.from(headerBlock.parentElement.querySelectorAll('code')).map(el => el.outerHTML));
- console.log('Все .not-prose в родителе:', Array.from(headerBlock.parentElement.querySelectorAll('.not-prose')).map(el => el.outerHTML));
- }
- // Функция для управления селекторами и контейнерами
- function manageSelectorsAndContainers(headerBlocks, headerSelectorUsed, containerSelectorUsed, codeContainer) {
- const savedSelectors = JSON.parse(localStorage.getItem('codeBlockSelectors') || '{}');
- // Сохраняем успешные селекторы и контейнеры
- if (headerBlocks.length > 0 && codeContainer) {
- savedSelectors[headerSelectorUsed] = savedSelectors[headerSelectorUsed] || {};
- savedSelectors[headerSelectorUsed].headerCount = headerBlocks.length;
- savedSelectors[headerSelectorUsed].containerSelector = containerSelectorUsed;
- savedSelectors[headerSelectorUsed].lastUsed = Date.now();
- localStorage.setItem('codeBlockSelectors', JSON.stringify(savedSelectors));
- console.log('Сохранены селекторы:', headerSelectorUsed, containerSelectorUsed);
- }
- // Очистка устаревших селекторов (старше 7 дней)
- const oneWeek = 7 * 24 * 60 * 60 * 1000;
- Object.keys(savedSelectors).forEach(selector => {
- if (Date.now() - savedSelectors[selector].lastUsed > oneWeek) {
- delete savedSelectors[selector];
- }
- });
- localStorage.setItem('codeBlockSelectors', JSON.stringify(savedSelectors));
- return savedSelectors;
- }
- // Функция для обновления всех кодовых блоков
- function refreshAllCodeBlocks() {
- const savedFilterStates = JSON.parse(localStorage.getItem('codeFilterStates') || '{}');
- const savedFilterValues = JSON.parse(localStorage.getItem('codeFilterValues') || '{}');
- const filters = [
- { value: 'invert', default: 1 },
- { value: 'sepia', default: 1 },
- { value: 'grayscale', default: 1 },
- { value: 'blur', unit: 'px', default: 2 },
- { value: 'contrast', default: 2 },
- { value: 'brightness', default: 1.5 },
- { value: 'hue-rotate', unit: 'deg', default: 90 },
- { value: 'saturate', default: 1 },
- { value: 'opacity', default: 1 }
- ];
- // Применяем к зарегистрированным блокам
- console.log('Реестр кодовых блоков:', codeBlockRegistry.length);
- codeBlockRegistry.forEach(({ codeContainer }) => {
- const activeFilters = filters
- .filter(filter => savedFilterStates[filter.value])
- .map(filter => {
- const unit = filter.unit || '';
- const value = savedFilterValues[filter.value] || filter.default;
- return `${filter.value}(${value}${unit})`;
- });
- codeContainer.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
- console.log('Применены фильтры к зарегистрированному контейнеру:', codeContainer, activeFilters);
- const commentElements = codeContainer.querySelectorAll('span[style*="color: rgb(136, 136, 136)"], .hljs-comment');
- console.log('Найдено комментариев в зарегистрированном контейнере:', commentElements.length);
- commentElements.forEach(element => {
- element.style.color = currentCommentColor;
- });
- });
- // Глобальное применение к новым или незарегистрированным блокам
- const allCodeContainers = document.querySelectorAll('.not-prose div > code, .not-prose pre > code');
- allCodeContainers.forEach(codeContainer => {
- if (!codeBlockRegistry.some(reg => reg.codeContainer === codeContainer.parentElement)) {
- const activeFilters = filters
- .filter(filter => savedFilterStates[filter.value])
- .map(filter => {
- const unit = filter.unit || '';
- const value = savedFilterValues[filter.value] || filter.default;
- return `${filter.value}(${value}${unit})`;
- });
- codeContainer.parentElement.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
- console.log('Применены фильтры к глобальному .not-prose контейнеру:', codeContainer.parentElement, activeFilters);
- const commentElements = codeContainer.querySelectorAll('span[style*="color: rgb(136, 136, 136)"], .hljs-comment');
- console.log('Найдено комментариев в глобальном .not-prose контейнере:', commentElements.length);
- commentElements.forEach(element => {
- element.style.color = currentCommentColor;
- });
- }
- });
- }
- // Функция поиска и обработки блоков кода
- function processCodeBlocks() {
- // Селекторы для заголовков блоков кода
- const headerSelectors = [
- 'div[class*="flex"][class*="rounded-t"] > span.font-mono.text-xs',
- 'div[class*="flex"][class*="bg-surface"] > span',
- 'div > span[class*="font-mono"]'
- ];
- // Селекторы для контейнеров кода
- const containerSelectors = [
- 'nextElementSibling',
- '.not-prose div[style*="overflow-x: auto"]',
- '.not-prose div > code',
- '.not-prose pre > code',
- '.not-prose div[style*="background: hsl"]'
- ];
- // Загружаем сохраненные селекторы
- const savedSelectors = JSON.parse(localStorage.getItem('codeBlockSelectors') || '{}');
- let headerBlocks = [];
- let headerSelectorUsed = null;
- let containerSelectorUsed = null;
- let codeContainer = null;
- // Сначала пытаемся использовать сохраненные селекторы
- for (const savedSelector of Object.keys(savedSelectors)) {
- const headers = Array.from(document.querySelectorAll(savedSelector))
- .filter(span => {
- const text = span.textContent.toLowerCase();
- return ['javascript', 'css', 'html', 'python', 'java', 'cpp', 'json', 'bash', 'sql', 'xml', 'yaml', 'markdown'].includes(text);
- })
- .map(span => span.closest('div'));
- if (headers.length > 0) {
- headerBlocks = [...new Set(headers)];
- headerSelectorUsed = savedSelector;
- const savedContainerSelector = savedSelectors[savedSelector].containerSelector;
- // Проверяем сохраненный селектор контейнера
- for (const headerBlock of headers) {
- if (savedContainerSelector === 'nextElementSibling') {
- codeContainer = headerBlock.nextElementSibling?.querySelector('code') ? headerBlock.nextElementSibling : null;
- } else if (savedContainerSelector === '.not-prose div > code') {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose div > code')?.parentElement;
- } else if (savedContainerSelector === '.not-prose pre > code') {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose pre > code')?.parentElement;
- } else {
- codeContainer = headerBlock.parentElement.querySelector(savedContainerSelector);
- }
- if (codeContainer) {
- containerSelectorUsed = savedContainerSelector;
- console.log('Использованы сохраненные селекторы:', headerSelectorUsed, containerSelectorUsed);
- break;
- }
- }
- if (codeContainer) break;
- }
- }
- // Если сохраненные селекторы не сработали, ищем новые
- if (headerBlocks.length === 0) {
- for (const selector of headerSelectors) {
- const headers = Array.from(document.querySelectorAll(selector))
- .filter(span => {
- const text = span.textContent.toLowerCase();
- return ['javascript', 'css', 'html', 'python', 'java', 'cpp', 'json', 'bash', 'sql', 'xml', 'yaml', 'markdown'].includes(text);
- })
- .map(span => span.closest('div'));
- if (headers.length > 0) {
- headerBlocks = [...new Set(headers)];
- headerSelectorUsed = selector;
- break;
- }
- }
- }
- console.log('Найдено заголовков блоков кода:', headerBlocks.length);
- headerBlocks.forEach(headerBlock => {
- const langSpan = headerBlock.querySelector('span.font-mono.text-xs');
- if (!langSpan) {
- console.log('Заголовок без span с языком:', headerBlock);
- return;
- }
- // Пытаемся найти контейнер кода
- codeContainer = null;
- if (headerBlock.nextElementSibling?.querySelector('code')) {
- codeContainer = headerBlock.nextElementSibling;
- containerSelectorUsed = 'nextElementSibling';
- } else if (headerBlock.parentElement.querySelector('.not-prose div[style*="overflow-x: auto"]')) {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose div[style*="overflow-x: auto"]');
- containerSelectorUsed = '.not-prose div[style*="overflow-x: auto"]';
- } else if (headerBlock.parentElement.querySelector('.not-prose div > code')) {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose div > code')?.parentElement;
- containerSelectorUsed = '.not-prose div > code';
- } else if (headerBlock.parentElement.querySelector('.not-prose pre > code')) {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose pre > code')?.parentElement;
- containerSelectorUsed = '.not-prose pre > code';
- } else if (headerBlock.parentElement.querySelector('.not-prose div[style*="background: hsl"]')) {
- codeContainer = headerBlock.parentElement.querySelector('.not-prose div[style*="background: hsl"]');
- containerSelectorUsed = '.not-prose div[style*="background: hsl"]';
- }
- if (codeContainer) {
- console.log('Найден контейнер кода для заголовка:', codeContainer.outerHTML);
- addFilterMenu(headerBlock, codeContainer);
- // Сохраняем успешные селекторы
- manageSelectorsAndContainers(headerBlocks, headerSelectorUsed, containerSelectorUsed, codeContainer);
- } else {
- console.log('Контейнер кода не найден для заголовка:', headerBlock.outerHTML);
- logDomStructure(headerBlock);
- }
- });
- // Обновляем все кодовые блоки после обработки
- refreshAllCodeBlocks();
- }
- // Инициализация
- setTimeout(() => {
- console.log('Инициализация processCodeBlocks');
- processCodeBlocks();
- }, 2000);
- // Наблюдатель за изменениями DOM
- const observer = new MutationObserver((mutations) => {
- console.log('Обнаружены изменения DOM, вызывается processCodeBlocks');
- processCodeBlocks();
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['class', 'style']
- });
- // Инициализируем глобальное применение фильтров
- refreshAllCodeBlocks();
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址