diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..d20c0fe --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 91f8fe1..41bfc3a 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/main.py b/main.py index 37dd397..843151d 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ from datetime import datetime from pathlib import Path from typing import Optional -__VERSION__ = "0.0.7" +__VERSION__ = "0.0.8" from PySide6.QtWidgets import ( QApplication, @@ -40,6 +40,12 @@ from PySide6.QtWidgets import ( QScrollArea, QSplashScreen, QStyle, + QListWidget, + QStackedWidget, + QGridLayout, + QSpacerItem, + QSizePolicy, + QSplitter, ) from PySide6.QtCore import QThread, Signal, Qt, QTimer, QMetaObject, Q_ARG, Slot from PySide6.QtUiTools import QUiLoader @@ -138,6 +144,34 @@ def kill_cursor(): return killed +def is_windsurf_running(): + """检测Windsurf是否正在运行""" + windsurf_exe_names = ['windsurf.exe', 'windsurf'] + for proc in psutil.process_iter(["name"]): + try: + name = proc.info["name"] + if name and name.lower() in windsurf_exe_names: + return True + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + return False + + +def kill_windsurf(): + """强制关闭Windsurf进程""" + killed = False + windsurf_exe_names = ['windsurf.exe', 'windsurf'] + for proc in psutil.process_iter(["name", "pid"]): + try: + name = proc.info["name"] + if name and name.lower() in windsurf_exe_names: + proc.kill() + killed = True + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + return killed + + def generate_random_email(): length = random.randint(6, 8) username = "".join(random.choices(string.ascii_lowercase + string.digits, k=length)) @@ -544,9 +578,11 @@ class ImageClickLabel(QLabel): """可点击的图片标签,点击放大显示""" clicked = Signal(str) - def __init__(self, image_path, parent=None): + def __init__(self, image_path, parent=None, max_w=350, max_h=450): super().__init__(parent) self.image_path = image_path + self.max_w = max_w + self.max_h = max_h self.original_pixmap = QPixmap(image_path) self.setScaledContents(True) self.setCursor(Qt.PointingHandCursor) @@ -557,11 +593,12 @@ class ImageClickLabel(QLabel): """更新图片显示(等比缩放)""" if not self.original_pixmap.isNull(): scaled_pixmap = self.original_pixmap.scaled( - 350, 450, + self.max_w, self.max_h, Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.setPixmap(scaled_pixmap) + self.setMaximumSize(self.max_w, self.max_h) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: @@ -637,33 +674,35 @@ class DonateDialog(QDialog): def show_full_image(self, image_path): """显示全屏图片""" - dialog = QDialog(self) - dialog.setWindowTitle("图片预览") - dialog.setMinimumSize(600, 700) - - layout = QVBoxLayout(dialog) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - - label = QLabel() - pixmap = QPixmap(image_path) - label.setPixmap(pixmap) - label.setAlignment(Qt.AlignCenter) - - scroll.setWidget(label) - layout.addWidget(scroll) - - close_btn = QPushButton("关闭") - close_btn.clicked.connect(dialog.accept) - close_btn.setMinimumWidth(100) - - btn_layout = QHBoxLayout() - btn_layout.addStretch() - btn_layout.addWidget(close_btn) - layout.addLayout(btn_layout) - - dialog.exec() + def launch(): + dialog = QDialog(self) + dialog.setWindowTitle("图片预览") + dialog.setMinimumSize(600, 700) + + layout = QVBoxLayout(dialog) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + + label = QLabel() + pixmap = QPixmap(image_path) + label.setPixmap(pixmap) + label.setAlignment(Qt.AlignCenter) + + scroll.setWidget(label) + layout.addWidget(scroll) + + close_btn = QPushButton("关闭") + close_btn.clicked.connect(dialog.accept) + close_btn.setMinimumWidth(100) + + btn_layout = QHBoxLayout() + btn_layout.addStretch() + btn_layout.addWidget(close_btn) + layout.addLayout(btn_layout) + + dialog.exec() + QTimer.singleShot(0, launch) class ChangeTokenThread(QThread): @@ -892,6 +931,52 @@ class OneClickRenewalThread(QThread): self.finished_signal.emit(False, str(e), "") +class MarkTokenUsabilityThread(QThread): + """标记服务端号池 Token 可用/不可用线程。""" + finished_signal = Signal(bool, dict, str) + + def __init__(self, token_id, available: bool): + super().__init__() + self.token_id = str(token_id).strip() + self.available = available + + def run(self): + endpoint = "available" if self.available else "unavailable" + url = f"https://api.yunzer.cn/api/cursor/token/{endpoint}?id={self.token_id}" + try: + response_data = None + try: + with requests.post(url, timeout=15) as response: + response.raise_for_status() + response_data = response.json() + except requests.exceptions.ProxyError: + with requests.Session() as session: + session.trust_env = False + with session.post(url, timeout=15) as response: + response.raise_for_status() + response_data = response.json() + + if not response_data: + self.finished_signal.emit(False, {}, "服务器未返回有效数据") + return + + if response_data.get("code") != 200: + msg = response_data.get("msg") or response_data.get("message") or "标记失败" + self.finished_signal.emit(False, {}, str(msg)) + return + + data = response_data.get("data") or {} + self.finished_signal.emit(True, data, "") + except requests.exceptions.Timeout: + self.finished_signal.emit(False, {}, "连接超时,请检查网络") + except requests.exceptions.ConnectionError as e: + self.finished_signal.emit(False, {}, f"网络连接失败: {str(e)}") + except requests.exceptions.RequestException as e: + self.finished_signal.emit(False, {}, f"请求失败: {str(e)}") + except Exception as e: + self.finished_signal.emit(False, {}, f"发生异常: {str(e)}") + + class SilentDetectThread(QThread): log_signal = Signal(str) finished_signal = Signal(bool, str, int) # (success, message/token, next_id) @@ -1004,6 +1089,130 @@ class SilentDetectThread(QThread): self.finished_signal.emit(False, f"发生异常: {str(e)}", 0) +def get_windsurf_user_path() -> Path: + home = Path.home() + if sys.platform == "win32": + return home / "AppData" / "Roaming" / "Windsurf" / "User" + elif sys.platform == "darwin": + return home / "Library" / "Application Support" / "Windsurf" / "User" + else: + return home / ".config" / "Windsurf" / "User" + + +def reset_windsurf_machine_id(log_callback) -> bool: + """重置 Windsurf 的机器码""" + try: + config_dir = get_windsurf_user_path() + global_storage_dir = config_dir / "globalStorage" + if not global_storage_dir.exists(): + log_callback("⚠️ 未找到 Windsurf 配置目录,请确认是否已安装并运行过 Windsurf。") + return False + + # 1. 检查 Windsurf 是否在运行 + windsurf_exe_names = ['windsurf.exe', 'windsurf'] + for proc in psutil.process_iter(["name"]): + try: + name = proc.info["name"] + if name and name.lower() in windsurf_exe_names: + log_callback("⚠️ 检测到 Windsurf 正在运行,请先关闭 Windsurf 软件!") + return False + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + # 2. 备份并更新 storage.json + storage_file = global_storage_dir / "storage.json" + if storage_file.exists(): + log_callback("📖 读取 storage.json...") + backup_file = global_storage_dir / "storage.json.backup" + if backup_file.exists(): + backup_file.unlink() + shutil.copy2(storage_file, backup_file) + + with open(storage_file, "r", encoding="utf-8") as f: + content = f.read() + data = json.loads(content) if content.strip() else {} + + new_machine_id = hashlib.sha256(os.urandom(32)).hexdigest() + new_dev_device_id = str(uuid.uuid4()) + new_sqm_id = "{" + str(uuid.uuid4()).upper() + "}" + + data["telemetry.machineId"] = new_machine_id + data["telemetry.devDeviceId"] = new_dev_device_id + data["telemetry.sqmId"] = new_sqm_id + + log_callback("🔧 已生成新的 storage.json Telemetry ID") + + with open(storage_file, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + else: + log_callback("⚠️ 未找到 storage.json") + + # 3. 备份并更新 state.vscdb + db_path = global_storage_dir / "state.vscdb" + if db_path.exists(): + log_callback("📖 连接 Windsurf 数据库...") + db_backup = global_storage_dir / "state.vscdb.backup" + if db_backup.exists(): + db_backup.unlink() + shutil.copy2(db_path, db_backup) + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + new_service_machine_id = str(uuid.uuid4()) + + cursor.execute( + "INSERT OR REPLACE INTO ItemTable (key, value) VALUES ('storage.serviceMachineId', ?)", + (new_service_machine_id,) + ) + + # 清除 codeium 登录会话 + cursor.execute( + "DELETE FROM ItemTable WHERE key LIKE 'secret://%codeium.windsurf%'" + ) + + conn.commit() + conn.close() + log_callback("🔧 已更新 state.vscdb 数据库机器码,并清除了旧的 Codeium 会话") + else: + log_callback("⚠️ 未找到 state.vscdb 数据库") + + log_callback("✅ Windsurf 机器码重置成功!") + return True + except Exception as e: + log_callback(f"❌ Windsurf 重置失败: {str(e)}") + return False + + +def clear_windsurf_cache(log_callback) -> bool: + """清除 Windsurf 缓存(删除 %APPDATA%/Windsurf 文件夹)""" + try: + path = Path.home() / "AppData" / "Roaming" / "Windsurf" + if not path.exists(): + log_callback("⚠️ 未找到 Windsurf 数据文件夹,无需清除。") + return True + + if is_windsurf_running(): + log_callback("⚠️ 检测到 Windsurf 正在运行,请先关闭 Windsurf 软件!") + return False + + log_callback(f"🧹 正在删除 Windsurf 数据文件夹: {path}") + + def remove_readonly(func, p, excinfo): + import stat + os.chmod(p, stat.S_IWRITE) + func(p) + + import stat + shutil.rmtree(path, onerror=remove_readonly) + + log_callback("✅ Windsurf 数据文件夹已成功清除!") + return True + except Exception as e: + log_callback(f"❌ 清除 Windsurf 数据失败: {str(e)}") + return False + + def get_device_info() -> str: """获取 CPU/RAM/磁盘等设备信息""" try: @@ -1385,9 +1594,83 @@ class MainWindow(QMainWindow): # 无感检测相关组件 self.txtSilentToken = self.findChild(QLineEdit, "txtSilentToken") + self.btnSilentIdMinus = QPushButton("-") + self.btnSilentIdPlus = QPushButton("+") self.btnSilentChange = self.findChild(QPushButton, "btnSilentChange") self.btnSilentUnavailable = self.findChild(QPushButton, "btnSilentUnavailable") self.btnSilentCopyToken = self.findChild(QPushButton, "btnSilentCopyToken") + + # 动态添加“可用”按钮,并应用红绿蓝配色样式 + self.btnSilentAvailable = QPushButton("可用") + self.btnSilentAvailable.setMinimumHeight(42) + + # 样式定义:无感换号-蓝色,不可用-红色,可用-绿色 + if self.btnSilentChange: + self.btnSilentChange.setStyleSheet(""" + QPushButton { + background-color: #1e88e5; + color: white; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #1565c0; + } + QPushButton:pressed { + background-color: #0d47a1; + } + QPushButton:disabled { + background-color: #cccccc; + color: #666666; + } + """) + + if self.btnSilentUnavailable: + self.btnSilentUnavailable.setStyleSheet(""" + QPushButton { + background-color: #e53935; + color: white; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #c62828; + } + QPushButton:pressed { + background-color: #b71c1c; + } + QPushButton:disabled { + background-color: #cccccc; + color: #666666; + } + """) + + self.btnSilentAvailable.setStyleSheet(""" + QPushButton { + background-color: #43a047; + color: white; + border: none; + border-radius: 4px; + font-weight: bold; + } + QPushButton:hover { + background-color: #2e7d32; + } + QPushButton:pressed { + background-color: #1b5e20; + } + QPushButton:disabled { + background-color: #cccccc; + color: #666666; + } + """) + + # 插入到布局中,放置在“不可用”按钮的旁边(即“不可用”后面,“复制 Token”前面) + layout = self.findChild(QHBoxLayout, "horizontalLayout_silentButtons") + if layout: + layout.insertWidget(2, self.btnSilentAvailable) self.lblSilentToken = self.findChild(QLabel, "lblSilentToken") self.lblCurrentDetectId = self.findChild(QLabel, "lblCurrentDetectId") self.lblCurrentDetectToken = self.findChild(QTextEdit, "lblCurrentDetectToken") @@ -1400,6 +1683,37 @@ class MainWindow(QMainWindow): if self.txtSilentToken: self.txtSilentToken.setPlaceholderText("在此输入数字 ID,例如:11") self.txtSilentToken.setMaximumWidth(150) + for btn in (self.btnSilentIdMinus, self.btnSilentIdPlus): + btn.setFixedSize(28, 28) + btn.setStyleSheet(""" + QPushButton { + border: 1px solid palette(mid); + border-radius: 4px; + font-weight: bold; + background: palette(button); + color: palette(button-text); + } + QPushButton:hover { + background: palette(alternate-base); + } + QPushButton:pressed { + background: palette(midlight); + } + """) + silent_token_layout = self.txtSilentToken.parentWidget().layout() if self.txtSilentToken.parentWidget() else None + if silent_token_layout and hasattr(silent_token_layout, "indexOf") and hasattr(silent_token_layout, "insertWidget"): + token_input_index = silent_token_layout.indexOf(self.txtSilentToken) + if token_input_index >= 0: + silent_token_row = QWidget(self.txtSilentToken.parentWidget()) + silent_token_row_layout = QHBoxLayout(silent_token_row) + silent_token_row_layout.setContentsMargins(0, 0, 0, 0) + silent_token_row_layout.setSpacing(4) + silent_token_layout.removeWidget(self.txtSilentToken) + silent_token_row_layout.addWidget(self.txtSilentToken) + silent_token_row_layout.addWidget(self.btnSilentIdMinus) + silent_token_row_layout.addWidget(self.btnSilentIdPlus) + silent_token_row_layout.addStretch() + silent_token_layout.insertWidget(token_input_index, silent_token_row) if self.btnSilentChange: self.btnSilentChange.setText("无感换号") if self.btnSilentUnavailable: @@ -1409,7 +1723,7 @@ class MainWindow(QMainWindow): if self.lblCurrentDetectId: self.lblCurrentDetectId.setText("当前检测 ID:-") if self.lblCurrentDetectToken: - self.lblCurrentDetectToken.setText("当前 Token:-") + self.lblCurrentDetectToken.setText("") if self.groupHelpSilentDetect: self.groupHelpSilentDetect.setTitle("帮助-无感检测(ID 换号版)") if self.lblHelpSilentDetectDesc: @@ -1419,6 +1733,415 @@ class MainWindow(QMainWindow): self.current_detect_id = "" self.current_detect_token = "" + # 无感检测按钮已移入应急检修的工具面板弹窗中 + + # 从左侧帮助导航列表中移除“无感检测”项与“应急检修”项,并在帮助页面进行偏置连接 + self.helpTabList = self.findChild(QListWidget, "helpTabList") + help_tab_list = self.helpTabList + self.helpContentStack = self.findChild(QStackedWidget, "helpContentStack") + if help_tab_list: + help_tab_list.setMinimumWidth(100) + help_tab_list.setMaximumWidth(130) + if help_tab_list.count() > 4: + help_tab_list.takeItem(4) # 移除“无感检测” + if help_tab_list.count() > 0: + help_tab_list.takeItem(0) # 移除“应急检修” + + if self.helpContentStack: + try: + help_tab_list.currentRowChanged.disconnect() + except Exception: + pass + help_tab_list.currentRowChanged.connect(lambda row: self.helpContentStack.setCurrentIndex(row + 1)) + help_tab_list.setCurrentRow(0) + self.helpContentStack.setCurrentIndex(1) + + # 直接在“帮助 -> 捐赠支持”页面中嵌入捐赠内容,不再使用弹窗 + group_help_donate = self.findChild(QGroupBox, "groupHelpDonate") + if group_help_donate: + lbl_help_donate_desc = self.findChild(QLabel, "lblHelpDonateDesc") + if lbl_help_donate_desc: + lbl_help_donate_desc.hide() + if self.btnDonate: + self.btnDonate.hide() + + layout_donate = group_help_donate.layout() + if not layout_donate: + layout_donate = QVBoxLayout(group_help_donate) + + title_donate = QLabel("您的捐赠将用于维护和改进本软件。\n\n感谢各位,为爱发电,作者需要大家的支持与关注!\n\n有问题请联系QQ:1066960883", group_help_donate) + title_donate_font = QFont() + title_donate_font.setPointSize(11) + title_donate_font.setBold(True) + title_donate.setFont(title_donate_font) + title_donate.setAlignment(Qt.AlignCenter) + layout_donate.addWidget(title_donate) + + layout_donate.addSpacing(15) + + images_layout = QHBoxLayout() + + wx_layout = QVBoxLayout() + wx_label = QLabel("微信支付", group_help_donate) + wx_label.setAlignment(Qt.AlignCenter) + wx_label.setStyleSheet("font-weight: bold;") + wx_layout.addWidget(wx_label) + + wx_path = get_resource_path(os.path.join("assets", "images", "donate", "wx.jpg")) + self.wx_image_embedded = ImageClickLabel(wx_path, parent=group_help_donate, max_w=180, max_h=230) + self.wx_image_embedded.clicked.connect(self.show_full_image) + wx_layout.addWidget(self.wx_image_embedded, 0, Qt.AlignCenter) + images_layout.addLayout(wx_layout) + + images_layout.addSpacing(20) + + zfb_layout = QVBoxLayout() + zfb_label = QLabel("支付宝", group_help_donate) + zfb_label.setAlignment(Qt.AlignCenter) + zfb_label.setStyleSheet("font-weight: bold;") + zfb_layout.addWidget(zfb_label) + + zfb_path = get_resource_path(os.path.join("assets", "images", "donate", "zfb.jpg")) + self.zfb_image_embedded = ImageClickLabel(zfb_path, parent=group_help_donate, max_w=180, max_h=230) + self.zfb_image_embedded.clicked.connect(self.show_full_image) + zfb_layout.addWidget(self.zfb_image_embedded, 0, Qt.AlignCenter) + images_layout.addLayout(zfb_layout) + + layout_donate.addLayout(images_layout) + layout_donate.addStretch() + + # 直接在“帮助 -> 关于软件”页面中嵌入关于内容,不再使用弹窗 + group_help_about = self.findChild(QGroupBox, "groupHelpAbout") + if group_help_about: + lbl_help_about_desc = self.findChild(QLabel, "lblHelpAboutDesc") + if lbl_help_about_desc: + lbl_help_about_desc.hide() + if self.btnAbout: + self.btnAbout.hide() + + layout_about = group_help_about.layout() + if not layout_about: + layout_about = QVBoxLayout(group_help_about) + + about_label = QLabel(group_help_about) + about_label.setText( + f"

牛马Cursor登录器

" + f"

当前版本:{__VERSION__}

" + f"

用途简介:用于 Cursor 账号登录、配置管理和常用工具操作。

" + f"

技术交流群:720797421 (QQ群)

" + ) + about_label.setStyleSheet("color: palette(text); font-size: 11pt; line-height: 1.6;") + about_label.setOpenExternalLinks(True) + layout_about.addWidget(about_label) + layout_about.addStretch() + + # 动态重组左侧菜单与页面层级,支持二级菜单 + self.tabList = self.findChild(QListWidget, "tabList") + if self.tabList: + self.tabList.setMinimumWidth(100) + self.tabList.setMaximumWidth(130) + self.contentStack = self.findChild(QStackedWidget, "contentStack") + + if self.tabList and self.contentStack: + # 1. 提取并暂存原有 UI 设计中的主页面控件 + page_login = self.findChild(QWidget, "pageLogin") + page_renewal = self.findChild(QWidget, "pageRenewal") + page_cursor_config = self.findChild(QWidget, "pageCursorConfig") + page_help = self.findChild(QWidget, "pageHelp") + + # 2. 从主 contentStack 中移除这些原有页面 + if page_login: + self.contentStack.removeWidget(page_login) + if page_renewal: + self.contentStack.removeWidget(page_renewal) + if page_cursor_config: + self.contentStack.removeWidget(page_cursor_config) + if page_help: + self.contentStack.removeWidget(page_help) + + # 样式定义:使二级侧边栏样式一致 + sidebar_style = """ + QListWidget { + border: 1px solid palette(mid); + border-radius: 4px; + background: palette(base); + color: palette(text); + outline: none; + } + QListWidget::item { + min-height: 34px; + padding: 8px 12px; + border-bottom: 1px solid palette(midlight); + } + QListWidget::item:selected { + color: palette(highlighted-text); + background: palette(highlight); + } + QListWidget::item:hover:!selected { + background: palette(alternate-base); + } + """ + + # 3. 创建全新的 "Cursor" 归集主页面,采用双栏二级子菜单布局 + self.pageCursor = QWidget() + layout_cursor = QHBoxLayout(self.pageCursor) + layout_cursor.setContentsMargins(0, 0, 0, 0) + + # Cursor 左侧二级菜单 + self.cursorTabList = QListWidget(self.pageCursor) + self.cursorTabList.setMinimumWidth(100) + self.cursorTabList.setMaximumWidth(130) + self.cursorTabList.setStyleSheet(sidebar_style) + self.cursorTabList.addItem("Token切换") + self.cursorTabList.addItem("一键续杯") + self.cursorTabList.addItem("Cursor配置") + self.cursorTabList.addItem("应急检修") + self.cursorTabList.setCurrentRow(0) + layout_cursor.addWidget(self.cursorTabList) + + # Cursor 右侧内容堆栈 + self.cursorContentStack = QStackedWidget(self.pageCursor) + layout_cursor.addWidget(self.cursorContentStack) + + if page_login: + self.cursorContentStack.addWidget(page_login) + if page_renewal: + self.cursorContentStack.addWidget(page_renewal) + if page_cursor_config: + self.cursorContentStack.addWidget(page_cursor_config) + + # 创建“应急检修”子页面,并将按钮直接显示在右侧 + self.pageCursorEmergency = QWidget() + layout_cursor_emergency = QVBoxLayout(self.pageCursorEmergency) + layout_cursor_emergency.setContentsMargins(10, 10, 10, 10) + + group_emergency = QGroupBox("应急检修", self.pageCursorEmergency) + group_emergency_layout = QVBoxLayout(group_emergency) + + desc_label = QLabel("应急检修:提供 Cursor 故障排查、缓存清理、Token 提取等应急工具。", group_emergency) + desc_label.setStyleSheet("color: palette(text); font-size: 10pt; margin-bottom: 10px;") + group_emergency_layout.addWidget(desc_label) + + grid_layout_emergency = QGridLayout() + grid_layout_emergency.setSpacing(10) + + self.btnDownloadDB = QPushButton("下载DB Browser", group_emergency) + self.btnDownloadDB.setMinimumHeight(45) + self.btnDownloadDB.setMinimumWidth(180) + self.btnDownloadDB.setMaximumWidth(260) + + self.btnClearCursorCache = QPushButton("清除Cursor缓存", group_emergency) + self.btnClearCursorCache.setMinimumHeight(45) + self.btnClearCursorCache.setMinimumWidth(180) + self.btnClearCursorCache.setMaximumWidth(260) + + self.btnExtractToken = QPushButton("Token提取", group_emergency) + self.btnExtractToken.setMinimumHeight(45) + self.btnExtractToken.setMinimumWidth(180) + self.btnExtractToken.setMaximumWidth(260) + + self.btnSilentDetect = QPushButton("无感检测", group_emergency) + self.btnSilentDetect.setMinimumHeight(45) + self.btnSilentDetect.setMinimumWidth(180) + self.btnSilentDetect.setMaximumWidth(260) + + # 应用与 self.btnEmergencyRepair 一致的样式 + for btn in (self.btnDownloadDB, self.btnClearCursorCache, self.btnExtractToken, self.btnSilentDetect): + if self.btnEmergencyRepair: + btn.setStyleSheet(self.btnEmergencyRepair.styleSheet()) + btn.setFont(self.btnEmergencyRepair.font()) + else: + btn.setStyleSheet(""" + QPushButton { + background-color: #1e88e5; + color: white; + border: none; + border-radius: 4px; + font-weight: bold; + font-size: 10pt; + } + QPushButton:hover { + background-color: #1565c0; + } + QPushButton:pressed { + background-color: #0d47a1; + } + """) + + grid_layout_emergency.addWidget(self.btnDownloadDB, 0, 0) + grid_layout_emergency.addWidget(self.btnClearCursorCache, 0, 1) + grid_layout_emergency.addWidget(self.btnExtractToken, 1, 0) + grid_layout_emergency.addWidget(self.btnSilentDetect, 1, 1) + grid_layout_emergency.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2) + + group_emergency_layout.addLayout(grid_layout_emergency) + group_emergency_layout.addStretch() + + layout_cursor_emergency.addWidget(group_emergency) + layout_cursor_emergency.addStretch() + + self.cursorContentStack.addWidget(self.pageCursorEmergency) + + # 连接应急检修的按钮信号 + self.btnDownloadDB.clicked.connect(self.download_db_tool) + self.btnClearCursorCache.clicked.connect(self.clear_cursor_cache) + self.btnExtractToken.clicked.connect(self.on_token_extract_clicked) + self.btnSilentDetect.clicked.connect(self.on_silent_detect_menu_clicked) + + self.cursorTabList.currentRowChanged.connect(self.cursorContentStack.setCurrentIndex) + + # 4. 创建全新的 "Windsurf" 归集主页面,双栏二级子菜单布局 + self.pageWindsurf = QWidget() + layout_windsurf = QHBoxLayout(self.pageWindsurf) + layout_windsurf.setContentsMargins(0, 0, 0, 0) + + # Windsurf 左侧二级菜单 + self.windsurfTabList = QListWidget(self.pageWindsurf) + self.windsurfTabList.setMinimumWidth(100) + self.windsurfTabList.setMaximumWidth(130) + self.windsurfTabList.setStyleSheet(sidebar_style) + self.windsurfTabList.addItem("Winsurf配置") + self.windsurfTabList.setCurrentRow(0) + layout_windsurf.addWidget(self.windsurfTabList) + + # Windsurf 右侧内容堆栈 + self.windsurfContentStack = QStackedWidget(self.pageWindsurf) + layout_windsurf.addWidget(self.windsurfContentStack) + + self.pageWindsurfConfig = QWidget() + layout_windsurf_config = QVBoxLayout(self.pageWindsurfConfig) + layout_windsurf_config.setContentsMargins(10, 10, 10, 10) + + group_windsurf = QGroupBox("Winsurf配置", self.pageWindsurfConfig) + group_layout = QVBoxLayout(group_windsurf) + + grid_layout = QGridLayout() + grid_layout.setSpacing(10) + + self.btnResetWindsurf = QPushButton("重置机器码", group_windsurf) + self.btnResetWindsurf.setMinimumHeight(45) + self.btnClearWindsurf = QPushButton("清除缓存", group_windsurf) + self.btnClearWindsurf.setMinimumHeight(45) + + for btn in (self.btnResetWindsurf, self.btnClearWindsurf): + if self.btnEmergencyRepair: + btn.setStyleSheet(self.btnEmergencyRepair.styleSheet()) + btn.setFont(self.btnEmergencyRepair.font()) + else: + btn.setStyleSheet(""" + QPushButton { + background-color: #1e88e5; + color: white; + border: none; + border-radius: 4px; + font-weight: bold; + font-size: 10pt; + } + QPushButton:hover { + background-color: #1565c0; + } + QPushButton:pressed { + background-color: #0d47a1; + } + """) + + grid_layout.addWidget(self.btnResetWindsurf, 0, 0) + grid_layout.addWidget(self.btnClearWindsurf, 0, 1) + grid_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2) + + group_layout.addLayout(grid_layout) + group_layout.addStretch() + + layout_windsurf_config.addWidget(group_windsurf) + layout_windsurf_config.addStretch() + + self.windsurfContentStack.addWidget(self.pageWindsurfConfig) + self.windsurfContentStack.setCurrentIndex(0) + self.windsurfTabList.currentRowChanged.connect(self.windsurfContentStack.setCurrentIndex) + + self.btnResetWindsurf.clicked.connect(self.on_reset_windsurf_clicked) + self.btnClearWindsurf.clicked.connect(self.on_clear_windsurf_clicked) + + # 5. 创建全新 "Kiro" 主页面,双栏布局占位 + self.pageKiro = QWidget() + layout_kiro = QHBoxLayout(self.pageKiro) + layout_kiro.setContentsMargins(0, 0, 0, 0) + + self.kiroTabList = QListWidget(self.pageKiro) + self.kiroTabList.setMinimumWidth(100) + self.kiroTabList.setMaximumWidth(130) + self.kiroTabList.setStyleSheet(sidebar_style) + self.kiroTabList.addItem("Kiro配置") + self.kiroTabList.setCurrentRow(0) + layout_kiro.addWidget(self.kiroTabList) + + self.kiroContentStack = QStackedWidget(self.pageKiro) + layout_kiro.addWidget(self.kiroContentStack) + + self.pageKiroConfig = QWidget() + layout_kiro_config = QVBoxLayout(self.pageKiroConfig) + layout_kiro_config.setContentsMargins(10, 10, 10, 10) + + group_kiro = QGroupBox("Kiro配置", self.pageKiroConfig) + group_kiro_layout = QVBoxLayout(group_kiro) + + kiro_label = QLabel("Kiro 应急检修工具正在开发中,敬请期待...", group_kiro) + kiro_label.setStyleSheet("color: palette(text); font-size: 10pt;") + group_kiro_layout.addWidget(kiro_label) + group_kiro_layout.addStretch() + + layout_kiro_config.addWidget(group_kiro) + layout_kiro_config.addStretch() + + self.kiroContentStack.addWidget(self.pageKiroConfig) + self.kiroContentStack.setCurrentIndex(0) + self.kiroTabList.currentRowChanged.connect(self.kiroContentStack.setCurrentIndex) + + # 6. 清空主内容堆栈并按新排序添加页面:Cursor、Windsurf、Kiro、帮助 + while self.contentStack.count() > 0: + self.contentStack.removeWidget(self.contentStack.widget(0)) + + self.contentStack.addWidget(self.pageCursor) # index 0 + self.contentStack.addWidget(self.pageWindsurf) # index 1 + self.contentStack.addWidget(self.pageKiro) # index 2 + if page_help: + self.contentStack.addWidget(page_help) # index 3 + + # 7. 重建左侧主 tabList 导航项并定位到第一项 + self.tabList.clear() + self.tabList.addItem("Cursor") + self.tabList.addItem("Windsurf") + self.tabList.addItem("Kiro") + self.tabList.addItem("帮助") + self.tabList.setCurrentRow(0) + + # 8. 获取并配置拖拽分割条,使主页面和日志区域大小可交互可拖动,并增强把手的视觉提示 + self.splitterMainLog = self.findChild(QSplitter, "splitterMainLog") + if self.splitterMainLog: + self.splitterMainLog.setStyleSheet(""" + QSplitter::handle:vertical { + background-color: palette(mid); + height: 5px; + } + QSplitter::handle:vertical:hover { + background-color: palette(highlight); + } + """) + # 设置初始比例:主区域 430,日志面板 170 + self.splitterMainLog.setSizes([430, 170]) + + self.splitterBody = self.findChild(QSplitter, "splitterBody") + if self.splitterBody: + self.splitterBody.setStyleSheet(""" + QSplitter::handle:horizontal { + background-color: palette(mid); + width: 5px; + } + QSplitter::handle:horizontal:hover { + background-color: palette(highlight); + } + """) + self._load_cached_logs_to_ui() # 调试信息 @@ -1486,10 +2209,16 @@ class MainWindow(QMainWindow): self.btnRefreshMemberStatus.clicked.connect(lambda: self.on_refresh_member_status_clicked(show_popup=True)) if self.btnOneClickRenewal: self.btnOneClickRenewal.clicked.connect(self.on_one_click_renewal_clicked) + if self.btnSilentIdMinus: + self.btnSilentIdMinus.clicked.connect(lambda: self._adjust_silent_id(-1)) + if self.btnSilentIdPlus: + self.btnSilentIdPlus.clicked.connect(lambda: self._adjust_silent_id(1)) if self.btnSilentChange: self.btnSilentChange.clicked.connect(self.on_silent_detect_clicked) if self.btnSilentUnavailable: self.btnSilentUnavailable.clicked.connect(self.on_silent_unavailable_clicked) + if self.btnSilentAvailable: + self.btnSilentAvailable.clicked.connect(self.on_silent_available_clicked) if self.btnSilentCopyToken: self.btnSilentCopyToken.clicked.connect(self.on_silent_copy_token_clicked) if self.actionExit: @@ -2234,20 +2963,48 @@ class MainWindow(QMainWindow): self.thread.start() def on_donate_clicked(self): - """打开捐赠对话框""" - dialog = DonateDialog(self) - dialog.exec() + """捐赠支持:跳转到帮助 -> 捐赠支持。""" + if self.tabList and hasattr(self, 'helpTabList') and self.helpTabList: + self.tabList.setCurrentRow(3) + self.helpTabList.setCurrentRow(1) def on_about_clicked(self): - """显示关于软件信息。""" - QMessageBox.about( - self, - "关于软件", - f"牛马Cursor登录器\n\n" - f"当前版本:{__VERSION__}\n\n" - "用于 Cursor 账号登录、配置管理和常用工具操作。\n\n" - "QQ群:720797421" - ) + """关于软件:跳转到帮助 -> 关于软件。""" + if self.tabList and hasattr(self, 'helpTabList') and self.helpTabList: + self.tabList.setCurrentRow(3) + self.helpTabList.setCurrentRow(2) + + def show_full_image(self, image_path): + """显示全屏图片""" + def launch(): + dialog = QDialog(self) + dialog.setWindowTitle("图片预览") + dialog.setMinimumSize(600, 700) + + layout = QVBoxLayout(dialog) + + scroll = QScrollArea(dialog) + scroll.setWidgetResizable(True) + + label = QLabel(scroll) + pixmap = QPixmap(image_path) + label.setPixmap(pixmap) + label.setAlignment(Qt.AlignCenter) + + scroll.setWidget(label) + layout.addWidget(scroll) + + close_btn = QPushButton("关闭", dialog) + close_btn.clicked.connect(dialog.accept) + close_btn.setMinimumWidth(100) + + btn_layout = QHBoxLayout() + btn_layout.addStretch() + btn_layout.addWidget(close_btn) + layout.addLayout(btn_layout) + + dialog.exec() + QTimer.singleShot(0, launch) def on_usage_guide_clicked(self): """打开使用说明图片(非模态,可与其他窗口并行)。""" @@ -2340,49 +3097,185 @@ class MainWindow(QMainWindow): dialog.show() def on_emergency_repair_clicked(self): - """应急检修:弹出工具面板。""" - if self._emergency_dialog and self._emergency_dialog.isVisible(): - self._emergency_dialog.raise_() - self._emergency_dialog.activateWindow() - return + """应急检修:切换到 Cursor 归集二级菜单下的“应急检修”子页面。""" + if self.tabList and self.cursorTabList: + self.tabList.setCurrentRow(0) + self.cursorTabList.setCurrentRow(3) + def on_silent_detect_menu_clicked(self): + """无感检测密码验证,通过后打开无感检测窗口。""" + pwd, ok = QInputDialog.getText( + self, + "无感检测", + "请输入密码:", + QLineEdit.Password, + ) + if not ok: + return + if pwd != "920103": + self.log("❌ 无感检测密码错误") + QMessageBox.warning(self, "提示", "密码错误,无法使用无感检测。") + return + + self.on_silent_detect_popup_clicked() + + def on_silent_detect_popup_clicked(self): + """点击无感检测按钮,弹窗显示无感检测功能""" + if not self.groupHelpSilentDetect: + QMessageBox.warning(self, "提示", "未找到无感检测组件") + return + dialog = QDialog(self) - dialog.setWindowTitle("应急检修") - dialog.setMinimumSize(400, 300) + dialog.setWindowTitle("无感检测") + dialog.setMinimumSize(450, 250) dialog.setModal(False) dialog.setWindowModality(Qt.NonModal) - - layout = QVBoxLayout(dialog) - layout.addWidget(QLabel("请选择要执行的操作:")) - - actions_widget = QWidget(dialog) - actions_layout = QVBoxLayout(actions_widget) - actions_layout.setContentsMargins(0, 0, 0, 0) - actions_layout.setSpacing(10) - actions_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) - - btn_download = QPushButton("下载DB Browser") - btn_clear_cache = QPushButton("清除Cursor缓存") - btn_extract_token = QPushButton("Token提取") - for btn in ( - btn_download, - btn_clear_cache, - btn_extract_token, - ): - btn.setMinimumWidth(180) - btn.setMaximumWidth(260) - btn.setSizePolicy(btn.sizePolicy().horizontalPolicy(), btn.sizePolicy().verticalPolicy()) - actions_layout.addWidget(btn, 0, Qt.AlignLeft) - - layout.addWidget(actions_widget) - layout.addStretch() - - btn_download.clicked.connect(self.download_db_tool) - btn_clear_cache.clicked.connect(self.clear_cursor_cache) - btn_extract_token.clicked.connect(self.on_token_extract_clicked) - self._emergency_dialog = dialog + + # 保存原父级和布局,以便关闭时归还 + original_parent = self.groupHelpSilentDetect.parentWidget() + original_layout = None + if original_parent: + original_layout = original_parent.layout() + + dialog_layout = QVBoxLayout(dialog) + dialog_layout.addWidget(self.groupHelpSilentDetect) + + # 添加关闭按钮 + btn_close = QPushButton("关闭", dialog) + btn_close.setMinimumHeight(35) + btn_close.clicked.connect(dialog.close) + dialog_layout.addWidget(btn_close) + + # 在关闭时将组件还原回原布局的顶部(在 spacer 之前) + def restore_widget(): + if original_layout: + original_layout.insertWidget(0, self.groupHelpSilentDetect) + elif original_parent: + self.groupHelpSilentDetect.setParent(original_parent) + + dialog.finished.connect(restore_widget) dialog.show() + def on_reset_windsurf_clicked(self): + """重置 Windsurf 机器码。""" + # 检测 Windsurf 是否正在运行 + if is_windsurf_running(): + msg_box = QMessageBox(self) + msg_box.setWindowTitle("Windsurf正在运行") + msg_box.setText("Windsurf正在运行中!\n请先保存代码并手动关闭Windsurf。") + msg_box.setIcon(QMessageBox.Warning) + btn_close = msg_box.addButton("💀 强制关闭", QMessageBox.ActionRole) + btn_cancel = msg_box.addButton("取消", QMessageBox.RejectRole) + msg_box.setDefaultButton(btn_cancel) + msg_box.exec_() + + if msg_box.clickedButton() == btn_close: + self.log("💀 正在强制关闭Windsurf...") + if kill_windsurf(): + self.log("✅ Windsurf已关闭") + else: + self.log("⚠️ 未找到运行中的Windsurf进程") + QMessageBox.warning(self, "警告", "未找到运行中的Windsurf进程") + return + else: + return + + # 弹窗询问确认 + reply = QMessageBox.question( + self, + "确认重置", + "确定要重置 Windsurf 的机器码吗?\n该操作会清除当前账户的登录状态,重置后您需要重新登录账户。", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + if reply != QMessageBox.Yes: + return + + self.log("🔄 开始重置 Windsurf 机器码...") + + # 禁用重置按钮 + if self.btnResetWindsurf: + self.btnResetWindsurf.setEnabled(False) + self.btnResetWindsurf.setText("🔄 重置中...") + + success = reset_windsurf_machine_id(self.log) + + if success: + QMessageBox.information( + self, + "重置成功", + "Windsurf 机器码已成功重置!\n您现在可以重新打开 Windsurf 并登录新的免费账户了。" + ) + else: + QMessageBox.critical( + self, + "重置失败", + "重置 Windsurf 机器码失败,请查看日志了解详细原因。\n可能原因:Windsurf 正在运行,请先手动关闭它。" + ) + + if self.btnResetWindsurf: + self.btnResetWindsurf.setEnabled(True) + self.btnResetWindsurf.setText("重置机器码") + + def on_clear_windsurf_clicked(self): + """清除 Windsurf 缓存(直接删除文件夹)""" + # 检测 Windsurf 是否正在运行 + if is_windsurf_running(): + msg_box = QMessageBox(self) + msg_box.setWindowTitle("Windsurf正在运行") + msg_box.setText("检测到 Windsurf 正在运行!\n清除缓存前需要先关闭 Windsurf。") + msg_box.setIcon(QMessageBox.Warning) + btn_close = msg_box.addButton("💀 强制关闭", QMessageBox.ActionRole) + btn_cancel = msg_box.addButton("取消", QMessageBox.RejectRole) + msg_box.setDefaultButton(btn_cancel) + msg_box.exec_() + + if msg_box.clickedButton() == btn_close: + self.log("💀 正在强制关闭Windsurf...") + if kill_windsurf(): + self.log("✅ Windsurf已关闭") + else: + self.log("⚠️ 未找到运行中的Windsurf进程") + QMessageBox.warning(self, "警告", "未找到运行中的Windsurf进程") + return + else: + return + + # 再次弹窗确认 + reply = QMessageBox.question( + self, + "确认清除缓存", + "确定要直接删除 %APPDATA%\\Windsurf 数据文件夹吗?\n该操作会彻底清除所有配置、扩展与缓存数据,且不可恢复!", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + if reply != QMessageBox.Yes: + return + + self.log("🔄 开始清除 Windsurf 缓存文件夹...") + if self.btnClearWindsurf: + self.btnClearWindsurf.setEnabled(False) + self.btnClearWindsurf.setText("🔄 清理中...") + + success = clear_windsurf_cache(self.log) + + if success: + QMessageBox.information( + self, + "清理成功", + "Windsurf 数据文件夹已成功删除清除!" + ) + else: + QMessageBox.critical( + self, + "清理失败", + "清除 Windsurf 数据文件夹失败,请查看日志了解详细原因。" + ) + + if self.btnClearWindsurf: + self.btnClearWindsurf.setEnabled(True) + self.btnClearWindsurf.setText("清除缓存") + def on_token_extract_clicked(self): """密码通过后打开 Token 提取窗口。""" pwd, ok = QInputDialog.getText( @@ -2636,16 +3529,29 @@ class MainWindow(QMainWindow): if self.lblCurrentDetectId: self.lblCurrentDetectId.setText(f"当前检测 ID:{self.current_detect_id}") if self.lblCurrentDetectToken: - self.lblCurrentDetectToken.setText(f"当前 Token:{self.current_detect_token}") + self.lblCurrentDetectToken.setText(self.current_detect_token) self.log(f"🚀 换号成功 (ID={self.current_detect_id})") self.log(f"🔑 提取的 Token: {self.current_detect_token}") self.log("🚀 正在为您启动 Cursor...") self.on_open_cursor_clicked() - QMessageBox.information(self, "成功", "无感检测换号成功!\n已自动为您启动 Cursor。") else: QMessageBox.critical(self, "错误", message) + def _adjust_silent_id(self, delta: int): + """调整无感检测输入框中的 ID。""" + if not self.txtSilentToken: + return + + raw_id = self.txtSilentToken.text().strip() + if raw_id and not raw_id.isdigit(): + QMessageBox.warning(self, "警告", "ID 必须为纯数字,无法自动增减!") + return + + current_id = int(raw_id) if raw_id else 0 + new_id = max(0, current_id + delta) + self.txtSilentToken.setText(str(new_id)) + def on_silent_detect_clicked(self): """执行无感检测换号。""" id_str = self.txtSilentToken.text().strip() if self.txtSilentToken else "" @@ -2657,24 +3563,12 @@ class MainWindow(QMainWindow): return if is_cursor_running(): - msg_box = QMessageBox(self) - msg_box.setWindowTitle("Cursor正在运行") - msg_box.setText("检测到 Cursor 正在运行!\n由于更新数据库需要独占锁,请先关闭 Cursor。") - msg_box.setIcon(QMessageBox.Warning) - btn_close = msg_box.addButton("💀 强制关闭并继续", QMessageBox.ActionRole) - btn_cancel = msg_box.addButton("取消", QMessageBox.RejectRole) - msg_box.setDefaultButton(btn_cancel) - msg_box.exec_() - - if msg_box.clickedButton() == btn_close: - self.log("💀 正在强制关闭Cursor...") - if kill_cursor(): - self.log("✅ Cursor已关闭") - else: - self.log("⚠️ 未找到运行中的Cursor进程") - QMessageBox.warning(self, "警告", "未找到运行中的Cursor进程") - return + self.log("💀 检测到 Cursor 正在运行,正在自动关闭后继续无感换号...") + if kill_cursor(): + self.log("✅ Cursor已自动关闭") else: + self.log("⚠️ 自动关闭 Cursor 失败,请手动关闭后重试") + QMessageBox.warning(self, "警告", "自动关闭 Cursor 失败,请手动关闭后重试。") return if self.btnSilentChange: @@ -2686,23 +3580,87 @@ class MainWindow(QMainWindow): self.detect_thread.finished_signal.connect(self.on_silent_detect_finished) self.detect_thread.start() - @Slot() - def on_silent_unavailable_clicked(self): - """用户反馈当前 Token 不可用占位逻辑。""" + def _format_token_mark_result(self, data: dict) -> str: + """格式化服务端标记可用/不可用接口返回数据。""" + token_id = data.get("id", self.current_detect_id) + is_used = data.get("is_used", "-") + is_available = data.get("is_available", "-") + state_changed = data.get("state_changed", "-") + update_time = data.get("update_time", "-") + return ( + f"ID: {token_id}\n" + f"is_used: {is_used}\n" + f"is_available: {is_available}\n" + f"state_changed: {state_changed}\n" + f"update_time: {update_time}" + ) + + def _start_mark_current_token_usability(self, available: bool): + """调用服务端接口标记当前检测 ID 的 Token 可用/不可用。""" if not getattr(self, "current_detect_id", None): QMessageBox.warning(self, "警告", "请先成功换号/检测一个 ID!") return - + + action_text = "可用" if available else "不可用" reply = QMessageBox.question( self, - "反馈 Token 不可用", - f"确定要反馈当前 ID: {self.current_detect_id} 对应的 Token 为不可用吗?", + f"反馈 Token {action_text}", + f"确定要反馈当前 ID: {self.current_detect_id} 对应的 Token 为{action_text}吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) - if reply == QMessageBox.Yes: - self.log(f"⚠️ 用户已点击不可用按钮反馈 ID: {self.current_detect_id}。") - QMessageBox.information(self, "提示", "已收到反馈,不可用接口待后端实现。") + if reply != QMessageBox.Yes: + return + + self.log(f"🔄 正在调用服务端接口标记 ID={self.current_detect_id} 为{action_text}...") + + if self.btnSilentAvailable: + self.btnSilentAvailable.setEnabled(False) + if self.btnSilentUnavailable: + self.btnSilentUnavailable.setEnabled(False) + + self._mark_token_thread = MarkTokenUsabilityThread(self.current_detect_id, available) + self._mark_token_thread.finished_signal.connect( + lambda success, data, error_msg, available=available: self.on_mark_token_usability_finished( + success, data, error_msg, available + ) + ) + self._mark_token_thread.start() + + @Slot(bool, dict, str, bool) + def on_mark_token_usability_finished(self, success, data, error_msg, available): + """服务端标记 Token 可用/不可用完成。""" + if self.btnSilentAvailable: + self.btnSilentAvailable.setEnabled(True) + if self.btnSilentUnavailable: + self.btnSilentUnavailable.setEnabled(True) + + action_text = "可用" if available else "不可用" + if success: + result_text = self._format_token_mark_result(data) + self.log(f"✅ 已标记 ID={data.get('id', self.current_detect_id)} 为{action_text}") + self.log( + "📡 服务端返回:" + f"id={data.get('id', self.current_detect_id)}, " + f"is_used={data.get('is_used', '-')}, " + f"is_available={data.get('is_available', '-')}, " + f"state_changed={data.get('state_changed', '-')}, " + f"update_time={data.get('update_time', '-')}" + ) + QMessageBox.information(self, "提交成功", f"Token 已标记为{action_text}。\n\n{result_text}") + else: + self.log(f"❌ 标记 ID={self.current_detect_id} 为{action_text}失败: {error_msg}") + QMessageBox.critical(self, "提交失败", f"Token 标记为{action_text}失败:\n{error_msg}") + + @Slot() + def on_silent_available_clicked(self): + """用户反馈当前 Token 可用。""" + self._start_mark_current_token_usability(True) + + @Slot() + def on_silent_unavailable_clicked(self): + """用户反馈当前 Token 不可用。""" + self._start_mark_current_token_usability(False) @Slot() def on_silent_copy_token_clicked(self): diff --git a/main_backup.py b/main_backup.py index e9e65c4..06215d9 100644 --- a/main_backup.py +++ b/main_backup.py @@ -1120,7 +1120,7 @@ class MainWindow(QMainWindow): self.statusBar().addPermanentWidget(QLabel(f"Version: {__VERSION__}")) self.log("🚀 程序启动成功") - self.log("📋 请先粘贴Token,然后点击换号") + # self.log("📋 请先粘贴Token,然后点击换号") if self._splash: self._splash.finish(self)