// ==UserScript== // @name AutoClick (Server is busy) ✧BETA✧ // @name:en AutoClick for "Server Busy" ✧BETA✧ // @name:ru АвтоКлик при "Сервер занят" ✧BETA✧ // @namespace https://chat.deepseek.com // @version 2.13 // @description Автоматически нажимает кнопку "Повторить" при появлении сообщения "The server is busy. Please try again later." // @description:en Automatically clicks the "Retry" button when "The server is busy. Please try again later." message appears // @author KiberAndy + Ai // @license MIT // @match https://chat.deepseek.com/* // @grant none // @icon https://chat.deepseek.com/favicon.svg // @downloadURL https://update.greasyfork.icu/scripts/534488/AutoClick%20%28Server%20is%20busy%29%20%E2%9C%A7BETA%E2%9C%A7.user.js // @updateURL https://update.greasyfork.icu/scripts/534488/AutoClick%20%28Server%20is%20busy%29%20%E2%9C%A7BETA%E2%9C%A7.meta.js // ==/UserScript== (function() { 'use strict'; const busyMessageText = "The server is busy. Please try again later."; const retryButtonText = "Повторить"; // Russian text const regenerateButtonText = "重新生成"; // Chinese text from the SVG ID (same as rect ID) // Переменные для отслеживания состояния по каждому сообщению let trackingMessageElement = null; // Ссылка на последний ОБРАБОТАННЫЙ элемент сообщения "Server is busy" (самый нижний) let lastRetryClickTimestamp = 0; // Временная метка (performance.now()) последнего клика для ТЕКУЩЕГО отслеживаемого сообщения // Порог времени в миллисекундах, прежде чем разрешить повторный клик на одном и том же сообщении const RETRY_CLICK_THRESHOLD_MS = 8000; // 8 секунд // Функция для более надежной проверки видимости элемента function isElementVisible(element) { if (!element) return false; // Быстрая проверка на отсоединенность от DOM или display: none у родителя if (element.offsetParent === null) return false; const style = getComputedStyle(element); // Проверка на display / visibility if (style.display === 'none' || style.visibility === 'hidden') return false; const rect = element.getBoundingClientRect(); // Проверка на нулевые размеры или нахождение вне видимой части окна if (rect.width === 0 || rect.height === 0 || rect.right < 0 || rect.bottom < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight) return false; // Опционально: можно добавить проверку на перекрытие другими элементами, но это усложнит скрипт return true; // Считаем элемент видимым, если прошел все тесты } // Функция имитации клика function simulateClick(element) { if (!element) { console.error("simulateClick called with null element."); return; } const options = { bubbles: true, cancelable: true, view: window }; try { const mousedownEvent = new MouseEvent('mousedown', options); element.dispatchEvent(mousedownEvent); const mouseupEvent = new MouseEvent('mouseup', options); element.dispatchEvent(mouseupEvent); const clickEvent = new MouseEvent('click', options); element.dispatchEvent(clickEvent); console.log('Simulated click event sequence on element:', element); // Лог об успешной имитации } catch (e) { console.error("Error simulating click:", e); console.log('Simulated click failed, falling back to native click.'); element.click(); // Попытка нативного клика как запасной вариант } } function findRetryButton(messageElement) { console.log('Attempting to find Retry button near message element:', messageElement); let bestCandidate = null; // Кандидат с наивысшим приоритетом let bestPriority = -1; // Уровень приоритета лучшего кандидата (3 - уникальный SVG, 2 - ds-icon-button/button/role, 1 - ds-icon/pointer) let minDistance = Infinity; // Дистанция лучшего кандидата (для разрешения совпадений приоритетов) // --- Метод 1 (Приоритетный): Найти контейнер с уникальным SVG ID "重新生成" --- console.log('Method 1 (Prioritized): Attempting to find container with specific SVG ID "重新生成".'); // Ищем элемент с уникальным ID SVG внутри области сообщения const specificSvgElement = messageElement.parentElement?.parentElement?.querySelector('#\u91CD\u65B0\u751F\u6210'); if (specificSvgElement) { console.log('Method 1: Found specific SVG element with ID "重新生成".', specificSvgElement); // Теперь ищем ближайшего предка этого SVG, который является нашим контейнером кнопки/иконки let potentialButtonContainer = specificSvgElement.closest('button, a, [role="button"], div.ds-icon-button, div.ds-icon, span.ds-icon-button, span.ds-icon'); if (potentialButtonContainer && isElementVisible(potentialButtonContainer)) { console.log("Method 1: ---> Found a VISIBLE container for the specific SVG.", potentialButtonContainer); // Найден контейнер, содержащий уникальный SVG, и он видим. Это наш лучший кандидат. bestCandidate = potentialButtonContainer; bestPriority = 3; // Самый высокий приоритет console.log(`Method 1: Set BEST candidate based on containing specific SVG (Priority: ${bestPriority}).`, bestCandidate); return bestCandidate; // Возвращаем сразу, т.к. это самый надежный способ найти кнопку } else if (potentialButtonContainer) { console.log("Method 1: Found a container for the specific SVG, but it is NOT visible:", potentialButtonContainer); } else { console.log("Method 1: Did not find a button/icon container as an ancestor of the specific SVG element."); } } else { console.log('Method 1: Did NOT find specific SVG element with ID "重新生成" near the message.'); } // --- Метод 2 (Резервный): Поиск по классам / тексту + структурная/позиционная близость --- // Этот метод сработает, если Метод 1 не найдет SVG (например, если DOM изменится) console.log('Attempting Method 2 (Fallback: Proximity search with prioritization)...'); let searchScope = messageElement.parentElement?.parentElement || document.body; // В Method 2 ищем элементы, которые могут быть кнопками (button, a, role=button) ИЛИ иконками const potentialButtonsAndIcons = searchScope.querySelectorAll('button, a, [role="button"], div[class*="icon"], span[class*="icon"]'); console.log("Method 2: Starting proximity search among", potentialButtonsAndIcons.length, "potential elements..."); potentialButtonsAndIcons.forEach(btn => { // Используем forEach для логирования всех кандидатов Method 2 // *** Строгая проверка видимости для Method 2 *** if (!isElementVisible(btn)) { // console.log("Method 2: Skipping non-visible element:", btn); // Опциональный лог return; // Пропускаем невидимые в Method 2 } const buttonText = btn.textContent.trim().toLowerCase(); const btnHTML = btn.innerHTML; // Проверяем HTML на наличие уникального SVG ID const tag = btn.tagName.toLowerCase(); const role = btn.getAttribute('role'); const classes = btn.className; let cursorStyle = 'N/A'; try { cursorStyle = getComputedStyle(btn).cursor; } catch(e) { /* ignore */ } // Определяем приоритет кандидата Method 2 let currentPriority = 0; // Приоритет 3: Самый высокий - элемент содержит уникальный SVG ID if (btnHTML.includes(' 0) { // Если это потенциальный кандидат для Method 2 try { const messageRect = messageElement.getBoundingClientRect(); const buttonRect = btn.getBoundingClientRect(); // Расчет расстояния только для видимых потенциальных кандидатов const distance = Math.abs(buttonRect.top - messageRect.bottom); // Выбираем лучшего кандидата: сначала по приоритету, потом по близости if (currentPriority > bestPriority) { // Найден кандидат с более высоким приоритетом, чем текущий лучший (если такой уже был найден Метод 1 или ранее в Метод 2) bestPriority = currentPriority; minDistance = distance; // Обновляем мин. дистанцию для этого нового уровня приоритета bestCandidate = btn; // Обновляем лучшего кандидата console.log(` Method 2: Found a NEW BEST candidate (Higher Priority: ${bestPriority}, Distance: ${distance}).`, bestCandidate); } else if (currentPriority === bestPriority) { // Найден кандидат с таким же приоритетом, как текущий лучший. Сравниваем дистанцию. if (distance < minDistance) { minDistance = distance; bestCandidate = btn; // Обновляем лучшего кандидата (он ближе) console.log(` Method 2: Found a NEW BEST candidate (Same Priority: ${bestPriority}, Closer Distance: ${distance}).`, bestCandidate); } else { console.log(` Method 2: Candidate (Priority: ${currentPriority}, Distance: ${distance}) is not better than current best (Priority: ${bestPriority}, Distance: ${minDistance}).`, btn); } } else { console.log(` Method 2: Candidate (Priority: ${currentPriority}) is lower than current best (Priority: ${bestPriority}).`, btn); } } catch (e) { console.error("Method 2 (Fallback): Error getting bounding client rect:", e); } } else if (currentPriority > 0 && !isElementVisible(btn)) { console.log(" Method 2: Candidate is not visible, skipping for selection.", btn); } }); // Конец forEach // --- Возвращаем лучшего кандидата, найденного любым методом --- if (bestCandidate && isElementVisible(bestCandidate)) { // Финальная проверка видимости перед возвратом console.log(`findRetryButton finished. Returning BEST visible candidate (Priority: ${bestPriority}).`, bestCandidate); return bestCandidate; } console.log('findRetryButton finished. No suitable visible button found.'); return null; } function checkAndClick() { const paragraphs = document.querySelectorAll('p'); let latestMessageElementThisCycle = null; // Переменная для последнего найденного элемента В ЭТОМ цикле // Итерируем по ВСЕМ параграфам, чтобы найти ПОСЛЕДНИЙ видимый с точным текстом for (const p of paragraphs) { if (p.textContent.trim() === busyMessageText && p.offsetParent !== null) { if (isElementVisible(p)) { latestMessageElementThisCycle = p; // Запоминаем последний найденный в этом цикле } } } // --- Логика отслеживания состояния и клика --- if (latestMessageElementThisCycle) { // Найден (и выбран как самый нижний) элемент сообщения в этом цикле. // Проверяем, является ли этот самый нижний элемент НОВЫМ по сравнению с тем, который мы отслеживали. if (trackingMessageElement === null || !trackingMessageElement.isSameNode(latestMessageElementThisCycle)) { console.log('Detected a NEWEST "Server is busy" message instance (based on lowest visible exact match). Resetting state.'); trackingMessageElement = latestMessageElementThisCycle; // Начинаем отслеживать НОВЫЙ самый нижний элемент lastRetryClickTimestamp = 0; // Сбрасываем временную метку для него } // else { // console.log('Tracking the same latest "Server is busy" message instance.'); // } // Теперь ищем и кликаем кнопку, связанную с ОТСЛЕЖИВАЕМЫМ элементом const retryButtonElement = findRetryButton(trackingMessageElement); // Ищем кнопку ОТНОСИТЕЛЬНО отслеживаемого элемента // --- Логирование клика (оставляем основные логи) --- if (retryButtonElement) { const now = performance.now(); // Проверяем временной порог ТОЛЬКО для отслеживаемого элемента if (lastRetryClickTimestamp === 0 || now - lastRetryClickTimestamp > RETRY_CLICK_THRESHOLD_MS) { console.log('Potential retry button found for tracked message. Attempting to click.'); // Логи, которые показывают элемент перед кликом console.log('Element being clicked:', retryButtonElement); console.log('Element tag:', retryButtonElement.tagName.toLowerCase()); if (retryButtonElement.tagName.toLowerCase() === 'a') { console.log('Element href:', retryButtonElement.href); } // *** ИСПОЛЬЗУЕМ ФУНКЦИЮ ИМИТАЦИИ КЛИКА *** simulateClick(retryButtonElement); lastRetryClickTimestamp = now; console.log('Clicked. Updated lastRetryClickTimestamp.'); } else { // console.log(`Potential retry button found, but within threshold (${(now - lastRetryClickTimestamp).toFixed(0)}ms since last click). Waiting.`); } } else { console.log('Tracked message present, but retry button not found.'); } } else { // В этом цикле НЕ найдено ни одного элемента с точным текстом "Server is busy". if (trackingMessageElement !== null) { console.log('"Server is busy" message (previously tracked) no longer found. Resetting state.'); trackingMessageElement = null; // Перестаем отслеживать старый элемент lastRetryClickTimestamp = 0; } // else { // console.log('No "Server is busy" message to track.'); // } } } const checkInterval = setInterval(checkAndClick, 2000); console.log('Tampermonkey script "Авто-перепопытка при Server busy" запущен. Ожидание самого нижнего сообщения о занятости и доступной кнопки...'); })();