您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Автоматически нажимает кнопку "Повторить" при появлении сообщения "The server is busy. Please try again later."
当前为
// ==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;'); })();