Claude Session Key 管理器

Claude Session Key 管理工具(移动端布局兼容),支持拖拽、测活、批量导入导出、WebDAV云备份等功能

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Claude Session Key 管理器
// @name:zh-CN   Claude Session Key 管理器
// @name:en      Claude Session Key Manager
// @version      1.1.2
// @description  Claude Session Key 管理工具(移动端布局兼容),支持拖拽、测活、导入导出、WebDAV云备份等功能
// @description:zh-CN  Claude Session Key 管理工具(移动端布局兼容),支持拖拽、测活、批量导入导出、WebDAV云备份等功能
// @description:en  Claude Session Key Manager with drag-and-drop, token validation, import/export, WebDAV backup and more
// @author       xiaoye6688
// @namespace    https://greasyfork.org/users/1317128-xiaoye6688
// @homepage     https://greasyfork.org/zh-CN/users/1317128-xiaoye6688
// @supportURL   https://greasyfork.org/zh-CN/users/1317128-xiaoye6688
// @license      MIT
// @date         2025-03-09
// @modified     2025-03-09

// @match        https://claude.ai/*
// @match        https://claude.asia/*
// @match        https://demo.fuclaude.com/*
// @include      https://*fuclaude*/*
//
// @icon         https://claude.ai/favicon.ico
// @run-at       document-end

// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_info
// @grant        GM_cookie

// @connect      ipapi.co
// @connect      api.claude.ai
// @connect      *
// ==/UserScript==

