DeepSeek 自动重试 & 点击次数上限

当页面中出现“服务器繁忙,请稍后再试”的提示时,自动点击 id 为“重新生成”的元素进行重试。页面右下角显示一个大盒子,包含自动重试开关及点击次数上限设置(含当前计数显示),点击次数达到上限时自动关闭重试。适用于 deepseek 页面。

// ==UserScript==
// @name         DeepSeek 自动重试 & 点击次数上限
// @namespace    http://tampermonkey.net/
// @version      1.8.2
// @description  当页面中出现“服务器繁忙,请稍后再试”的提示时,自动点击 id 为“重新生成”的元素进行重试。页面右下角显示一个大盒子,包含自动重试开关及点击次数上限设置(含当前计数显示),点击次数达到上限时自动关闭重试。适用于 deepseek 页面。
// @author       Loki2077
// @match        https://chat.deepseek.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 自动重试是否启用,默认开启
    let autoRetryEnabled = true;
    // 记录当前自动点击重试的次数
    let retryClickCount = 0;
    // 定义间隔时间变量(单位:秒)
    let jgtime = 10;
    // 定义倒计时变量
    let countdown = jgtime;

    // 定义多个错误提示关键词
    const errorPhrases = [
        "服务器繁忙,请稍后再试",
        "服务器繁忙",
        "The server is busy. Please try again later."
    ];

    // 创建 UI 大盒子,包含开关按钮和点击次数上限设置
    function createUIBox() {
        const container = document.createElement('div');
        container.id = 'auto-retry-box';
        container.style.position = 'fixed';
        container.style.bottom = '10px';
        container.style.right = '10px';
        container.style.zIndex = 9999;
        container.style.background = 'rgba(120, 241, 104, 0.79)';
        container.style.color = '#fff';
        container.style.padding = '10px';
        container.style.borderRadius = '5px';
        container.style.fontSize = '14px';

        // 开关按钮
        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'auto-retry-toggle';
        toggleBtn.style.width = '100%';
        toggleBtn.style.padding = '8px';
        toggleBtn.style.backgroundColor = '#007BFF';
        toggleBtn.style.color = '#FFFFFF';
        toggleBtn.style.border = 'none';
        toggleBtn.style.borderRadius = '5px';
        toggleBtn.style.cursor = 'pointer';
        toggleBtn.textContent = autoRetryEnabled ? '关闭自动重试' : '开启自动重试';

        toggleBtn.addEventListener('click', () => {
            autoRetryEnabled = !autoRetryEnabled;
            toggleBtn.textContent = autoRetryEnabled ? '关闭自动重试' : '开启自动重试';
            countdown = autoRetryEnabled ? jgtime : '关闭';
            console.log(`自动重试功能已${autoRetryEnabled ? '开启' : '关闭'}`);
            // 每次点击更新容器背景颜色
            container.style.backgroundColor = autoRetryEnabled ? 'rgba(120, 241, 104, 0.79)' : 'rgba(0, 0, 0, 0.3)';
        });

        container.appendChild(toggleBtn);

        // 点击次数上限和当前计数显示
        const limitBox = document.createElement('div');
        limitBox.style.marginTop = '10px';

        const limitLabel = document.createElement('label');
        limitLabel.htmlFor = 'max-click-input';
        limitLabel.textContent = '点击次数上限: ';

        const maxClickInput = document.createElement('input');
        maxClickInput.id = 'max-click-input';
        maxClickInput.type = 'number';
        maxClickInput.value = '5';
        maxClickInput.min = '0';
        maxClickInput.style.width = '50px';
        maxClickInput.style.marginRight = '10px';

        // 当前点击次数显示
        const countDisplay = document.createElement('span');
        countDisplay.id = 'click-count';
        countDisplay.textContent = retryClickCount;

        // 组合 limitBox
        limitBox.appendChild(limitLabel);
        limitBox.appendChild(maxClickInput);
        limitBox.appendChild(document.createTextNode(' 当前点击次数: '));
        limitBox.appendChild(countDisplay);

        container.appendChild(limitBox);

        // 间隔时间设置
        const intervalBox = document.createElement('div');
        intervalBox.style.marginTop = '10px';

        const intervalLabel = document.createElement('label');
        intervalLabel.htmlFor = 'interval-input';
        intervalLabel.textContent = '间隔时间 (秒): ';

        const intervalInput = document.createElement('input');
        intervalInput.id = 'interval-input';
        intervalInput.type = 'number';
        intervalInput.value = jgtime;
        intervalInput.min = '1';
        intervalInput.style.width = '80px';
        intervalInput.style.marginRight = '10px';

        intervalInput.addEventListener('change', () => {
            jgtime = Number(intervalInput.value) || jgtime;
            countdown = jgtime;
            clearInterval(checkInterval);
            checkInterval = setInterval(checkAndRetry, jgtime * 1000);
            console.log(`间隔时间已修改为 ${jgtime} 秒`);
        });

        const countdownDisplay = document.createElement('span');
        countdownDisplay.id = 'countdown-display';
        countdownDisplay.textContent = `倒计时: ${countdown} 秒`;

        intervalBox.appendChild(intervalLabel);
        intervalBox.appendChild(intervalInput);
        intervalBox.appendChild(countdownDisplay);

        container.appendChild(intervalBox);

        document.body.appendChild(container);
    }

    // 新增弹窗提示函数
    function showPopup(message) {
        // 若容器不存在,则创建一个固定在屏幕右上角的容器,子项将从下往上排列
        let container = document.getElementById('popup-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'popup-container';
            container.style.position = 'fixed';
            container.style.top = '20px';
            container.style.right = '20px';
            container.style.display = 'flex';
            container.style.flexDirection = 'column';
            container.style.alignItems = 'flex-end';
            container.style.gap = '5px';
            container.style.zIndex = 10000;
            document.body.appendChild(container);
        }
        
        // 创建提示弹窗
        const popup = document.createElement('div');
        popup.textContent = message;
        popup.style.backgroundColor = '#d0f0c040'; // 淡绿色背景
        popup.style.color = '#000';
        popup.style.padding = '10px 20px';
        popup.style.borderRadius = '5px';
        popup.style.fontSize = '14px';
        popup.style.opacity = 0;
        popup.style.transform = 'translateY(20px)';
        popup.style.transition = 'transform 0.5s ease, opacity 0.5s ease';
        
        container.appendChild(popup);
        
        // 增加延迟,让每个弹窗之间有间隔,延迟时间根据当前容器中的弹窗数量计算(每个延迟200ms)
        const delay = container.children.length * 500;
        
        // 触发动画:自下而上渐显(延迟启动)
        setTimeout(() => {
           popup.style.opacity = 1;
           popup.style.transform = 'translateY(0)';
        }, delay);
        
        // 2秒后开始淡出并向上移动,然后移除该提示(考虑延迟后再计时)
        setTimeout(() => {
           popup.style.opacity = 0;
           popup.style.transform = 'translateY(-20px)';
           // 动画结束后移除元素
           setTimeout(() => {
               popup.remove();
           }, 1000);
        }, 5000 + delay*3);
    }

    // 给msg参数,调用showPopup函数,和console.log函数
    function showMsg(msg){
        console.log(msg);
        showPopup(msg);
    }

    // 检查页面是否出现错误提示,并执行自动重试操作(带点击次数上限判断)
    function checkAndRetry() {
        if (!autoRetryEnabled) return;

        const maxClickInput = document.getElementById('max-click-input');
        const maxClickCount = Number(maxClickInput.value) || 0;
        if (retryClickCount >= maxClickCount && maxClickCount > 0) {
            showMsg("点击次数上限,自动重试关闭。");
            autoRetryEnabled = false;
            // 更新容器背景颜色
            container.style.backgroundColor = autoRetryEnabled ? 'rgba(120, 241, 104, 0.79)' : 'rgba(0, 0, 0, 0.3)';
            document.getElementById('auto-retry-toggle').textContent = '开启自动重试';
            return;
        }

        

        // 查找 class 包含 "d7dc56a8" 的 div
        const targetDivs = document.querySelectorAll('div[class*="d7dc56a8"]');
        for (let i = 0; i < targetDivs.length; i++) {
            // 检查 div 内容是否包含错误提示关键词
            const textContent = targetDivs[i].textContent;
            // 如果找到错误提示,就模拟点击“重新生成”按钮
            if (errorPhrases.some(phrase => textContent.includes(phrase))) {
                // 找到错误提示,弹窗提示
                showMsg("检测到错误提示");
                // 提取textContext中的错误提示;
                showMsg(errorPhrases.find(phrase => textContent.includes(phrase)));
                // 当前错误提示是什么
                showMsg("开始寻找重新生成按钮");
                const retryElement = targetDivs[i].querySelector('#重新生成');
                if (retryElement) {
                    showMsg("找到重新生成按钮");
                    showMsg("模拟点击 重新生成 按钮");
                    if (typeof retryElement.click === "function") {
                        retryElement.click();
                        // 模拟点击成功后,重置倒计时
                        countdown = jgtime;
                        showMsg("模拟点击 成功");
                    } else {
                        const event = new MouseEvent("click", {
                            view: window,
                            bubbles: true,
                            cancelable: true
                        });
                        retryElement.dispatchEvent(event);
                    }
                    retryClickCount++;
                    document.getElementById('click-count').textContent = retryClickCount;
                } else {
                    showMsg("检测到错误提示,但未找到 '重新生成' 按钮。")
                }
                break;
            }
        }


        // 更新容器背景颜色
        container.style.backgroundColor = autoRetryEnabled ? 'rgba(120, 241, 104, 0.79)' : 'rgba(0, 0, 0, 0.3)';
    }

    // 初始化 UI
    createUIBox();

    // 每隔 jgtime 秒检测页面内容
    let checkInterval = setInterval(checkAndRetry, jgtime * 1000);

    // 更新倒计时显示
    setInterval(() => {
        // 如果启用了自动重试功能,并且倒计时减到 0,就重置倒计时
        if (countdown === 0 && autoRetryEnabled) {
            countdown = jgtime;
        }
        // 如果倒计时大于 0 并且自动重试功能开启,则继续倒计时
        if (countdown > 0 && autoRetryEnabled) {
            countdown--;
        } else {
            countdown = "关闭";
        }
        // 更新倒计时显示
        if (countdown === "关闭") {
            document.getElementById('countdown-display').textContent = ` ${countdown} `;
        }else{
            document.getElementById('countdown-display').textContent = `倒计时: ${countdown} 秒`;
        }
    }, 1000);

    // 如有需要,可以使用 MutationObserver 监听 DOM 变化(可选)
    /*
    const observer = new MutationObserver(checkAndRetry);
    observer.observe(document.body, { childList: true, subtree: true });
    */
})();