// ==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
// ==/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('<rect id="\u91CD\u65B0\u751F\u6210"')) {
currentPriority = 3;
}
// Приоритет 2: Настоящие кнопки или элементы с явным классом кнопки/ролью (если не Приоритет 3)
else if (tag === 'button' || role === 'button' || (typeof classes === 'string' && classes.includes('ds-icon-button'))) {
currentPriority = 2;
}
// Приоритет 1: Просто иконки, элементы с курсором pointer, или элементы с текстом (если не Приоритет 2 или 3)
else if ((typeof classes === 'string' && classes.includes('ds-icon')) || ((tag === 'div' || tag === 'span') && cursorStyle === 'pointer') ||
buttonText.includes('повторить') || buttonText.includes('retry') || buttonText.includes('regenerate') || buttonText.includes(regenerateButtonText.toLowerCase())) {
currentPriority = 1;
}
// *** ДОБАВЛЕНО: Подробное логирование каждого кандидата Method 2 ***
console.log(` Method 2 Candidate: Tag: ${tag}, Class: "${classes}", Role: ${role}, Priority: ${currentPriority}, Text: "${buttonText}", Visible?: ${isElementVisible(btn)}, Cursor: ${cursorStyle}, Contains SVG: ${btnHTML.includes('<rect id="\u91CD\u65B0\u751F\u6210"')}`, btn);
if (currentPriority > 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" запущен. Ожидание самого нижнего сообщения о занятости и доступной кнопки...');
})();