(function () {
  "use strict";

  const config = {
    storageKey: "claudeTokens",
    ipApiUrl: "https://ipapi.co/country_code",
    defaultToken: {
      name: "Token00",
      key: "sk-key",
    },
    currentTokenKey: "currentClaudeToken",
    testResultsKey: "claudeTokenTestResults",
    testResultExpiry: 1800000, // 30分钟过期
  };

  const theme = {
    light: {
      bgColor: "#fcfaf5",
      textColor: "#333",
      borderColor: "#ccc",
      buttonBg: "#f5f1e9",
      buttonHoverBg: "#e5e1d9",
      modalBg: "rgba(0, 0, 0, 0.5)",
    },
    dark: {
      bgColor: "#2c2b28",
      textColor: "#f5f4ef",
      borderColor: "#3f3f3c",
      buttonBg: "#3f3f3c",
      buttonHoverBg: "#4a4a47",
      modalBg: "rgba(0, 0, 0, 0.7)",
    },
  };

  const getStyles = (isDarkMode) => `
  :root {
  --bg-color: ${isDarkMode ? theme.dark.bgColor : theme.light.bgColor};
  --text-color: ${isDarkMode ? theme.dark.textColor : theme.light.textColor};
  --border-color: ${
    isDarkMode ? theme.dark.borderColor : theme.light.borderColor
  };
  --button-bg: ${isDarkMode ? theme.dark.buttonBg : theme.light.buttonBg};
  --button-hover-bg: ${
    isDarkMode ? theme.dark.buttonHoverBg : theme.light.buttonHoverBg
  };
  --modal-bg: ${isDarkMode ? theme.dark.modalBg : theme.light.modalBg};
  }

  /* 浮动按钮样式 */
  #claude-toggle-button {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--bg-color);
  color: #b3462f;
  cursor: move;
  position: fixed;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  transition: background-color 0.3s ease, transform 0.2s ease;
  outline: none;
  padding: 0;
  user-select: none;
  touch-action: none;
  border: 1px solid var(--border-color);
  font-size: 18px;
  }

  #claude-toggle-button:hover {
  transform: scale(1.1);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
  }

  /* 下拉容器样式 */
  .claude-dropdown-container {
  position: fixed;
  background-color: var(--bg-color);
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
  display: none;
  flex-direction: column;
  gap: 0; /* 移除flex布局产生的空隙 */
  width: 600px;
  max-height: 80vh;
  overflow-y: auto;
  z-index: 9999;
  border: 1px solid var(--border-color);
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 0.3s ease, transform 0.3s ease;
  scrollbar-gutter: stable; /* 保持滚动条空间稳定 */
  scrollbar-width: thin; /* Firefox滚动条样式 */
  scrollbar-color: ${
    isDarkMode
      ? "rgba(255, 255, 255, 0.2) transparent"
      : "rgba(0, 0, 0, 0.2) transparent"
  };
  }

  /* 标题容器 */
  .claude-title-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 10px;
  }

  .claude-title-container h2 {
  margin: 0;
  color: var(--text-color);
  font-size: 18px;
  font-weight: 600;
  }

  .claude-ip-display {
  font-size: 14px;
  color: var(--text-color);
  padding: 4px 10px;
  background-color: var(--button-bg);
  border-radius: 12px;
  }

  /* Token 网格容器 */
  .claude-token-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  max-height: calc(2 * (90px + 12px) + 24px); /* 两行token的高度加上间隙和padding */
  overflow-y: auto;
  padding: 12px 0 12px 12px;
  scrollbar-gutter: stable; /* 保持滚动条空间稳定,防止出现时推动内容 */
  border: 1px solid var(--border-color);
  border-radius: 8px;
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.03)" : "rgba(0, 0, 0, 0.02)"
  };
  /* Firefox滚动条样式支持 */
  scrollbar-width: thin;
  scrollbar-color: ${
    isDarkMode
      ? "rgba(255, 255, 255, 0.2) transparent"
      : "rgba(0, 0, 0, 0.2) transparent"
  };
  }

  /* Token 卡片样式 */
  .claude-token-item {
  padding: 15px;
  border-radius: 8px;
  background-color: var(--bg-color);
  border: 1px solid var(--border-color);
  cursor: pointer;
  transition: all 0.3s ease;
  position: relative;
  height: 90px; /* 固定高度 */
  box-sizing: border-box; /* 确保padding不会增加总高度 */
  display: flex;
  flex-direction: column;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  }

  .claude-token-item:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  border-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.2)"
  };
  }

  .claude-token-item.current-token {
  border: 2px solid #b3462f;
  background-color: ${
    isDarkMode ? "rgba(179, 70, 47, 0.1)" : "rgba(179, 70, 47, 0.05)"
  };
  position: relative;
  }

  .current-token-badge {
  position: absolute;
  top: -8px;
  left: 8px;
  background-color: #b3462f;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  }

  .current-token-badge::after {
  content: "";
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: white;
  }

  /* Token 内容样式 */
  .token-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1; /* 填充可用空间 */
  justify-content: space-between; /* 顶部行和底部行分别位于容器顶部和底部 */
  }

  .token-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  }

  .token-name-container {
  display: flex;
  align-items: center;
  gap: 8px;
  }

  .token-number {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  background-color: var(--button-bg);
  }

  /* 账号类型标签样式 */
  .account-type-label {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  background-color: var(--button-bg);
  color: var(--text-color);
  margin-right: 6px;
  font-weight: 500;
  display: inline-block;
  }

  .token-name {
  font-weight: 500;
  font-size: 14px;
  }

  .token-actions {
  display: flex;
  gap: 8px;
  }

  .token-action-btn {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-color);
  transition: all 0.2s ease;
  }

  .token-action-btn:hover {
  background-color: var(--button-hover-bg);
  transform: scale(1.1);
  }

  .token-action-btn.delete-btn {
  color: #e24a4a;
  }

  .token-bottom-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  }

  .token-status {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  }

  .status-indicator {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: #888;
  }

  .status-indicator.success {
  background-color: #48bb78;
  }

  .status-indicator.error {
  background-color: #e53e3e;
  }

  .status-indicator.loading {
  background-color: #888;
  animation: pulse 1.5s infinite;
  }

  @keyframes pulse {
  0% { opacity: 0.4; }
  50% { opacity: 1; }
  100% { opacity: 0.4; }
  }

  .token-time {
  font-size: 12px;
  color: var(--text-color);
  opacity: 0.7;
  }

  /* 按钮容器 */
  .claude-button-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  padding-top: 12px;
  }

  .claude-button {
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 13px;
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  position: relative;
  }

  .claude-button:hover {
  transform: translateY(-2px);
  }

  .claude-button.primary {
  background-color: #b3462f;
  color: white;
  }

  .claude-button.primary:hover {
  background-color: #a03d2a;
  }

  .claude-button.secondary {
  background-color: var(--button-bg);
  color: var(--text-color);
  }

  .claude-button.secondary:hover {
  background-color: var(--button-hover-bg);
  }

  /* 工具提示样式 */
  .claude-button[data-tooltip]:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 12px;
  white-space: nowrap;
  z-index: 10001;
  margin-bottom: 5px;
  pointer-events: none;
  opacity: 0;
  animation: tooltip-fade-in 0.2s ease forwards;
  }

  @keyframes tooltip-fade-in {
  from { opacity: 0; transform: translate(-50%, 5px); }
  to { opacity: 1; transform: translate(-50%, 0); }
  }

  /* 模态框样式 */
  .claude-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: var(--modal-bg);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10001;
  }

  .claude-modal-content {
  background-color: var(--bg-color);
  padding: 20px;
  padding-right: 14px; /* 右侧padding稍微增加,为滚动条预留空间但不过多 */
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  width: 500px;
  max-width: 90%;
  overflow-y: auto;
  position: relative;
  scrollbar-gutter: stable; /* 保持滚动条空间稳定 */
  scrollbar-width: thin; /* Firefox滚动条样式 */
  scrollbar-color: ${
    isDarkMode
      ? "rgba(255, 255, 255, 0.2) transparent"
      : "rgba(0, 0, 0, 0.2) transparent"
  };
  }

  .claude-modal-content.narrow-modal {
  width: 400px;
  max-width: 80%;
  }

  .claude-modal h2 {
  margin-top: 0;
  margin-bottom: 15px;
  color: var(--text-color);
  font-size: 18px;
  font-weight: 600;
  }

  .claude-modal input, .claude-modal textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 15px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 14px;
  background-color: var(--bg-color);
  color: var(--text-color);
  }

  .claude-modal-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 15px;
  }

  .claude-close-button {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: var(--text-color);
  padding: 5px;
  line-height: 1;
  }

  /* 自定义滚动条样式 */
  .claude-token-grid::-webkit-scrollbar {
  width: 6px;
  /* 初始状态下滚动条透明 */
  background-color: transparent;
  }

  .claude-token-grid::-webkit-scrollbar-track {
  background: transparent;
  margin: 4px 0;
  }

  .claude-token-grid::-webkit-scrollbar-thumb {
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.15)"
  };
  border-radius: 6px;
  transition: background-color 0.3s ease;
  /* 初始状态下滚动条半透明 */
  opacity: 0.6;
  }

  .claude-token-grid::-webkit-scrollbar-thumb:hover {
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"
  };
  opacity: 1;
  }

  .claude-token-grid:hover::-webkit-scrollbar-thumb {
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"
  };
  opacity: 1;
  }

  /* 滚动条样式 */
  .claude-dropdown-container::-webkit-scrollbar,
  .claude-modal-content::-webkit-scrollbar {
  width: 6px;
  background-color: transparent;
  }

  .claude-dropdown-container::-webkit-scrollbar-track,
  .claude-modal-content::-webkit-scrollbar-track {
  background: transparent;
  margin: 4px 0;
  }

  .claude-dropdown-container::-webkit-scrollbar-thumb,
  .claude-modal-content::-webkit-scrollbar-thumb {
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.15)"
  };
  border-radius: 6px;
  transition: background-color 0.3s ease;
  opacity: 0.6;
  }

  .claude-dropdown-container::-webkit-scrollbar-thumb:hover,
  .claude-modal-content::-webkit-scrollbar-thumb:hover {
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"
  };
  opacity: 1;
  }

  .claude-dropdown-container:hover::-webkit-scrollbar-thumb,
  .claude-modal-content:hover::-webkit-scrollbar-thumb {
  opacity: 1;
  }

  /* 预览容器 */
  .claude-preview-container {
  margin-top: 15px;
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 15px;
  }

  .claude-preview-title {
  font-size: 16px;
  margin-bottom: 10px;
  color: var(--text-color);
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 5px;
  }

  .claude-preview-item {
  margin-bottom: 8px;
  font-size: 14px;
  padding: 8px;
  border-radius: 4px;
  background-color: var(--button-bg);
  }

  /* 滚动提示样式 */
  .scroll-indicator {
  grid-column: 1 / -1; /* 横跨所有列 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  margin-top: 5px;
  color: ${isDarkMode ? "rgba(255, 255, 255, 0.6)" : "rgba(0, 0, 0, 0.5)"};
  font-size: 12px;
  background-color: ${
    isDarkMode ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.03)"
  };
  border-radius: 6px;
  gap: 8px;
  }

  .scroll-arrow {
  animation: bounce 1.5s infinite;
  }

  @keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(3px); }
  }

  @media (max-width: 768px) {
  /* 浮动按钮移动端优化 */
  #claude-toggle-button {
  width: 36px;
  height: 36px;
  font-size: 10px;
  top: 10px;
  right: 10px;
  left: auto !important; /* 强制右下角显示 */
  }

  /* 下拉容器移动端适配 */
  .claude-dropdown-container {
  position: fixed;
  top: 10px !important;
  left: 10px !important;
  right: 10px !important;
  bottom: auto !important;
  width: auto !important;
  max-width: none;
  max-height: calc(100vh - 80px);
  padding: 15px;
  border-radius: 8px;
  }

  /* 标题容器移动端适配 */
  .claude-title-container {
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  padding-bottom: 8px;
  }

  .claude-title-container h2 {
  font-size: 16px;
  }

  .claude-ip-display {
  font-size: 12px;
  padding: 3px 8px;
  }

  /* Token网格移动端单列布局 */
  .claude-token-grid {
  grid-template-columns: 1fr; /* 单列显示 */
  gap: 10px;
  max-height: calc(3 * (80px + 10px) + 20px); /* 三行token的高度 */
  padding: 10px;
  }

  /* Token卡片移动端优化 */
  .claude-token-item {
  height: 100px;
  padding: 12px;
  }

  .token-number {
  font-size: 11px;
  padding: 1px 6px;
  }

  .token-name {
  font-size: 13px;
  }

  .token-action-btn {
  padding: 3px;
  }

  .token-action-btn svg {
  width: 12px;
  height: 12px;
  }

  .token-time {
  font-size: 11px;
  }

  .status-indicator {
  width: 8px;
  height: 8px;
  }

  /* 按钮容器移动端适配 */
  .claude-button-container {
  grid-template-columns: repeat(2, 1fr); /* 改为2列布局 */
  gap: 6px;
  padding-top: 10px;
  }

  .claude-button {
  padding: 10px 8px;
  font-size: 12px;
  gap: 3px;
  }

  /* 模态框移动端适配 */
  .claude-modal {
  padding: 10px;
  }

  .claude-modal-content {
  width: 100%;
  max-width: calc(100vw - 20px);
  max-height: calc(100vh - 40px);
  padding: 15px;
  margin: 0;
  }

  .claude-modal-content.narrow-modal {
  width: 100%;
  max-width: calc(100vw - 20px);
  }

  .claude-modal h2 {
  font-size: 16px;
  margin-bottom: 12px;
  }

  .claude-modal input,
  .claude-modal textarea {
  padding: 12px;
  font-size: 16px; /* 防止iOS缩放 */
  border-radius: 6px;
  }

  .claude-modal-buttons {
  flex-direction: column-reverse;
  gap: 8px;
  }

  .claude-modal-buttons .claude-button {
  width: 100%;
  padding: 12px;
  font-size: 14px;
  }

  /* WebDAV表单移动端优化 */
  .input-group {
  flex-direction: column !important;
  gap: 5px !important;
  }

  .input-group label {
  min-width: auto !important;
  text-align: left !important;
  font-size: 14px;
  }

  .claude-webdav-actions {
  grid-template-columns: 1fr !important;
  gap: 8px;
  }

  /* 预览容器移动端优化 */
  .claude-preview-container {
  max-height: 150px;
  padding: 10px;
  }

  .claude-preview-title {
  font-size: 14px;
  }

  .claude-preview-item {
  font-size: 12px;
  padding: 6px;
  }

  /* 信息提示移动端优化 */
  .claude-info-section {
  font-size: 10px !important;
  padding: 6px !important;
  }

  /* 滚动提示移动端优化 */
  .scroll-indicator {
  padding: 6px;
  font-size: 11px;
  }

  /* 移动端触摸优化 */
  .claude-token-item,
  .claude-button,
  .token-action-btn {
  min-height: 44px; /* iOS推荐的最小触摸区域 */
  }

  /* 命名规则容器移动端适配 */
  .claude-naming-rule {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  }

  .claude-naming-rule label {
  font-size: 14px;
  }

  .claude-naming-rule input {
  padding: 10px;
  font-size: 16px;
  }
  }

  /* 超小屏幕适配 (小于480px) */
  @media (max-width: 480px) {
  .claude-dropdown-container {
  top: 5px !important;
  left: 5px !important;
  right: 5px !important;
  padding: 12px;
  }

  .claude-button-container {
  grid-template-columns: 1fr; /* 超小屏幕单列 */
  }

  .claude-token-grid {
  max-height: calc(2 * (80px + 10px) + 20px); /* 两行token */
  }
  }
  `;

  const UI = {
    createElem(tag, className = "", styles = {}) {
      const elem = document.createElement(tag);
      if (className) elem.className = className;
      Object.assign(elem.style, styles);
      return elem;
    },

    createButton(text, className, icon = "") {
      const button = this.createElem("button", className);
      if (icon) {
        button.innerHTML = `${icon} ${text}`;
      } else {
        button.textContent = text;
      }
      return button;
    },

    createModal(title, content, includeCloseButton = true) {
      const modal = this.createElem("div", "claude-modal");
      modal.setAttribute("aria-modal", "true");
      modal.setAttribute("role", "dialog");

      const modalContent = this.createElem("div", "claude-modal-content");

      const titleElem = this.createElem("h2");
      titleElem.textContent = title;
      modalContent.appendChild(titleElem);

      if (includeCloseButton) {
        const closeButton = this.createElem("button", "claude-close-button");
        closeButton.textContent = "×";
        closeButton.addEventListener("click", () =>
          document.body.removeChild(modal)
        );
        modalContent.appendChild(closeButton);
      }

      modalContent.appendChild(content);

      const buttonContainer = this.createElem("div", "claude-modal-buttons");
      modalContent.appendChild(buttonContainer);

      modal.appendChild(modalContent);
      document.body.appendChild(modal);

      return {
        modal,
        buttonContainer,
        close: () => document.body.removeChild(modal),
      };
    },
  };

  const App = {
    init() {
      this.isDarkMode =
        document.documentElement.getAttribute("data-mode") === "dark";
      this.injectStyles();
      this.tokens = this.loadTokens();
      this.createUI();
      this.setupEventListeners();
      this.observeThemeChanges();

      // 获取保存的位置或使用默认值
      const savedPosition = {
        left: GM_getValue("buttonLeft", 10),
        bottom: GM_getValue("buttonBottom", 10),
      };

      // 设置按钮位置
      this.toggleButton.style.left = `${savedPosition.left}px`;
      this.toggleButton.style.bottom = `${savedPosition.bottom}px`;

      // 初始化拖拽状态
      this.isDragging = false;
      this.buttonLeft = savedPosition.left;
      this.buttonBottom = savedPosition.bottom;

      // 获取IP信息
      this.fetchIPCountryCode();

      // 添加窗口大小变化监听
      window.addEventListener("resize", () => {
        const isMobile = window.innerWidth <= 768;

        if (isMobile) {
          // 移动端时重置按钮位置
          this.toggleButton.style.right = "10px";
          this.toggleButton.style.top = "10px";
          this.toggleButton.style.left = "auto";

          // 如果下拉窗口可见,重新定位
          if (this.state.isDropdownVisible) {
            this.dropdownContainer.style.top = "10px";
            this.dropdownContainer.style.left = "10px";
            this.dropdownContainer.style.right = "10px";
            this.dropdownContainer.style.bottom = "auto";
            this.dropdownContainer.style.width = "auto";
          }
        } else {
          // 桌面端恢复保存的位置
          const savedPosition = {
            left: GM_getValue("buttonLeft", 10),
            bottom: GM_getValue("buttonBottom", 10),
          };
          this.toggleButton.style.left = `${savedPosition.left}px`;
          this.toggleButton.style.bottom = `${savedPosition.bottom}px`;
          this.toggleButton.style.right = "auto";

          // 如果下拉窗口可见,重新计算位置
          if (this.state.isDropdownVisible) {
            this.updateDropdownPosition();
          }
        }
      });
    },

    injectStyles() {
      this.styleElem = document.createElement("style");
      this.styleElem.textContent = getStyles(this.isDarkMode);
      document.head.appendChild(this.styleElem);
    },

    updateStyles() {
      this.styleElem.textContent = getStyles(this.isDarkMode);
    },

    loadTokens() {
      try {
        const savedTokens = GM_getValue(config.storageKey);
        let tokens =
          savedTokens && savedTokens.length > 0
            ? savedTokens
            : [config.defaultToken];

        // 为没有创建时间的token添加默认值
        tokens = tokens.map((token) => {
          if (!token.createdAt) {
            const now = new Date();
            return {
              ...token,
              createdAt: now.toLocaleString("zh-CN", {
                month: "2-digit",
                day: "2-digit",
                hour: "2-digit",
                minute: "2-digit",
              }),
              timestamp: now.getTime(),
            };
          }
          return token;
        });

        return tokens;
      } catch (error) {
        console.error("加载 tokens 失败:", error);
        return [config.defaultToken];
      }
    },

    saveTokens() {
      try {
        GM_setValue(config.storageKey, this.tokens);
      } catch (error) {
        console.error("保存 tokens 失败:", error);
        alert("保存 tokens 失败,请重试。");
      }
    },

    createUI() {
      // 创建浮动按钮
      this.toggleButton = UI.createElem("button", "claude-toggle-button");
      this.toggleButton.id = "claude-toggle-button";

      // 移动端检测并设置按钮位置
      const isMobile = window.innerWidth <= 768;
      if (isMobile) {
        // 移动端固定在右下角
        this.toggleButton.style.right = "20px";
        this.toggleButton.style.bottom = "20px";
        this.toggleButton.style.left = "auto";
      } else {
        // 桌面端使用保存的位置
        const savedPosition = {
          left: GM_getValue("buttonLeft", 10),
          bottom: GM_getValue("buttonBottom", 10),
        };
        this.toggleButton.style.left = `${savedPosition.left}px`;
        this.toggleButton.style.bottom = `${savedPosition.bottom}px`;
      }
      this.toggleButton.innerHTML = `
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" pointer-events="none">
      <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" pointer-events="none"></path>
      </svg>
      `;
      document.body.appendChild(this.toggleButton);

      // 创建下拉容器
      this.dropdownContainer = UI.createElem(
        "div",
        "claude-dropdown-container"
      );
      document.body.appendChild(this.dropdownContainer);

      // 创建标题容器
      const titleContainer = UI.createElem("div", "claude-title-container");

      const title = UI.createElem("h2");
      title.textContent = "Claude Session Key 管理器";

      this.ipDisplay = UI.createElem("div", "claude-ip-display");
      this.ipDisplay.textContent = "IP: 加载中...";

      titleContainer.appendChild(title);
      titleContainer.appendChild(this.ipDisplay);
      this.dropdownContainer.appendChild(titleContainer);

      // 创建 Token 网格
      this.tokenGrid = UI.createElem("div", "claude-token-grid");
      this.dropdownContainer.appendChild(this.tokenGrid);

      // 更新 Token 网格
      this.updateTokenGrid();

      // 创建按钮容器
      const buttonContainer = UI.createElem("div", "claude-button-container");

      // 测试所有按钮
      const testAllButton = UI.createButton(
        "测活",
        "claude-button primary",
        "🔍"
      );
      testAllButton.setAttribute("data-tooltip", "测试所有Token是否有效");
      testAllButton.addEventListener("click", () => this.testAllTokens());
      buttonContainer.appendChild(testAllButton);

      // 清理无效按钮
      const cleanInvalidButton = UI.createButton(
        "清理",
        "claude-button secondary",
        "🗑️"
      );
      cleanInvalidButton.setAttribute("data-tooltip", "清理所有无效的Token");
      cleanInvalidButton.addEventListener("click", () =>
        this.removeInvalidTokens()
      );
      buttonContainer.appendChild(cleanInvalidButton);

      // 添加 Token 按钮
      const addTokenButton = UI.createButton(
        "添加",
        "claude-button secondary",
        "➕"
      );
      addTokenButton.setAttribute("data-tooltip", "添加新的Token");
      addTokenButton.addEventListener("click", () => this.showAddTokenModal());
      buttonContainer.appendChild(addTokenButton);

      // 批量导入按钮
      const importButton = UI.createButton(
        "导入",
        "claude-button secondary",
        "📥"
      );
      importButton.setAttribute("data-tooltip", "批量导入多个Token");
      importButton.addEventListener("click", () => this.showBulkImportModal());
      buttonContainer.appendChild(importButton);

      // 批量导出按钮
      const exportButton = UI.createButton(
        "导出",
        "claude-button secondary",
        "📤"
      );
      exportButton.setAttribute("data-tooltip", "导出所有Token");
      exportButton.addEventListener("click", () => this.exportTokens());
      buttonContainer.appendChild(exportButton);

      // WebDAV备份按钮
      const webdavButton = UI.createButton(
        "云备份",
        "claude-button secondary",
        "☁️"
      );
      webdavButton.setAttribute("data-tooltip", "WebDAV云备份与恢复");
      webdavButton.addEventListener("click", () => this.showWebDAVModal());
      buttonContainer.appendChild(webdavButton);

      this.dropdownContainer.appendChild(buttonContainer);

      // 添加信息提示
      const infoSection = UI.createElem("div", "claude-info-section", {
        marginTop: "10px",
        padding: "8px",
        backgroundColor: "#f8f9fa",
        borderRadius: "6px",
        fontSize: "11px",
        color: "#666",
        textAlign: "center",
      });
      infoSection.innerHTML =
        "悬停显示面板 • 拖拽按钮调整位置 • 支持WebDAV云备份";
      this.dropdownContainer.appendChild(infoSection);
    },

    updateTokenGrid() {
      this.tokenGrid.innerHTML = "";

      // 获取当前使用的 token
      const currentTokenName = GM_getValue(config.currentTokenKey);

      // 加载测试结果
      const testResults = this.loadTestResults();

      this.tokens.forEach((token, index) => {
        const tokenItem = UI.createElem("div", "claude-token-item");
        if (token.name === currentTokenName) {
          tokenItem.classList.add("current-token");

          // 添加选中标记
          const currentBadge = UI.createElem("div", "current-token-badge");
          tokenItem.appendChild(currentBadge);
        }

        // Token 信息容器
        const tokenInfo = UI.createElem("div", "token-info");

        // 顶部行:名称和操作按钮
        const topRow = UI.createElem("div", "token-top-row");

        // 名称容器
        const nameContainer = UI.createElem("div", "token-name-container");

        const numberBadge = UI.createElem("span", "token-number");
        numberBadge.textContent = `#${(index + 1).toString().padStart(2, "0")}`;

        const nameSpan = UI.createElem("span", "token-name");
        nameSpan.textContent = token.name;

        nameContainer.appendChild(numberBadge);
        nameContainer.appendChild(nameSpan);

        // 操作按钮
        const actions = UI.createElem("div", "token-actions");

        // 编辑按钮
        const editButton = UI.createElem("button", "token-action-btn edit-btn");
        editButton.innerHTML = `
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
        </svg>
        `;
        editButton.dataset.index = index;
        editButton.addEventListener("click", (e) => {
          e.stopPropagation();
          this.showEditTokenModal(index);
        });

        // 删除按钮
        const deleteButton = UI.createElem(
          "button",
          "token-action-btn delete-btn"
        );
        deleteButton.innerHTML = `
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M3 6h18"></path>
        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path>
        <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
        </svg>
        `;
        deleteButton.dataset.index = index;
        deleteButton.addEventListener("click", (e) => {
          e.stopPropagation();
          this.confirmDeleteToken(index);
        });

        actions.appendChild(editButton);
        actions.appendChild(deleteButton);

        topRow.appendChild(nameContainer);
        topRow.appendChild(actions);

        // 底部行:状态和时间
        const bottomRow = UI.createElem("div", "token-bottom-row");

        // 添加时间戳(使用token的创建时间)
        const timeSpan = UI.createElem("span", "token-time");
        timeSpan.textContent = token.createdAt || "";
        bottomRow.appendChild(timeSpan);

        // 状态指示器
        const status = UI.createElem("div", "token-status");
        const statusIndicator = UI.createElem("div", "status-indicator");

        // 检查缓存的测试结果
        const testResult = testResults[token.key];
        if (testResult) {
          statusIndicator.classList.add(testResult.status);
        }

        status.appendChild(statusIndicator);
        status.addEventListener("click", async (e) => {
          e.stopPropagation();
          await this.testSingleToken(token, statusIndicator, bottomRow);
        });

        // 如果有缓存的账号类型,添加标签到状态容器内
        if (testResult && testResult.accountType) {
          const accountTypeLabel = UI.createElem('span', 'account-type-label');
          accountTypeLabel.textContent = testResult.accountType;
          status.insertBefore(accountTypeLabel, statusIndicator);
        }

        bottomRow.appendChild(status);

        // 将行添加到信息容器
        tokenInfo.appendChild(topRow);
        tokenInfo.appendChild(bottomRow);

        // 将信息容器添加到 token 项
        tokenItem.appendChild(tokenInfo);

        // 点击切换 token
        tokenItem.addEventListener("click", () => this.switchToToken(token));

        // 将 token 项添加到网格
        this.tokenGrid.appendChild(tokenItem);
      });

      // 如果token数量超过4个(两行),添加滚动提示
      if (this.tokens.length > 4) {
        const scrollIndicator = UI.createElem("div", "scroll-indicator");
        scrollIndicator.innerHTML = `
        <div class="scroll-text">向下滚动查看更多 (${
          this.tokens.length - 4
        })</div>
        <div class="scroll-arrow">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M7 13l5 5 5-5"></path>
        <path d="M7 6l5 5 5-5"></path>
        </svg>
        </div>
        `;
        this.tokenGrid.appendChild(scrollIndicator);
      }
    },

    async switchToToken(token) {
      // 检查是否有缓存的测试结果
      const cachedResult = this.getTestResult(token.key);

      // 如果有缓存的测试结果且为无效,提示用户并询问是否继续
      if (cachedResult && cachedResult.status === "error") {
        const confirmResult = await this.showConfirmDialog(
          "警告",
          `该 Token "${token.name}" 已被标记为无效,是否仍要切换到该 Token?`
        );

        if (!confirmResult) {
          return;
        }
      }

      // 应用 token
      this.applyToken(token.key);
      GM_setValue(config.currentTokenKey, token.name);

      // 隐藏下拉菜单
      this.hideDropdown();
    },

    applyToken(token) {
      const currentURL = window.location.href;

      if (currentURL.startsWith("https://claude.ai/")) {
        GM_cookie.set(
          {
            name: "sessionKey",
            value: token,
            domain: ".claude.ai",
            path: "/",
            secure: true,
            sameSite: "lax",
          },
          function () {
            window.location.reload();
          }
        );
      } else {
        let loginUrl;
        const hostname = new URL(currentURL).hostname;
        if (hostname !== "claude.ai") {
          loginUrl = `https://${hostname}/login_token?session_key=${token}`;
        }

        if (loginUrl) {
          window.location.href = loginUrl;
        }
      }
    },

    async testSingleToken(token, statusIndicator, bottomRow) {
      // 显示加载状态
      statusIndicator.className = "status-indicator loading";

      // 测试 token
      const result = await this.testToken(token.key);

      // 保存测试结果
      this.saveTestResult(token.key, result);

      // 更新状态指示器
      statusIndicator.className = `status-indicator ${result.status}`;

      // 添加或更新账号类型标签
      this.updateAccountTypeLabel(bottomRow, result);

      // 不再更新时间戳,保持显示token的创建时间
    },

    // 更新账号类型标签
    updateAccountTypeLabel(bottomRow, result) {
      // 移除现有的账号类型标签
      const existingLabel = bottomRow.querySelector('.account-type-label');
      if (existingLabel) {
        existingLabel.remove();
      }

      // 如果测试成功且有账号类型信息,添加标签
      if (result.status === 'success' && result.accountType) {
        const accountTypeLabel = document.createElement('span');
        accountTypeLabel.className = 'account-type-label';
        accountTypeLabel.textContent = result.accountType;
        
        // 找到状态容器并在状态指示器前插入标签
        const statusContainer = bottomRow.querySelector('.token-status');
        const statusIndicator = bottomRow.querySelector('.status-indicator');
        if (statusContainer && statusIndicator) {
          statusContainer.insertBefore(accountTypeLabel, statusIndicator);
        }
      }
    },

    async testAllTokens() {
      // 获取所有 token 项
      const tokenItems = this.tokenGrid.querySelectorAll(".claude-token-item");

      // 禁用测试按钮
      const testButton = this.dropdownContainer.querySelector(
        ".claude-button.primary"
      );
      testButton.disabled = true;
      testButton.textContent = "测试中...";

      // 清除所有缓存的测试结果
      GM_setValue(config.testResultsKey, {});

      const tokens = Array.from(tokenItems);

      // 按4个一组处理所有tokens
      for (let i = 0; i < tokens.length; i += 4) {
        // 取出当前4个(或更少)token
        const currentChunk = tokens.slice(i, Math.min(i + 4, tokens.length));

        // 并行处理这最多4个token
        await Promise.all(
          currentChunk.map(async (tokenItem) => {
            const index = Array.from(tokenItems).indexOf(tokenItem);
            const token = this.tokens[index];
            const statusIndicator =
              tokenItem.querySelector(".status-indicator");
            const bottomRow = tokenItem.querySelector(".token-bottom-row");

            await this.testSingleToken(token, statusIndicator, bottomRow);
          })
        );
      }

      // 恢复测试按钮
      testButton.disabled = false;
      testButton.innerHTML = "🔍 测活";
    },

    async testToken(key) {
      return new Promise((resolve) => {
        GM_xmlhttpRequest({
          method: "GET",
          url: "https://claude.ai/api/organizations",
          headers: {
            accept:
              "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
            "accept-language": "en-US,en;q=0.9",
            "cache-control": "max-age=0",
            cookie: `sessionKey=${key}`,
            "user-agent": "Mozilla/5.0 (X11; Linux x86_64)",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
          },
          onload: (response) => {
            try {
              if (response.status !== 200) {
                resolve({
                  status: "error",
                  message: "无效",
                });
                return;
              }

              const responseText = response.responseText;

              if (responseText.toLowerCase().includes("unauthorized")) {
                resolve({
                  status: "error",
                  message: "无效",
                });
                return;
              }

              if (responseText.trim() === "") {
                resolve({
                  status: "error",
                  message: "无响应",
                });
                return;
              }

              try {
                const objects = JSON.parse(responseText);
                if (objects && objects.length > 0) {
                  // 解析账号类型
                  const accountType = this.parseAccountType(objects[0]);
                  resolve({
                    status: "success",
                    message: "有效",
                    accountType: accountType,
                    organizationData: objects[0]
                  });
                  return;
                }
              } catch (e) {
                resolve({
                  status: "error",
                  message: "解析失败",
                });
                return;
              }

              resolve({
                status: "error",
                message: "无效数据",
              });
            } catch (error) {
              console.error("解析响应时发生错误:", error);
              resolve({
                status: "error",
                message: "测试失败",
              });
            }
          },
          onerror: (error) => {
            console.error("请求发生错误:", error);
            resolve({
              status: "error",
              message: "网络错误",
            });
          },
          ontimeout: () => {
            resolve({
              status: "error",
              message: "超时",
            });
          },
        });
      });
    },

    // 解析账号类型
    parseAccountType(organizationData) {
      const rateLimitTier = organizationData.rate_limit_tier;
      const capabilities = organizationData.capabilities || [];
      const billingType = organizationData.billing_type;
      const apiDisabledReason = organizationData.api_disabled_reason;
      
      // 根据 rate_limit_tier 判断账号类型
      if (rateLimitTier === "default_claude_max_5x") {
        return "Max(5x)";
      } else if (rateLimitTier === "default_claude_max_20x") {
        return "Max(20x)";
      } else if (rateLimitTier === "default_claude_ai") {
        return "Free";
      } else if (rateLimitTier === "auto_api_evaluation") {
        if (apiDisabledReason === "out_of_credits") {
          return "API(无额度)";
        }
        return "API";
      } else if (capabilities.includes("claude_max")) {
        return "Max";
      } else if (capabilities.includes("api")) {
        return "API";
      } else if (capabilities.includes("chat")) {
        return "Free";
      }
      
      return "未知";
    },

    loadTestResults() {
      try {
        const cached = GM_getValue(config.testResultsKey, {});
        const now = Date.now();
        // 清理过期的测试结果
        const filtered = Object.entries(cached).reduce((acc, [key, value]) => {
          if (now - value.timestamp < config.testResultExpiry) {
            acc[key] = value;
          }
          return acc;
        }, {});
        return filtered;
      } catch (error) {
        console.error("加载测试结果缓存失败:", error);
        return {};
      }
    },

    saveTestResult(key, result) {
      try {
        const testResults = this.loadTestResults();
        const now = new Date();
        // 统一使用简短时间格式
        const formattedTime = now.toLocaleString("zh-CN", {
          month: "2-digit",
          day: "2-digit",
          hour: "2-digit",
          minute: "2-digit",
        });

        testResults[key] = {
          status: result.status,
          message: result.message,
          timestamp: now.getTime(),
          testTime: formattedTime, // 保存简短格式的时间
          accountType: result.accountType, // 保存账号类型
          organizationData: result.organizationData // 保存组织信息
        };
        GM_setValue(config.testResultsKey, testResults);
      } catch (error) {
        console.error("保存测试结果失败:", error);
      }
    },

    getTestResult(key) {
      const testResults = this.loadTestResults();
      return testResults[key];
    },

    async removeInvalidTokens() {
      const confirmResult = await this.showConfirmDialog(
        "确认清理",
        "是否删除所有无效的 Tokens?此操作不可撤销。"
      );

      if (!confirmResult) return;

      const testResults = this.loadTestResults();
      const validTokens = this.tokens.filter((token) => {
        const result = testResults[token.key];
        return !result || result.status === "success";
      });

      if (validTokens.length === this.tokens.length) {
        alert("没有发现无效的 Tokens");
        return;
      }

      this.tokens = validTokens;
      this.saveTokens();
      this.updateTokenGrid();
    },

    showAddTokenModal() {
      const content = UI.createElem("div", "claude-add-token-form");

      const nameInput = UI.createElem("input");
      nameInput.placeholder = "Token 名称";
      nameInput.setAttribute("aria-label", "Token 名称");

      const keyInput = UI.createElem("input");
      keyInput.placeholder = "Token 密钥";
      keyInput.setAttribute("aria-label", "Token 密钥");

      content.appendChild(nameInput);
      content.appendChild(keyInput);

      const { modal, buttonContainer, close } = UI.createModal(
        "添加 Token",
        content
      );
      modal
        .querySelector(".claude-modal-content")
        .classList.add("narrow-modal");

      const addButton = UI.createButton("添加", "claude-button primary");
      addButton.addEventListener("click", () => {
        if (this.validateInput(nameInput.value, keyInput.value)) {
          // 获取当前时间并格式化
          const now = new Date();
          const formattedTime = now.toLocaleString("zh-CN", {
            month: "2-digit",
            day: "2-digit",
            hour: "2-digit",
            minute: "2-digit",
          });

          this.tokens.push({
            name: nameInput.value,
            key: keyInput.value,
            createdAt: formattedTime, // 添加创建时间
            timestamp: now.getTime(), // 添加时间戳用于排序
          });

          this.saveTokens();
          this.updateTokenGrid();
          close();
        }
      });

      const cancelButton = UI.createButton("取消", "claude-button secondary");
      cancelButton.addEventListener("click", close);

      buttonContainer.appendChild(cancelButton);
      buttonContainer.appendChild(addButton);
    },

    showEditTokenModal(index) {
      const token = this.tokens[index];
      const content = UI.createElem("div", "claude-edit-token-form");

      const nameInput = UI.createElem("input");
      nameInput.value = token.name;
      nameInput.placeholder = "Token 名称";

      const keyInput = UI.createElem("input");
      keyInput.value = token.key;
      keyInput.placeholder = "Token 密钥";

      content.appendChild(nameInput);
      content.appendChild(keyInput);

      const { modal, buttonContainer, close } = UI.createModal(
        "编辑 Token",
        content
      );
      modal
        .querySelector(".claude-modal-content")
        .classList.add("narrow-modal");

      const saveButton = UI.createButton("保存", "claude-button primary");
      saveButton.addEventListener("click", () => {
        if (this.validateInput(nameInput.value, keyInput.value)) {
          // 保留原有的创建时间和时间戳
          this.tokens[index] = {
            name: nameInput.value,
            key: keyInput.value,
            createdAt: token.createdAt || "",
            // 保留原有的创建时间
            timestamp: token.timestamp || Date.now(), // 保留原有的时间戳
          };

          this.saveTokens();
          this.updateTokenGrid();
          close();
        }
      });

      const cancelButton = UI.createButton("取消", "claude-button secondary");
      cancelButton.addEventListener("click", close);

      buttonContainer.appendChild(cancelButton);
      buttonContainer.appendChild(saveButton);
    },

    confirmDeleteToken(index) {
      const token = this.tokens[index];
      const content = UI.createElem("div", "claude-delete-confirm");

      content.innerHTML = `
      <div style="font-size: 48px; text-align: center; margin-bottom: 16px;">⚠️</div>
      <div style="font-size: 18px; font-weight: 600; text-align: center; margin-bottom: 12px;">删除确认</div>
      <div style="text-align: center; margin-bottom: 24px;">
      您确定要删除 Token "${token.name}" 吗?<br>
      此操作无法撤销。
      </div>
      `;

      const { modal, buttonContainer, close } = UI.createModal("", content);
      modal
        .querySelector(".claude-modal-content")
        .classList.add("narrow-modal");

      const deleteButton = UI.createButton("删除", "claude-button primary");
      deleteButton.style.backgroundColor = "#e53e3e";
      deleteButton.addEventListener("click", () => {
        this.deleteToken(index);
        close();
      });

      const cancelButton = UI.createButton("取消", "claude-button secondary");
      cancelButton.addEventListener("click", close);

      buttonContainer.appendChild(cancelButton);
      buttonContainer.appendChild(deleteButton);
    },

    deleteToken(index) {
      this.tokens.splice(index, 1);
      this.saveTokens();
      this.updateTokenGrid();
    },

    showBulkImportModal() {
      const content = UI.createElem("div", "claude-bulk-import-form");

      // 文本区域标签
      const textareaLabel = UI.createElem("label");
      textareaLabel.innerHTML =
        "<strong>1️⃣ Tokens 粘贴区:</strong><br>在这里粘贴您需要导入的 Tokens,每行一个!";
      content.appendChild(textareaLabel);

      // 文本区域
      const textarea = UI.createElem("textarea");
      textarea.rows = 10;
      content.appendChild(textarea);

      // 命名规则容器
      const namingRuleContainer = UI.createElem("div", "claude-naming-rule");

      // 命名规则标签
      const namingRuleLabel = UI.createElem("label");
      namingRuleLabel.innerHTML = "<strong>2️⃣ Tokens 命名规则:</strong>";
      namingRuleContainer.appendChild(namingRuleLabel);

      // 名称前缀
      const prefixLabel = UI.createElem("label");
      prefixLabel.textContent = "名称前缀:";
      namingRuleContainer.appendChild(prefixLabel);

      const prefixInput = UI.createElem("input");
      prefixInput.value = "token";
      namingRuleContainer.appendChild(prefixInput);

      // 起始编号
      const startNumberLabel = UI.createElem("label");
      startNumberLabel.textContent = "名称起始编号:";
      namingRuleContainer.appendChild(startNumberLabel);

      const startNumberInput = UI.createElem("input");
      startNumberInput.type = "number";
      startNumberInput.value = "1";
      namingRuleContainer.appendChild(startNumberInput);

      content.appendChild(namingRuleContainer);

      // 预览容器
      const previewLabel = UI.createElem("label");
      previewLabel.innerHTML = "<strong>3️⃣ Tokens 导入结果预览:</strong>";
      content.appendChild(previewLabel);

      const previewContainer = UI.createElem("div", "claude-preview-container");
      content.appendChild(previewContainer);

      const { modal, buttonContainer, close } = UI.createModal(
        "批量导入 Tokens",
        content
      );

      const importButton = UI.createButton("导入", "claude-button primary");
      importButton.addEventListener("click", () => {
        this.performBulkImport(
          textarea.value,
          prefixInput.value,
          startNumberInput.value
        );
        close();
      });

      const cancelButton = UI.createButton("取消", "claude-button secondary");
      cancelButton.addEventListener("click", close);

      buttonContainer.appendChild(cancelButton);
      buttonContainer.appendChild(importButton);

      // 更新预览
      const updatePreview = () => {
        this.previewBulkImport(
          textarea.value,
          prefixInput.value,
          startNumberInput.value,
          previewContainer
        );
      };

      [textarea, prefixInput, startNumberInput].forEach((elem) => {
        elem.addEventListener("input", updatePreview);
      });

      // 初始化预览
      updatePreview();
    },

    previewBulkImport(input, namePrefix, startNumber, previewContainer) {
      previewContainer.innerHTML = "";

      const tokens = this.parseTokens(input);
      const namedTokens = this.applyNamingRule(
        tokens,
        namePrefix,
        parseInt(startNumber)
      );

      const previewTitle = UI.createElem("div", "claude-preview-title");
      previewTitle.textContent = "请核对下方导入结果:";
      previewContainer.appendChild(previewTitle);

      if (namedTokens.length === 0) {
        const emptyMessage = UI.createElem("div", "claude-preview-item");
        emptyMessage.textContent = "等待输入...";
        previewContainer.appendChild(emptyMessage);
        return;
      }

      namedTokens.forEach((token) => {
        const previewItem = UI.createElem("div", "claude-preview-item");
        previewItem.innerHTML = `
        <strong>${token.name}:</strong>
        <span style="font-family: monospace; word-break: break-all;">${token.key}</span>
        `;
        previewContainer.appendChild(previewItem);
      });
    },

    performBulkImport(input, namePrefix, startNumber) {
      const tokens = this.parseTokens(input);
      const namedTokens = this.applyNamingRule(
        tokens,
        namePrefix,
        parseInt(startNumber)
      );

      if (namedTokens.length === 0) {
        alert("没有有效的 Tokens 可导入");
        return;
      }

      this.tokens = [...this.tokens, ...namedTokens];
      this.saveTokens();
      this.updateTokenGrid();
    },

    parseTokens(input) {
      return input
        .split("\n")
        .map((line) => line.trim())
        .filter((line) => this.validateTokenKey(line))
        .map((key) => ({
          key,
        }));
    },

    applyNamingRule(tokens, namePrefix, startNumber) {
      return tokens.map((token, index) => {
        const number = startNumber + index;
        const name = `${namePrefix}${number.toString().padStart(2, "0")}`;
        return {
          ...token,
          name,
        };
      });
    },

    exportTokens() {
      const testResults = this.loadTestResults();
      const exportData = this.tokens.map((token) => {
        const testResult = testResults[token.key] || {};
        return {
          name: token.name,
          sessionKey: token.key,
          isValid:
            testResult.status === "success"
              ? true
              : testResult.status === "error"
              ? false
              : null,
          testTime: testResult.testTime || null,
          testMessage: testResult.message || null,
        };
      });

      // 创建并下载 JSON 文件
      const blob = new Blob([JSON.stringify(exportData, null, 2)], {
        type: "application/json",
      });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `claude_tokens_${
        new Date().toISOString().split("T")[0]
      }.json`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    },

    showWebDAVModal() {
      const content = UI.createElem("div", "claude-webdav-form");
      content.style.cssText = "width: 100%; max-width: 600px;";

      // 添加帮助信息
      const helpInfo = UI.createElem("div", "claude-webdav-help");
      helpInfo.style.cssText = `
      margin-bottom: 10px;
      padding: 12px;
      background-color: var(--bg-color);
      border: 1px solid var(--border-color);
      border-radius: 8px;
      font-size: 13px;
      color: var(--text-color);
      box-shadow: 0 2px 4px rgba(0,0,0,0.05);
      `;
      helpInfo.innerHTML = `
      <p style="margin: 0 0 10px 0; font-weight: 600; color: var(--text-color);">📝 WebDAV服务器设置说明:</p>
      <ul style="margin: 0; padding-left: 20px; line-height: 1.6;">
      <li>URL必须是完整的WebDAV路径,例如:https://dav.jianguoyun.com/dav/Claude/</li>
      <li>确保路径末尾有斜杠"/"</li>
      <li>如果遇到404错误,请确认路径是否存在</li>
      <li>坚果云用户请使用应用专用密码</li>
      </ul>
      `;
      content.appendChild(helpInfo);

      // 创建表单容器
      const formContainer = UI.createElem(
        "div",
        "claude-webdav-form-container"
      );
      formContainer.style.cssText = `
      display: grid;
      gap: 8px;
      margin-bottom: 15px;
      `;

      // WebDAV服务器URL输入
      const urlGroup = UI.createElem("div", "input-group");
      urlGroup.style.cssText = "display: flex; align-items: center; gap: 10px;";

      const urlLabel = UI.createElem("label");
      urlLabel.textContent = "WebDAV URL:";
      urlLabel.style.cssText =
        "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

      const urlInput = UI.createElem("input");
      urlInput.type = "text";
      urlInput.placeholder = "https://dav.jianguoyun.com/dav/Claude/";
      urlInput.value = GM_getValue("webdav_url", "");
      urlInput.style.cssText = `
      flex: 1;
      padding: 10px;
      border: 1px solid var(--border-color);
      border-radius: 6px;
      font-size: 14px;
      background-color: var(--bg-color);
      color: var(--text-color);
      transition: all 0.3s ease;
      `;

      urlGroup.appendChild(urlLabel);
      urlGroup.appendChild(urlInput);
      formContainer.appendChild(urlGroup);

      // 用户名输入
      const usernameGroup = UI.createElem("div", "input-group");
      usernameGroup.style.cssText =
        "display: flex; align-items: center; gap: 10px;";

      const usernameLabel = UI.createElem("label");
      usernameLabel.textContent = "用户名:";
      usernameLabel.style.cssText =
        "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

      const usernameInput = UI.createElem("input");
      usernameInput.type = "text";
      usernameInput.placeholder = "用户名";
      usernameInput.value = GM_getValue("webdav_username", "");
      usernameInput.style.cssText = `
      flex: 1;
      padding: 10px;
      border: 1px solid var(--border-color);
      border-radius: 6px;
      font-size: 14px;
      background-color: var(--bg-color);
      color: var(--text-color);
      transition: all 0.3s ease;
      `;

      usernameGroup.appendChild(usernameLabel);
      usernameGroup.appendChild(usernameInput);
      formContainer.appendChild(usernameGroup);

      // 密码输入
      const passwordGroup = UI.createElem("div", "input-group");
      passwordGroup.style.cssText =
        "display: flex; align-items: center; gap: 10px;";

      const passwordLabel = UI.createElem("label");
      passwordLabel.textContent = "密码:";
      passwordLabel.style.cssText =
        "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

      const passwordInput = UI.createElem("input");
      passwordInput.type = "password";
      passwordInput.placeholder = "密码";
      passwordInput.value = GM_getValue("webdav_password", "");
      passwordInput.style.cssText = `
      flex: 1;
      padding: 10px;
      border: 1px solid var(--border-color);
      border-radius: 6px;
      font-size: 14px;
      background-color: var(--bg-color);
      color: var(--text-color);
      transition: all 0.3s ease;
      `;

      passwordGroup.appendChild(passwordLabel);
      passwordGroup.appendChild(passwordInput);
      formContainer.appendChild(passwordGroup);

      // 文件名输入
      const filenameGroup = UI.createElem("div", "input-group");
      filenameGroup.style.cssText =
        "display: flex; align-items: center; gap: 10px;";

      const filenameLabel = UI.createElem("label");
      filenameLabel.textContent = "文件名:";
      filenameLabel.style.cssText =
        "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

      const filenameInput = UI.createElem("input");
      filenameInput.type = "text";
      filenameInput.placeholder = "claude_tokens.json";
      filenameInput.value = GM_getValue(
        "webdav_filename",
        "claude_tokens.json"
      );
      filenameInput.style.cssText = `
      flex: 1;
      padding: 10px;
      border: 1px solid var(--border-color);
      border-radius: 6px;
      font-size: 14px;
      background-color: var(--bg-color);
      color: var(--text-color);
      transition: all 0.3s ease;
      `;

      filenameGroup.appendChild(filenameLabel);
      filenameGroup.appendChild(filenameInput);
      formContainer.appendChild(filenameGroup);

      content.appendChild(formContainer);

      // 测试连接按钮
      const testConnectionButton = UI.createButton(
        "测试连接",
        "claude-button secondary"
      );
      testConnectionButton.style.cssText = `
      width: 100%;
      margin: 10px 0;
      padding: 10px;
      font-size: 14px;
      font-weight: 500;
      border-radius: 6px;
      background-color: var(--button-bg);
      color: var(--text-color);
      border: 1px solid var(--border-color);
      cursor: pointer;
      transition: all 0.3s ease;
      `;
      testConnectionButton.addEventListener("click", async () => {
        this.saveWebDAVSettings(
          urlInput.value,
          usernameInput.value,
          passwordInput.value,
          filenameInput.value
        );

        statusDisplay.style.display = "block";
        statusDisplay.textContent = "正在测试连接...";
        statusDisplay.style.backgroundColor = "var(--bg-color)";

        try {
          await this.checkWebDAVDirectory(
            urlInput.value,
            usernameInput.value,
            passwordInput.value
          );
          statusDisplay.textContent = "✅ 连接成功!目录存在且可访问。";
          statusDisplay.style.backgroundColor = "#d4edda";
        } catch (error) {
          statusDisplay.textContent = `❌ 连接失败: ${error.message}`;
          statusDisplay.style.backgroundColor = "#f8d7da";
        }
      });
      content.appendChild(testConnectionButton);

      // 状态显示
      const statusDisplay = UI.createElem("div", "claude-webdav-status");
      statusDisplay.style.cssText = `
      margin: 10px 0;
      padding: 10px;
      border-radius: 6px;
      font-size: 14px;
      text-align: center;
      display: none;
      transition: all 0.3s ease;
      `;
      content.appendChild(statusDisplay);

      // 操作按钮容器
      const actionsContainer = UI.createElem("div", "claude-webdav-actions");
      actionsContainer.style.cssText = `
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 10px;
      margin-top: 15px;
      `;

      // 备份按钮
      const backupButton = UI.createButton(
        "备份到WebDAV",
        "claude-button primary"
      );
      backupButton.style.cssText = `
      padding: 12px;
      font-size: 14px;
      font-weight: 500;
      border-radius: 6px;
      background-color: #b3462f;
      color: white;
      border: none;
      cursor: pointer;
      transition: all 0.3s ease;
      `;
      backupButton.addEventListener("click", async () => {
        this.saveWebDAVSettings(
          urlInput.value,
          usernameInput.value,
          passwordInput.value,
          filenameInput.value
        );

        statusDisplay.style.display = "block";
        statusDisplay.textContent = "正在备份...";
        statusDisplay.style.backgroundColor = "var(--bg-color)";

        try {
          await this.backupToWebDAV(
            urlInput.value,
            usernameInput.value,
            passwordInput.value,
            filenameInput.value
          );
          statusDisplay.textContent = "✅ 备份成功!";
          statusDisplay.style.backgroundColor = "#d4edda";
        } catch (error) {
          statusDisplay.textContent = `❌ 备份失败: ${error.message}`;
          statusDisplay.style.backgroundColor = "#f8d7da";
        }
      });

      // 恢复按钮
      const restoreButton = UI.createButton(
        "从WebDAV恢复",
        "claude-button secondary"
      );
      restoreButton.style.cssText = `
      padding: 12px;
      font-size: 14px;
      font-weight: 500;
      border-radius: 6px;
      background-color: var(--button-bg);
      color: var(--text-color);
      border: 1px solid var(--border-color);
      cursor: pointer;
      transition: all 0.3s ease;
      `;
      restoreButton.addEventListener("click", async () => {
        this.saveWebDAVSettings(
          urlInput.value,
          usernameInput.value,
          passwordInput.value,
          filenameInput.value
        );

        statusDisplay.style.display = "block";
        statusDisplay.textContent = "正在恢复...";
        statusDisplay.style.backgroundColor = "var(--bg-color)";

        try {
          await this.restoreFromWebDAV(
            urlInput.value,
            usernameInput.value,
            passwordInput.value,
            filenameInput.value
          );
          statusDisplay.textContent = "✅ 恢复成功!";
          statusDisplay.style.backgroundColor = "#d4edda";
          this.updateTokenGrid();
        } catch (error) {
          statusDisplay.textContent = `❌ 恢复失败: ${error.message}`;
          statusDisplay.style.backgroundColor = "#f8d7da";
        }
      });

      // 关闭按钮
      const closeButton = UI.createButton("关闭", "claude-button secondary");
      closeButton.style.cssText = `
      padding: 12px;
      font-size: 14px;
      font-weight: 500;
      border-radius: 6px;
      background-color: var(--button-bg);
      color: var(--text-color);
      border: 1px solid var(--border-color);
      cursor: pointer;
      transition: all 0.3s ease;
      `;
      closeButton.addEventListener("click", () => {
        modal.remove();
      });

      actionsContainer.appendChild(backupButton);
      actionsContainer.appendChild(restoreButton);
      actionsContainer.appendChild(closeButton);
      content.appendChild(actionsContainer);

      // 创建模态框
      const { modal, buttonContainer } = UI.createModal(
        "☁️ WebDAV备份与恢复",
        content,
        true
      );
      document.body.appendChild(modal);

      // 添加关闭按钮事件监听
      const closeBtn = modal.querySelector(".claude-close-button");
      if (closeBtn) {
        closeBtn.addEventListener("click", () => {
          document.body.removeChild(modal);
        });
      }
    },

    saveWebDAVSettings(url, username, password, filename) {
      GM_setValue("webdav_url", url);
      GM_setValue("webdav_username", username);
      GM_setValue("webdav_password", password);
      GM_setValue("webdav_filename", filename);
    },

    async backupToWebDAV(url, username, password, filename) {
      // 准备备份数据
      const testResults = this.loadTestResults();
      const exportData = this.tokens.map((token) => {
        const testResult = testResults[token.key] || {};
        return {
          name: token.name,
          sessionKey: token.key,
          isValid:
            testResult.status === "success"
              ? true
              : testResult.status === "error"
              ? false
              : null,
          testTime: testResult.testTime || null,
          testMessage: testResult.message || null,
        };
      });

      const jsonData = JSON.stringify(exportData, null, 2);

      // 确保URL以/结尾
      if (!url.endsWith("/")) {
        url += "/";
      }

      // 先检查目录是否存在
      try {
        await this.checkWebDAVDirectory(url, username, password);
      } catch (error) {
        // 如果是404错误,尝试创建目录
        if (error.message.includes("404")) {
          try {
            // 尝试创建父目录
            const parentUrl = url.substring(
              0,
              url.lastIndexOf("/", url.length - 2) + 1
            );
            if (parentUrl !== url) {
              await this.createWebDAVDirectory(
                parentUrl,
                username,
                password,
                url.substring(parentUrl.length, url.length - 1)
              );
            } else {
              throw new Error("无法确定父目录");
            }
          } catch (createError) {
            throw new Error(`目录不存在且无法创建: ${createError.message}`);
          }
        } else {
          throw error;
        }
      }

      // 发送到WebDAV服务器
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "PUT",
          url: url + filename,
          headers: {
            "Content-Type": "application/json",
            Authorization: "Basic " + btoa(username + ":" + password),
          },
          data: jsonData,
          onload: function (response) {
            if (response.status >= 200 && response.status < 300) {
              resolve();
            } else {
              console.error("WebDAV备份失败:", response);
              reject(
                new Error(
                  `HTTP错误: ${response.status} ${
                    response.statusText || ""
                  }\n响应: ${response.responseText || "无响应内容"}`
                )
              );
            }
          },
          onerror: function (error) {
            console.error("WebDAV备份网络错误:", error);
            reject(new Error(`网络错误: ${error.statusText || "连接失败"}`));
          },
        });
      });
    },

    // 检查WebDAV目录是否存在
    checkWebDAVDirectory(url, username, password) {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "PROPFIND",
          url: url,
          headers: {
            Depth: "0",
            Authorization: "Basic " + btoa(username + ":" + password),
          },
          onload: function (response) {
            if (response.status >= 200 && response.status < 300) {
              resolve();
            } else {
              reject(
                new Error(
                  `HTTP错误: ${response.status} ${response.statusText || ""}`
                )
              );
            }
          },
          onerror: function (error) {
            reject(new Error(`网络错误: ${error.statusText || "连接失败"}`));
          },
        });
      });
    },

    // 创建WebDAV目录
    createWebDAVDirectory(parentUrl, username, password, dirName) {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "MKCOL",
          url: parentUrl + dirName,
          headers: {
            Authorization: "Basic " + btoa(username + ":" + password),
          },
          onload: function (response) {
            if (response.status >= 200 && response.status < 300) {
              resolve();
            } else {
              reject(
                new Error(
                  `无法创建目录: HTTP错误 ${response.status} ${
                    response.statusText || ""
                  }`
                )
              );
            }
          },
          onerror: function (error) {
            reject(new Error(`网络错误: ${error.statusText || "连接失败"}`));
          },
        });
      });
    },

    async restoreFromWebDAV(url, username, password, filename) {
      // 确保URL以/结尾
      if (!url.endsWith("/")) {
        url += "/";
      }

      // 从WebDAV服务器获取数据
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "GET",
          url: url + filename,
          headers: {
            Authorization: "Basic " + btoa(username + ":" + password),
          },
          onload: (response) => {
            if (response.status >= 200 && response.status < 300) {
              try {
                const data = JSON.parse(response.responseText);

                // 转换数据格式
                const tokens = data.map((item) => ({
                  name: item.name,
                  key: item.sessionKey,
                  createdAt: new Date().toLocaleString("zh-CN", {
                    month: "2-digit",
                    day: "2-digit",
                    hour: "2-digit",
                    minute: "2-digit",
                  }),
                  timestamp: Date.now(),
                }));

                // 更新tokens
                this.tokens = tokens;
                this.saveTokens();

                resolve();
              } catch (error) {
                console.error(
                  "解析WebDAV数据失败:",
                  error,
                  response.responseText
                );
                reject(new Error(`解析数据失败: ${error.message}`));
              }
            } else {
              console.error("WebDAV恢复失败:", response);
              reject(
                new Error(
                  `HTTP错误: ${response.status} ${
                    response.statusText || ""
                  }\n响应: ${response.responseText || "无响应内容"}`
                )
              );
            }
          },
          onerror: function (error) {
            console.error("WebDAV恢复网络错误:", error);
            reject(new Error(`网络错误: ${error.statusText || "连接失败"}`));
          },
        });
      });
    },

    validateInput(name, key) {
      if (!name || !key) {
        alert("Token 名称和密钥都要填写!");
        return false;
      }
      // 移除对token名称的严格限制,允许更多字符,包括@和.
      // if (!/^[a-zA-Z0-9-_]+$/.test(name)) {
      //     alert("Token 名称只能包含字母、数字、下划线和连字符!");
      //     return false;
      // }
      if (!this.validateTokenKey(key)) {
        alert("无效的 Token 密钥格式!");
        return false;
      }
      return true;
    },

    validateTokenKey(key) {
      return /^sk-ant-sid\d{2}-[A-Za-z0-9_-]*$/.test(key);
    },

    showConfirmDialog(title, message) {
      return new Promise((resolve) => {
        const content = UI.createElem("div", "claude-confirm-dialog");

        content.innerHTML = `
        <div style="font-size: 48px; text-align: center; margin-bottom: 16px;">⚠️</div>
        <div style="font-size: 18px; font-weight: 600; text-align: center; margin-bottom: 12px;">${title}</div>
        <div style="text-align: center; margin-bottom: 24px;">${message}</div>
        `;

        const { modal, buttonContainer, close } = UI.createModal("", content);
        modal
          .querySelector(".claude-modal-content")
          .classList.add("narrow-modal");

        const confirmButton = UI.createButton("确认", "claude-button primary");
        confirmButton.addEventListener("click", () => {
          close();
          resolve(true);
        });

        const cancelButton = UI.createButton("取消", "claude-button secondary");
        cancelButton.addEventListener("click", () => {
          close();
          resolve(false);
        });

        buttonContainer.appendChild(cancelButton);
        buttonContainer.appendChild(confirmButton);
      });
    },

    fetchIPCountryCode() {
      this.ipDisplay.textContent = "IP: 加载中...";

      GM_xmlhttpRequest({
        method: "GET",
        url: config.ipApiUrl,
        onload: (response) => {
          if (response.status === 200) {
            this.ipDisplay.textContent = "IP: " + response.responseText.trim();
          } else {
            this.ipDisplay.textContent = "IP: 获取失败";
          }
        },
        onerror: () => {
          this.ipDisplay.textContent = "IP: 获取失败";
        },
      });
    },

    setupEventListeners() {
      // 拖拽相关事件
      this.toggleButton.addEventListener(
        "mousedown",
        this.onMouseDown.bind(this)
      );
      document.addEventListener("mousemove", this.onMouseMove.bind(this));
      document.addEventListener("mouseup", this.onMouseUp.bind(this));

      // 状态管理对象
      this.state = {
        isDropdownVisible: false,
        isDragging: false,
        isProcessingClick: false, // 处理点击状态
        isClosing: false, // 窗口正在关闭的状态
      };

      // 定时器
      this.closeTimeout = null;

      // 移除 hover 展开逻辑,改为纯点击展开

      // 按钮点击事件
      this.toggleButton.addEventListener("click", (e) => {
        if (this.state.isDragging) return; // 如果正在拖拽,忽略点击

        this.state.isProcessingClick = true;
        clearTimeout(this.closeTimeout);

        if (this.state.isDropdownVisible) {
          this.hideDropdown();
        } else {
          this.showDropdown();
        }

        setTimeout(() => {
          this.state.isProcessingClick = false;
        }, 100);
      });

      // 移除弹窗 hover 逻辑

      // 点击其他区域关闭下拉菜单
      document.addEventListener("click", (e) => {
        if (
          this.dropdownContainer.style.display === "flex" &&
          !this.dropdownContainer.contains(e.target) &&
          e.target !== this.toggleButton
        ) {
          this.hideDropdown();
        }
      });

      // 添加触摸事件支持
      const isMobile = window.innerWidth <= 768;

      if (isMobile) {
        // 移动端触摸事件
        this.toggleButton.addEventListener("touchstart", (e) => {
          e.preventDefault();
          this.state.isProcessingClick = true;
          clearTimeout(this.closeTimeout);

          if (this.state.isDropdownVisible) {
            this.hideDropdown();
          } else {
            this.showDropdown();
          }

          setTimeout(() => {
            this.state.isProcessingClick = false;
          }, 100);
        });

        // 点击其他区域关闭 - 触摸版本
        document.addEventListener("touchstart", (e) => {
          if (
            this.dropdownContainer.style.display === "flex" &&
            !this.dropdownContainer.contains(e.target) &&
            e.target !== this.toggleButton
          ) {
            this.hideDropdown();
          }
        });
      }
    },

    onMouseDown(e) {
      const isMobile = window.innerWidth <= 768;
      if (isMobile) {
        // 移动端禁用拖拽功能
        return;
      }

      if (e.button !== 0) return; // 只处理左键点击

      this.isDragging = true;
      this.state.isDragging = true;
      this.startX = e.clientX;
      this.startY = e.clientY;

      const rect = this.toggleButton.getBoundingClientRect();
      this.offsetX = this.startX - rect.left;
      this.offsetY = this.startY - rect.top;

      this.toggleButton.style.cursor = "grabbing";

      // 清除关闭定时器
      clearTimeout(this.closeTimeout);

      // 阻止默认行为和事件冒泡
      e.preventDefault();
      e.stopPropagation();
    },

    onMouseMove(e) {
      if (!this.isDragging) return;

      const x = e.clientX - this.offsetX;
      const y = e.clientY - this.offsetY;

      // 计算底部位置
      const bottom = window.innerHeight - y - this.toggleButton.offsetHeight;

      // 确保按钮在窗口范围内
      const maxX = window.innerWidth - this.toggleButton.offsetWidth;
      const maxBottom = window.innerHeight - this.toggleButton.offsetHeight;

      this.buttonLeft = Math.max(0, Math.min(x, maxX));
      this.buttonBottom = Math.max(0, Math.min(bottom, maxBottom));

      // 更新按钮位置
      this.toggleButton.style.left = `${this.buttonLeft}px`;
      this.toggleButton.style.bottom = `${this.buttonBottom}px`;
      this.toggleButton.style.top = "auto";

      // 如果下拉窗口可见,更新其位置
      if (this.state.isDropdownVisible) {
        this.updateDropdownPosition();
      }

      e.preventDefault();
    },

    // 智能下拉窗口位置计算方法
    updateDropdownPosition() {
      const buttonRect = this.toggleButton.getBoundingClientRect();
      const dropdownWidth = 600; // 下拉窗口宽度
      const margin = 10; // 边距
      
      // 获取窗口尺寸
      const windowWidth = window.innerWidth;
      const windowHeight = window.innerHeight;
      
      // 预估下拉窗口高度
      const dropdownHeight = Math.min(
        this.dropdownContainer.scrollHeight || 400,
        windowHeight * 0.8
      );
      
      // 计算按钮在屏幕中的位置比例
      const buttonCenterX = buttonRect.left + buttonRect.width / 2;
      const buttonCenterY = buttonRect.top + buttonRect.height / 2;
      
      // 检测按钮是否在各个角落区域
      const isLeftSide = buttonCenterX < windowWidth * 0.3;
      const isRightSide = buttonCenterX > windowWidth * 0.7;
      const isTopSide = buttonCenterY < windowHeight * 0.3;
      const isBottomSide = buttonCenterY > windowHeight * 0.7;
      
      // 计算各个方向的可用空间
      const spaceRight = windowWidth - buttonRect.right - margin;
      const spaceLeft = buttonRect.left - margin;
      const spaceBelow = windowHeight - buttonRect.bottom - margin;
      const spaceAbove = buttonRect.top - margin;
      
      let left, top;
      let preferredDirection = '';
      
      // 智能决策展开方向
      if (isLeftSide && isTopSide) {
        // 左上角:优先向右下展开
        if (spaceRight >= dropdownWidth && spaceBelow >= dropdownHeight) {
          left = buttonRect.right + margin;
          top = buttonRect.top;
          preferredDirection = 'right-down';
        } else if (spaceRight >= dropdownWidth) {
          left = buttonRect.right + margin;
          top = Math.max(margin, windowHeight - dropdownHeight - margin);
          preferredDirection = 'right-up';
        } else if (spaceBelow >= dropdownHeight) {
          left = Math.max(margin, buttonRect.left);
          top = buttonRect.bottom + margin;
          preferredDirection = 'down-right';
        } else {
          // 空间不足,居中显示
          left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
          top = Math.max(margin, (windowHeight - dropdownHeight) / 2);
          preferredDirection = 'center';
        }
      } else if (isRightSide && isTopSide) {
        // 右上角:优先向左下展开
        if (spaceLeft >= dropdownWidth && spaceBelow >= dropdownHeight) {
          left = buttonRect.left - dropdownWidth - margin;
          top = buttonRect.top;
          preferredDirection = 'left-down';
        } else if (spaceLeft >= dropdownWidth) {
          left = buttonRect.left - dropdownWidth - margin;
          top = Math.max(margin, windowHeight - dropdownHeight - margin);
          preferredDirection = 'left-up';
        } else if (spaceBelow >= dropdownHeight) {
          left = Math.max(margin, buttonRect.right - dropdownWidth);
          top = buttonRect.bottom + margin;
          preferredDirection = 'down-left';
        } else {
          left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
          top = Math.max(margin, (windowHeight - dropdownHeight) / 2);
          preferredDirection = 'center';
        }
      } else if (isLeftSide && isBottomSide) {
        // 左下角:优先向右上展开
        if (spaceRight >= dropdownWidth && spaceAbove >= dropdownHeight) {
          left = buttonRect.right + margin;
          // 确保向上展开时与按钮底部对齐,但不超出顶部边距
          top = Math.max(margin, buttonRect.bottom - dropdownHeight);
          preferredDirection = 'right-up';
        } else if (spaceRight >= dropdownWidth) {
          left = buttonRect.right + margin;
          // 如果空间不足,尽可能向上,但保持顶部边距
          top = Math.max(margin, Math.min(buttonRect.top, windowHeight - dropdownHeight - margin));
          preferredDirection = 'right-up';
        } else if (spaceAbove >= dropdownHeight) {
          left = Math.max(margin, buttonRect.left);
          // 向上展开时,确保与按钮顶部有间距,并保持顶部边距
          top = Math.max(margin, buttonRect.top - dropdownHeight - margin);
          preferredDirection = 'up-right';
        } else {
          left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
          top = Math.max(margin, (windowHeight - dropdownHeight) / 2);
          preferredDirection = 'center';
        }
      } else if (isRightSide && isBottomSide) {
        // 右下角:优先向左上展开
        if (spaceLeft >= dropdownWidth && spaceAbove >= dropdownHeight) {
          left = buttonRect.left - dropdownWidth - margin;
          // 确保向上展开时与按钮底部对齐,但不超出顶部边距
          top = Math.max(margin, buttonRect.bottom - dropdownHeight);
          preferredDirection = 'left-up';
        } else if (spaceLeft >= dropdownWidth) {
          left = buttonRect.left - dropdownWidth - margin;
          // 如果空间不足,尽可能向上,但保持顶部边距
          top = Math.max(margin, Math.min(buttonRect.top, windowHeight - dropdownHeight - margin));
          preferredDirection = 'left-up';
        } else if (spaceAbove >= dropdownHeight) {
          left = Math.max(margin, buttonRect.right - dropdownWidth);
          // 向上展开时,确保与按钮顶部有间距,并保持顶部边距
          top = Math.max(margin, buttonRect.top - dropdownHeight - margin);
          preferredDirection = 'up-left';
        } else {
          left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
          top = Math.max(margin, (windowHeight - dropdownHeight) / 2);
          preferredDirection = 'center';
        }
      } else {
        // 非角落区域,使用原有逻辑
        // 如果是底部区域,需要特殊处理向上展开
        if (isBottomSide) {
          // 底部区域:优先水平展开,如果空间不足则向上展开
          if (spaceRight >= dropdownWidth) {
            left = buttonRect.right + margin;
            // 底部向右展开时,尽量与按钮底部对齐,但确保不超出顶部
            top = Math.max(margin, buttonRect.bottom - dropdownHeight);
            preferredDirection = 'right-up';
          } else if (spaceLeft >= dropdownWidth) {
            left = buttonRect.left - dropdownWidth - margin;
            // 底部向左展开时,尽量与按钮底部对齐,但确保不超出顶部
            top = Math.max(margin, buttonRect.bottom - dropdownHeight);
            preferredDirection = 'left-up';
          } else {
            // 水平空间不足,居中向上展开
            left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
            // 确保向上展开时保持顶部边距
            top = Math.max(margin, buttonRect.top - dropdownHeight - margin);
            preferredDirection = 'center-up';
          }
        } else {
          // 非底部区域,使用原有逻辑
          // 优先右侧显示
          if (spaceRight >= dropdownWidth) {
            left = buttonRect.right + margin;
            top = Math.max(margin, Math.min(buttonRect.top, windowHeight - dropdownHeight - margin));
            preferredDirection = 'right';
          } else if (spaceLeft >= dropdownWidth) {
            left = buttonRect.left - dropdownWidth - margin;
            top = Math.max(margin, Math.min(buttonRect.top, windowHeight - dropdownHeight - margin));
            preferredDirection = 'left';
          } else {
            // 水平空间不足,居中显示
            left = Math.max(margin, (windowWidth - dropdownWidth) / 2);
            top = Math.max(margin, Math.min(buttonRect.top, windowHeight - dropdownHeight - margin));
            preferredDirection = 'center';
          }
        }
      }
      
      // 最终边界检查,确保不会超出屏幕
      left = Math.max(margin, Math.min(left, windowWidth - dropdownWidth - margin));
      top = Math.max(margin, Math.min(top, windowHeight - dropdownHeight - margin));
      
      // 应用新位置
      this.dropdownContainer.style.left = `${left}px`;
      this.dropdownContainer.style.top = `${top}px`;
      
      // 可选:添加调试信息
      if (window.claudeDebug) {
        console.log(`下拉窗口位置决策: ${preferredDirection}`, {
          buttonRect,
          windowSize: { width: windowWidth, height: windowHeight },
          spaces: { left: spaceLeft, right: spaceRight, above: spaceAbove, below: spaceBelow },
          position: { left, top },
          dropdownSize: { width: dropdownWidth, height: dropdownHeight }
        });
      }
    },

    onMouseUp(e) {
      if (!this.isDragging) return;

      this.isDragging = false;
      this.state.isDragging = false;
      this.toggleButton.style.cursor = "move";

      // 保存位置
      GM_setValue("buttonLeft", this.buttonLeft);
      GM_setValue("buttonBottom", this.buttonBottom);

      // 移除拖拽结束后的 hover 逻辑
      e.preventDefault();
    },

    // 移除 scheduleHideDropdown 方法,因为不再需要 hover 逻辑

    showDropdown() {
      // 立即更新状态
      this.state.isDropdownVisible = true;
      this.state.isClosing = false;

      const isMobile = window.innerWidth <= 768;

      if (isMobile) {
        // 移动端固定全屏显示
        this.dropdownContainer.style.top = "10px";
        this.dropdownContainer.style.left = "10px";
        this.dropdownContainer.style.right = "10px";
        this.dropdownContainer.style.bottom = "auto";
        this.dropdownContainer.style.width = "auto";
      } else {
        // 桌面端计算位置
        this.updateDropdownPosition();
      }

      this.dropdownContainer.style.opacity = "0";
      this.dropdownContainer.style.display = "flex";

      setTimeout(() => {
        this.dropdownContainer.style.opacity = "1";
        this.dropdownContainer.style.transform = "scale(1)";
        this.toggleButton.style.transform = "scale(1.1)";
      }, 10);
    },

    hideDropdown() {
      // 设置正在关闭状态
      this.state.isClosing = true;

      // 添加动画
      this.dropdownContainer.style.opacity = "0";
      this.dropdownContainer.style.transform = "scale(0.95)";
      this.toggleButton.style.transform = "scale(1)";

      // 等待动画完成后隐藏
      this.closeTimeout = setTimeout(() => {
        this.dropdownContainer.style.display = "none";
        this.state.isDropdownVisible = false;
        this.state.isClosing = false; // 重置关闭状态
      }, 300);
    },

    observeThemeChanges() {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (
            mutation.type === "attributes" &&
            mutation.attributeName === "data-mode"
          ) {
            this.isDarkMode =
              document.documentElement.getAttribute("data-mode") === "dark";
            this.updateStyles();
          }
        });
      });

      observer.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ["data-mode"],
      });
    },
  };

  // 初始化应用
  App.init();
})();