AutoClick for "Server Busy" ✧BETA✧

Автоматически нажимает кнопку "Повторить" при появлении сообщения "The server is busy. Please try again later."

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          AutoClick for "Server Busy" ✧BETA✧
// @name:en       AutoClick for "Server Busy" ✧BETA✧
// @name:ru       АвтоКлик при "Сервер занят" ✧BETA✧
// @namespace     https://chat.deepseek.com
// @version       2.20
// @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 DEBUG = false; // Установить в true для подробных логов

    const log = (...args) => {
        if (DEBUG) {
            console.log('%c[ASB]%c', 'color: orange; font-weight: bold;', 'color: unset;', ...args);
        }
    };
    const errorLog = (...args) => { // Ошибки логируем всегда, если не DEBUG то без префикса
        if (DEBUG) {
            console.error('%c[ASB Error]%c', 'color: red; font-weight: bold;', 'color: unset;', ...args);
        } else {
            console.error(...args);
        }
    };

    const busyMessageText = "Server busy, please try again later.";
    const regenerateButtonText = "重新生成";
    const continueButtonText = "Continue";

    const messageRetryState = new WeakMap();

    function isElementVisible(element, elementName = 'Element') {
        if (DEBUG) log(`isElementVisible: Checking visibility for ${elementName}`, element);

        if (!element) {
           if (DEBUG) log(`isElementVisible [FAIL]: ${elementName} is null.`);
           return false;
        }
        if (!document.body.contains(element)) {
           if (DEBUG) log(`isElementVisible [FAIL]: ${elementName} is not in DOM.`, element);
           return false;
        }

        const style = getComputedStyle(element);
        if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
            if (DEBUG) log(`isElementVisible [FAIL]: ${elementName} hidden by style (display/visibility/opacity). D: ${style.display}, V: ${style.visibility}, O: ${style.opacity}`);
            return false;
        }

        // offsetParent check can be tricky, if display is not none, it might still be "visible" (e.g. fixed)
        // So, we rely more on display, visibility, opacity and dimensions.
        if (element.offsetParent === null && style.display !== 'fixed' && style.display !== 'sticky') {
             // If display is 'none', previous check would catch it.
             // This checks for cases where element is detached in a way that might make it non-interactive.
             // For `<span>` elements, `offsetParent` can be its parent block element.
             // If `elementName` is "Busy Message Span" and it's truly visible, its `offsetParent` should not be `null` unless it's `position:fixed/sticky`.
             if (DEBUG) log(`isElementVisible [INFO]: ${elementName} has offsetParent null and is not fixed/sticky. Display: ${style.display}`);
        }


        const rect = element.getBoundingClientRect();
        if (rect.width === 0 && rect.height === 0) {
            if (DEBUG) log(`isElementVisible [FAIL]: ${elementName} has zero width AND zero height.`, rect);
            return false;
        }
        // Check if in viewport - this is a "soft" check, can be removed if too restrictive
        // const inViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
        // if (!inViewport) {
        //    if (DEBUG) log(`isElementVisible [WARN]: ${elementName} is outside viewport. This might be okay.`);
        // }

        if (DEBUG) log(`isElementVisible [PASS]: ${elementName} is considered VISIBLE.`);
        return true;
    }

    function simulateClick(element) {
        if (!element) {
             errorLog("simulateClick called with null element.");
             return;
        }
        log('Attempting to simulate click on:', element);
        const options = { bubbles: true, cancelable: true, view: window, clientX: 1, clientY: 1 };
        try {
            element.dispatchEvent(new PointerEvent('pointerdown', options));
            element.dispatchEvent(new MouseEvent('mousedown', options));
            element.dispatchEvent(new PointerEvent('pointerup', options));
            element.dispatchEvent(new MouseEvent('mouseup', options));
            element.dispatchEvent(new MouseEvent('click', options));
            if (typeof element.focus === 'function') element.focus();
            // Нативный click() может быть нужен для некоторых элементов
            if (typeof element.click === 'function' && !element.closest('svg')) {
                 log('Additionally calling native .click() method on element:', element);
                 element.click();
            }
            log('Simulated click event sequence successfully dispatched on element:', element);
        } catch (e) {
            errorLog("Error during advanced click simulation:", e, "on element:", element);
            log('Falling back to basic native click.');
            try {
                element.click();
                log('Basic native click() called on element:', element);
            } catch (nativeClickError) {
                errorLog("Error with basic native click():", nativeClickError, "on element:", element);
            }
        }
    }

    function findRetryButton(messageSpanElement) {
        log('findRetryButton: Initiated for message span:', messageSpanElement);
        // Метод 0: Прямой структурный поиск для новой разметки кнопки "Обновить"
        try {
            const messageInnerContainer = messageSpanElement.parentElement; // span -> div.ac2694a7
            if (messageInnerContainer && messageInnerContainer.classList.contains('ac2694a7')) {
                const messageOuterContainer = messageInnerContainer.parentElement; // div.ac2694a7 -> div.e13328ad
                if (messageOuterContainer && messageOuterContainer.classList.contains('e13328ad')) {
                    // В логах мы видели, что previousElementSibling от div.e13328ad это div.fbb737a4,
                    // который содержит и текст "Теперь мышь работает..." и кнопку.
                    // А div._9663006 является родителем для div.fbb737a4.
                    // Попробуем сначала найти кнопку в непосредственном соседе, а потом в его родителе.

                    let buttonSearchContexts = [];
                    const directPreviousSibling = messageOuterContainer.previousElementSibling; // Ожидаем div.fbb737a4 или div._9663006

                    if (directPreviousSibling && directPreviousSibling.nodeType === Node.ELEMENT_NODE) {
                        buttonSearchContexts.push(directPreviousSibling);
                        // Если прямой сосед НЕ _9663006, то возможно _9663006 это его родитель
                        if (!directPreviousSibling.classList.contains('_9663006') && directPreviousSibling.parentElement && directPreviousSibling.parentElement.classList.contains('_9663006')) {
                           // Эта ветка не должна сработать судя по логам, но на всякий случай
                           // buttonSearchContexts.push(directPreviousSibling.parentElement);
                        } else if (directPreviousSibling.classList.contains('_9663006')) {
                            // Если прямой сосед это _9663006, то искать надо внутри него, возможно в .fbb737a4
                            const innerContext = directPreviousSibling.querySelector('.fbb737a4');
                            if (innerContext) buttonSearchContexts.unshift(innerContext); // Искать сначала в .fbb737a4
                        }
                    }


                    for (const buttonHostBlock of buttonSearchContexts) {
                        log('findRetryButton (M0): Searching in buttonHostBlock:', buttonHostBlock);

                        const specificSvgPathStart = "M12 .5C18.351.5";
                        const buttonSelectors = [
                            // Ищем по уникальному SVG path кнопки "Обновить"
                            `div.ds-icon-button svg path[d^="${specificSvgPathStart}"]`,
                            // Резервные варианты, если path изменится
                            'div.ds-icon-button[tabindex="0"]',
                            'div.ds-icon-button'
                        ];

                        for (const selector of buttonSelectors) {
                            log(`findRetryButton (M0): Attempting selector "${selector}" within buttonHostBlock.`);
                            let foundElement = buttonHostBlock.querySelector(selector);

                            if (foundElement) {
                                const actualButton = foundElement.tagName.toLowerCase() === 'path' ? foundElement.closest('div.ds-icon-button') : foundElement;
                                if (actualButton && actualButton.classList.contains('ds-icon-button')) {
                                    if (isElementVisible(actualButton, 'Method 0 Button')) {
                                        log('Retry Button Found and VISIBLE (Method 0 - New Structure):', actualButton);
                                        return actualButton;
                                    } else {
                                        log('findRetryButton (M0): Button found by Method 0 but NOT visible:', actualButton);
                                    }
                                }
                            }
                        }
                    }
                     log('findRetryButton (M0): No visible button found via structural search in identified contexts.');
                }
            }
        } catch(e) {
            errorLog("Error in Method 0 (Structural for new Refresh button):", e);
        }
        log('Method 0 did not find the button. Falling back to generic methods.');

        // --- Fallback methods (для кнопки Regenerate или если структура сильно изменится) ---
        const searchScopeForFallbacks = messageSpanElement.closest('div.chat-message-container, div.message-container, main, body') || document.body; // Более широкий, но релевантный поиск
        log('findRetryButton (Fallback): Search scope:', searchScopeForFallbacks);
        // ... (остальные фолбэк методы можно оставить как были, или упростить, если они не нужны)

        // Метод 1 (Старый): Поиск по SVG ID "重新生成" (для кнопки Regenerate ответа ИИ)
        const specificSvgElementById = searchScopeForFallbacks.querySelector('#\\u91CD\\u65B0\\u751F\\u6210');
        if (specificSvgElementById) {
            let potentialButtonContainer = specificSvgElementById.closest('button, div.ds-icon-button');
            if (potentialButtonContainer && isElementVisible(potentialButtonContainer, 'Method 1 Button')) {
                log('Retry/Regenerate Button Found (Method 1 - SVG ID) and VISIBLE:', potentialButtonContainer);
                return potentialButtonContainer;
            }
        }
        log('findRetryButton: No suitable button found after ALL methods.');
        return null;
    }


    function checkAndClick() {
        if (DEBUG) log('---------------- ciclo ----------------');
        log('checkAndClick: Cycle started.');

        const potentialMessageSpans = document.querySelectorAll('div.e13328ad div.ac2694a7 span');
        let latestMessageElementThisCycle = null;

        for (const span of potentialMessageSpans) {
            const spanText = span.textContent?.trim();
            if (spanText === busyMessageText) {
                log(`checkAndClick: Found span with matching busyMessageText ("${busyMessageText}")`);
                if (isElementVisible(span, 'Busy Message Span')) {
                    log('checkAndClick: Matched span IS VISIBLE:', span);
                    latestMessageElementThisCycle = span;
                } else {
                    log('checkAndClick: Matched span IS NOT VISIBLE. Skipping this one.');
                }
            }
        }

        if (latestMessageElementThisCycle) {
             log('checkAndClick: "Server is busy" message IS active and visible.');
             if (!messageRetryState.has(latestMessageElementThisCycle)) {
                 log('checkAndClick: New "Server is busy" instance. Initializing state.');
                 messageRetryState.set(latestMessageElementThisCycle, { buttonClicked: false, timestamp: Date.now() });
             }

             const currentState = messageRetryState.get(latestMessageElementThisCycle);
             if (currentState.buttonClicked) {
                 log('checkAndClick: Retry button ALREADY CLICKED for this message instance. Skipping.');
             } else {
                 const retryButtonElement = findRetryButton(latestMessageElementThisCycle);
                 if (retryButtonElement) {
                     log('checkAndClick: Retry button element candidate found:', retryButtonElement);
                     const buttonVisible = isElementVisible(retryButtonElement, 'Final Retry Button Check');
                     let buttonClickable = false;
                     if (buttonVisible) {
                         const style = getComputedStyle(retryButtonElement);
                         buttonClickable = style.pointerEvents !== 'none' && style.cursor !== 'default' && style.cursor !== 'auto' && style.cursor !== 'not-allowed';
                         log('checkAndClick: Retry button pointerEvents:', style.pointerEvents, 'Cursor:', style.cursor, '=> Clickable:', buttonClickable);
                     }

                     if (buttonVisible && buttonClickable) {
                         log('checkAndClick: Retry button is VISIBLE and CLICKABLE. Attempting click.');
                         simulateClick(retryButtonElement);
                         currentState.buttonClicked = true;
                         log('checkAndClick: Clicked Retry/Refresh. Marked state as clicked.');
                     } else {
                         log('checkAndClick: Retry button NOT clickable or NOT visible (Final Check). Will try next cycle.', 'Visible:', buttonVisible, 'Clickable:', buttonClickable);
                     }
                 } else {
                     log('checkAndClick: Retry button NOT found by findRetryButton. Will try next cycle.');
                 }
             }
        }

        // --- Кнопка "Continue" ---
        const potentialContinueButtons = document.querySelectorAll('div[role="button"].ds-button.ds-button--secondary.ds-button--bordered.ds-button--rect.ds-button--m');
        let continueButtonToClick = null;
        for (const btn of potentialContinueButtons) {
            if (isElementVisible(btn, 'Continue Button Candidate') && btn.textContent?.trim() === continueButtonText) {
                continueButtonToClick = btn;
                break;
            }
        }
        if (continueButtonToClick) {
            const style = getComputedStyle(continueButtonToClick);
            const isClickable = style.pointerEvents !== 'none' && style.cursor !== 'default' && style.cursor !== 'auto' && style.cursor !== 'not-allowed';
            if (isClickable) {
                log(`"${continueButtonText}" button is clickable. Attempting to click.`);
                simulateClick(continueButtonToClick);
            }
        }
        log('checkAndClick: Cycle ended.');
    }

    const checkInterval = setInterval(checkAndClick, 2500); // Можно вернуть интервал к 2-2.5с
    console.log('%c[ASB]%c Script "AutoClick (Server is busy) + Auto-Click Continue (Improved Retry)" v2.20 запущен.', 'color: orange; font-weight: bold;', 'color: unset;');

})();