DeepSeek繁忙自动点击重试(适配新版deepseek重试提醒)

DeepSeek提示繁忙,自动点击重试,并显示操作状态通知

// ==UserScript==
// @name         DeepSeek繁忙自动点击重试(适配新版deepseek重试提醒)
// @version      1.1.1
// @namespace    http://tampermonkey.net/
// @license      MIT
// @description  DeepSeek提示繁忙,自动点击重试,并显示操作状态通知
// @author       Dingning
// @include      *://chat.deepseek.com/*
// @icon         https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/dark/deepseek-color.png
// @grant        none
// ==/UserScript==

; (function () {
  ; ('use strict')

  // 配置参数
  let CHECK_INTERVAL = 1
  let retryDelay = 1
  let maxRetryDelay = 60
  let retryCount = 0
  let threshold = 5
  let normalDelayIncrement = 2
  let fastFrequencyDelayIncrement = 30
  let retryTimeoutId = null
  let configPanel
  let countdownTimer = null
  let remainingTime = 0
  let isMonitoring = true
  let isPanelExpanded = true

  // 添加全局样式
  const addGlobalStyles = () => {
    const style = document.createElement('style')
    style.textContent = `
          input::placeholder { color: #999!important; opacity: 1!important; }
          input { color: #333!important; }
        .toggle-btn.paused { background: #ff3b30!important; }
        .collapse-btn {
              padding: 6px 10px;
              border: none;
              border-radius: 4px;
              background: #007aff;
              color: white;
              cursor: pointer;
              font-size: 12px;
              transition: background 0.2s ease;
          }
        .collapse-btn:hover {
              background: #0063cc;
          }
      `
    document.head.appendChild(style)
  }

  function normalizePathData(pathData) {
    if (!pathData) return ''
    // 移除所有空格并将连续的命令字母分开
    return pathData
      .replace(/\s+/g, ' ')
      .trim()
      .replace(/([A-Za-z])([A-Za-z])/g, '$1 $2')
  }

  function findPathInAllSVGs(targetPathData, findAll = false) {
    // 获取页面中所有的SVG元素
    const svgElements = document.querySelectorAll('svg')
    const matches = []

    // 遍历每个SVG元素
    for (const svg of svgElements) {
      // 获取当前SVG中的所有path元素
      const pathElements = svg.querySelectorAll('path')

      // 遍历path元素查找匹配的路径数据
      for (const path of pathElements) {
        const pathData = path.getAttribute('d')

        // 比较路径数据(忽略空格差异)
        if (normalizePathData(pathData) === normalizePathData(targetPathData)) {
          if (!findAll) {
            // 只需要第一个匹配项时立即返回
            return path
          }
          matches.push(path)
        }
      }
    }

    // 返回所有匹配项或null
    return matches.length > 0 ? matches : null
  }

  const checkAndRetry = () => {
    if (!isMonitoring) return

    const loadingElements = document.querySelectorAll('.ds-loading')
    const hasLoading = loadingElements.length > 0

    const retryBtn = null

    // 查找所有匹配的路径元素
    const allMatches = findPathInAllSVGs(
      'M12 .5C18.351.5 23.5 5.649 23.5 12S18.351 23.5 12 23.5.5 18.351.5 12 5.649.5 12 .5zm-.225 4.8a.7.7 0 0 0-.528.224.703.703 0 0 0-.213.517.84.84 0 0 0 .056.304c.037.09.087.168.146.235l.809.831a.782.782 0 0 0-.147-.01 1.112 1.112 0 0 0-.157-.012 4.69 4.69 0 0 0-2.436.673 5.26 5.26 0 0 0-1.82 1.832c-.456.763-.685 1.617-.685 2.56 0 .966.232 1.845.696 2.639A5.33 5.33 0 0 0 9.36 16.99c.779.464 1.648.697 2.606.697.95 0 1.816-.233 2.595-.697a5.326 5.326 0 0 0 1.875-1.886 5.03 5.03 0 0 0 .696-2.606.716.716 0 0 0-.247-.55.754.754 0 0 0-.55-.236.78.78 0 0 0-.573.235.731.731 0 0 0-.236.551 3.46 3.46 0 0 1-.483 1.808c-.314.539-.741.97-1.28 1.292a3.44 3.44 0 0 1-1.797.482 3.44 3.44 0 0 1-1.797-.482 3.679 3.679 0 0 1-1.291-1.292 3.521 3.521 0 0 1-.472-1.808c0-.659.158-1.258.472-1.797a3.588 3.588 0 0 1 1.29-1.28 3.44 3.44 0 0 1 1.798-.484c.164 0 .3.008.404.023l-1.111 1.112a.722.722 0 0 0-.225.528c0 .21.07.386.213.528a.718.718 0 0 0 1.033-.012l2.246-2.246a.66.66 0 0 0 .203-.527.753.753 0 0 0-.203-.54l-2.223-2.268a.847.847 0 0 0-.247-.169.62.62 0 0 0-.28-.067z',
      true
    )
    if (allMatches.length) {
      hasBusyMessage = true
      retryBtn = allMatches[0].closest('.ds-icon-button')
    }

    if (!hasLoading && !hasBusyMessage) {
      if (retryCount > 0) createNotification('已重置重试次数和重试延迟', 'success')
      retryCount = 0
      retryDelay = 1
      if (configPanel) configPanel.style.display = 'none'
    } else if (hasBusyMessage) {
      if (configPanel) {
        configPanel.style.display = 'block'
        updateStatusDisplay()
      }
    }

    if (hasBusyMessage && !retryTimeoutId) {
      startCountdown(retryDelay)

      retryTimeoutId = setTimeout(() => {

        if (retryBtn) {
          retryBtn.click()
          retryCount++
          createNotification(`已点击重试,重试次数: ${retryCount}`, 'success')

          const toastContents = document.querySelectorAll('.ds-toast__content')
          for (const toast of toastContents) {
            if (toast.textContent === '你发送消息的频率过快,请稍后再发') {
              retryDelay = Math.min(retryDelay + fastFrequencyDelayIncrement, maxRetryDelay)
              createNotification(`检测到频率过快,延迟调整为 ${retryDelay} 秒`, 'warning')
              break
            }
          }

          if (retryCount >= threshold) {
            retryDelay = Math.min(retryDelay + normalDelayIncrement, maxRetryDelay)
            createNotification(`重试次数过多,延迟调整为 ${retryDelay} 秒`, 'warning')
          }
        }

        clearTimeout(retryTimeoutId)
        retryTimeoutId = null
      }, retryDelay * 1000)
    }
  }

  // 通知系统
  const createNotificationContainer = () => {
    const container = document.createElement('div')
    container.id = 'retry-notifications'
    container.style.cssText = `
          position: fixed;
          top: 20px;
          right: 20px;
          z-index: 10000;
          display: flex;
          flex-direction: column;
          gap: 10px;
          max-width: 300px;
      `
    document.body.appendChild(container)
    return container
  }

  const createNotification = (message, type = 'info') => {
    const notification = document.createElement('div')
    notification.className = `retry-notification ${type}`
    notification.style.cssText = `
          padding: 12px 16px;
          background: white;
          border-radius: 8px;
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
          font-size: 14px;
          color: #333;
          opacity: 0;
          transform: translateX(100%);
          transition: all 0.3s ease;
          display: flex;
          align-items: center;
          gap: 8px;
          border-left: 4px solid ${{
        success: '#52c41a',
        warning: '#faad14',
        info: '#1890ff'
      }[type]
      };
      `

    const icon = document.createElement('span')
    icon.textContent = { success: '✅', warning: '⚠️', info: 'ℹ️' }[type]
    notification.appendChild(icon)

    const text = document.createElement('span')
    text.textContent = message
    notification.appendChild(text)

    const container = document.getElementById('retry-notifications') || createNotificationContainer()
    container.appendChild(notification)

    setTimeout(() => {
      notification.style.opacity = '1'
      notification.style.transform = 'translateX(0)'
    }, 50)

    setTimeout(() => {
      notification.style.opacity = '0'
      setTimeout(() => notification.remove(), 300)
    }, 3000)
  }

  // 配置面板
  const createConfigPanel = () => {
    const panel = document.createElement('div')
    panel.id = 'config-panel'
    panel.style.cssText = `
          position: fixed;
          bottom: 20px;
          right: 20px;
          z-index: 10000;
          background: rgba(255, 255, 255, 0.95);
          color: #414158;
          border-radius: 12px;
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
          padding: 20px;
          display: flex;
          flex-direction: column;
          gap: 16px;
          max-width: 300px;
          backdrop-filter: blur(20px);
          border: 1px solid rgba(255, 255, 255, 0.2);
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
          display: none;
      `

    // 折叠按钮
    const collapseBtn = document.createElement('button')
    collapseBtn.textContent = isPanelExpanded ? '折叠' : '展开'
    collapseBtn.className = 'collapse-btn'
    collapseBtn.addEventListener('click', () => {
      isPanelExpanded = !isPanelExpanded
      collapseBtn.textContent = isPanelExpanded ? '折叠' : '展开'
      if (isPanelExpanded) {
        panel.style.height = 'auto'
        panel.style.padding = '20px'
        buttonContainer.style.marginTop = '10px'
        saveButton.style.display = 'block'
        panel.querySelectorAll('.config-item').forEach(item => (item.style.display = 'flex'))
      } else {
        panel.style.height = 'auto'
        panel.style.padding = '10px'
        buttonContainer.style.marginTop = '0px'
        saveButton.style.display = 'none'
        panel.querySelectorAll('.config-item').forEach(item => (item.style.display = 'none'))
      }
    })
    panel.appendChild(collapseBtn)

    // 状态显示
    const statusContainer = document.createElement('div')
    statusContainer.style.cssText = `
          background: rgba(245, 245, 247, 0.8);
          border-radius: 8px;
          padding: 12px;
          min-width: 100px;
      `

    const createStatusRow = (title, value, id) => {
      const row = document.createElement('div')
      row.style.cssText = `
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-bottom: 6px;
              font-size: 13px;
          `

      const titleSpan = document.createElement('span')
      titleSpan.textContent = title
      titleSpan.style.color = '#666'

      const valueSpan = document.createElement('span')
      valueSpan.id = id
      valueSpan.textContent = value
      valueSpan.style.cssText = `
              font-weight: 500;
              color: #1a1a1a;
          `

      row.append(titleSpan, valueSpan)
      return row
    }

    statusContainer.append(
      createStatusRow('检测状态', isMonitoring ? '运行中' : '已暂停', 'monitor-status'),
      createStatusRow('当前重试次数', retryCount, 'retry-count'),
      createStatusRow('下次尝试', '等待中', 'countdown')
    )
    panel.appendChild(statusContainer)

    // 配置项
    const createInputRow = (label, value) => {
      const row = document.createElement('div')
      row.style.cssText = `
              display: flex;
              align-items: center;
              justify-content: space-between;
              gap: 10px;
          `

      const labelEl = document.createElement('label')
      labelEl.textContent = label
      labelEl.style.cssText = `
              font-size: 13px;
              color: #666;
              flex: 1;
          `

      const input = document.createElement('input')
      input.type = 'number'
      input.value = value
      input.placeholder = '输入数值'
      input.style.cssText = `
              width: 80px;
              padding: 6px 8px;
              border: 1px solid #ddd;
              border-radius: 6px;
              font-size: 13px;
              background: rgba(255, 255, 255, 0.8);
          `

      row.append(labelEl, input)
      return { row, input }
    }

    const configs = [
      { label: '检查间隔 (秒)', value: CHECK_INTERVAL, action: v => (CHECK_INTERVAL = v) },
      { label: '初始延迟 (秒)', value: retryDelay, action: v => (retryDelay = v) },
      { label: '最大延迟 (秒)', value: maxRetryDelay, action: v => (maxRetryDelay = v) },
      { label: '重试阈值', value: threshold, action: v => (threshold = v) },
      { label: '正常增量 (秒)', value: normalDelayIncrement, action: v => (normalDelayIncrement = v) },
      { label: '过快增量 (秒)', value: fastFrequencyDelayIncrement, action: v => (fastFrequencyDelayIncrement = v) }
    ]

    configs.forEach(({ label, value, action }, index) => {
      const { row, input } = createInputRow(label, value)
      row.classList.add('config-item')
      if (!isPanelExpanded) {
        row.style.display = 'none'
      }
      input.addEventListener('input', () => {
        action(parseInt(input.value) || 1)
        clearInterval(checkIntervalId)
        checkIntervalId = setInterval(checkAndRetry, CHECK_INTERVAL * 1000)
      })
      panel.appendChild(row)
    })

    // 控制按钮和保存按钮容器
    const buttonContainer = document.createElement('div')
    buttonContainer.style.cssText = `
          margin-top: 10px;
          display: flex;
          gap: 10px;
      `

    const toggleBtn = document.createElement('button')
    toggleBtn.textContent = '暂停检测'
    toggleBtn.className = 'toggle-btn'
    toggleBtn.style.cssText = `
          flex: 1;
          padding: 10px;
          border: none;
          border-radius: 8px;
          background: #007aff;
          color: white;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: all 0.2s ease;
      `

    toggleBtn.addEventListener('click', () => {
      isMonitoring = !isMonitoring
      toggleBtn.textContent = isMonitoring ? '暂停检测' : '继续检测'
      toggleBtn.classList.toggle('paused', !isMonitoring)
      createNotification(isMonitoring ? '已恢复自动检测' : '已暂停自动检测', isMonitoring ? 'success' : 'warning')

      if (isMonitoring) {
        clearInterval(checkIntervalId)
        checkIntervalId = setInterval(checkAndRetry, CHECK_INTERVAL * 1000)
      } else {
        clearInterval(checkIntervalId)
        clearTimeout(retryTimeoutId)
        retryTimeoutId = null
        remainingTime = 0
      }
      updateStatusDisplay()
    })

    buttonContainer.appendChild(toggleBtn)

    // 打开配置按钮(在折叠状态下显示)
    const openConfigBtn = document.createElement('button')
    openConfigBtn.textContent = '打开配置'
    openConfigBtn.style.cssText = `
          padding: 10px 18px;
          border: none;
          border-radius: 8px;
          background: #007aff;
          color: white;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: background 0.2s ease;
      `
    openConfigBtn.addEventListener('click', () => {
      isPanelExpanded = true
      collapseBtn.textContent = '折叠'
      panel.style.height = 'auto'
      panel.style.padding = '20px'
      panel.querySelectorAll('.config-item').forEach(item => (item.style.display = 'block'))
    })

    // 保存按钮
    const saveButton = document.createElement('button')
    saveButton.textContent = '保存配置'
    saveButton.style.cssText = `
          padding: 10px 18px;
          border: none;
          border-radius: 8px;
          background: #007aff;
          color: white;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: background 0.2s ease;
      `
    saveButton.addEventListener('mouseenter', () => (saveButton.style.background = '#0063cc'))
    saveButton.addEventListener('mouseleave', () => (saveButton.style.background = '#007aff'))
    saveButton.addEventListener('click', () => createNotification('配置已保存', 'success'))

    if (isPanelExpanded) {
      buttonContainer.appendChild(saveButton)
    } else {
      buttonContainer.appendChild(openConfigBtn)
    }

    panel.appendChild(buttonContainer)

    document.body.appendChild(panel)
    return panel
  }

  // 倒计时管理
  const startCountdown = seconds => {
    clearInterval(countdownTimer)
    remainingTime = seconds
    countdownTimer = setInterval(() => {
      remainingTime = Math.max(0, remainingTime - 1)
      updateStatusDisplay()
      if (remainingTime <= 0) clearInterval(countdownTimer)
    }, 1000)
  }

  const updateStatusDisplay = () => {
    document.getElementById('countdown').textContent = remainingTime > 0 ? `${remainingTime}秒` : '等待中'
    document.getElementById('retry-count').textContent = retryCount
    document.getElementById('monitor-status').textContent = isMonitoring ? '运行中' : '已暂停'
  }

  // 初始化
  addGlobalStyles()
  let checkIntervalId = setInterval(checkAndRetry, CHECK_INTERVAL * 1000)
  configPanel = createConfigPanel()

  window.addEventListener('beforeunload', () => {
    clearInterval(checkIntervalId)
    clearInterval(countdownTimer)
  })
})()