diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 342e5d5..91f8fe1 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/layout/main1.ui b/layout/main1.ui index 2e6cc8c..511326b 100644 --- a/layout/main1.ui +++ b/layout/main1.ui @@ -850,32 +850,88 @@ - - - 请输入新 Token (兼容纯 token / Cookie 串): - - - - - - - 在此粘贴 Token,格式支持 Workos... 或原始 token - - - - - - + + - 0 - 42 + 150 + 16777215 - - 无感换号 + + 在此输入 ID,例如:11 + + + + 当前检测 ID:- + + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">当前 Token:-</p></body></html> + + + + 16777215 + 80 + + + + + + + + + + + 0 + 42 + + + + 无感换号 + + + + + + + + 0 + 42 + + + + 不可用 + + + + + + + + 0 + 42 + + + + 复制 Token + + + + + diff --git a/main.py b/main.py index d78e4a1..37dd397 100644 --- a/main.py +++ b/main.py @@ -892,6 +892,118 @@ class OneClickRenewalThread(QThread): self.finished_signal.emit(False, str(e), "") +class SilentDetectThread(QThread): + log_signal = Signal(str) + finished_signal = Signal(bool, str, int) # (success, message/token, next_id) + + def __init__(self, target_id): + super().__init__() + self.target_id = target_id + + def run(self): + try: + self.log_signal.emit(f"🔄 正在从服务端获取 ID={self.target_id} 的 Token...") + url = f"https://api.yunzer.cn/api/cursor/token/peek?id={self.target_id}&data_type=tk" + + response_data = None + try: + response = requests.get(url, timeout=15) + response.raise_for_status() + response_data = response.json() + except Exception: + # 备用连接(直连,不走系统代理) + with requests.Session() as session: + session.trust_env = False + response = session.get(url, timeout=15) + response.raise_for_status() + response_data = response.json() + + if not response_data or response_data.get("code") != 200: + msg = response_data.get("msg") or "获取失败" + self.finished_signal.emit(False, f"获取 Token 失败: {msg}", 0) + return + + data_obj = response_data.get("data") or {} + new_token = data_obj.get("token") + self.new_token = new_token + next_id = data_obj.get("next_id") or (int(self.target_id) + 1) + + if not new_token: + self.finished_signal.emit(False, "服务端返回的 Token 为空!", 0) + return + + self.log_signal.emit("✅ 成功提取 Token!正在执行本地更换...") + new_email = generate_random_email() + + config_dir = get_cursor_config_path() + if not config_dir.exists(): + self.finished_signal.emit(False, "未找到Cursor配置目录", 0) + return + + # 读取原 storage.json 并更新 + storage_file = config_dir / "storage.json" + if not storage_file.exists(): + self.finished_signal.emit(False, "未找到 storage.json 文件", 0) + return + + if not os.access(storage_file, os.W_OK): + import stat + storage_file.chmod(storage_file.stat().st_mode | stat.S_IWRITE) + + with open(storage_file, "r", encoding="utf-8") as f: + content = f.read() + data = json.loads(content) if content.strip() else {} + + old_email = ( + data.get("cursorAuth", {}).get("cachedEmail") + or data.get("cursorAccount", {}).get("email") + or "" + ) + if old_email: + new_email = str(old_email).strip() + + # 修改 storage.json 的数据 + if "cursorAuth" not in data: + data["cursorAuth"] = {} + data["cursorAuth"]["accessToken"] = new_token + data["cursorAuth"]["refreshToken"] = new_token + data["cursorAuth"]["cachedEmail"] = new_email + data["cursorAuth"]["cachedSignUpType"] = data["cursorAuth"].get("cachedSignUpType", "Auth_0") + data["cursorAuth"]["onboardingDate"] = datetime.utcnow().isoformat(timespec="milliseconds") + "Z" + data["cursorAuth"]["plan"] = "pro" + data["cursorAuth"]["stripeMembershipType"] = "pro" + data["cursorAuth"]["membershipType"] = "pro" + + if "cursorAccount" not in data: + data["cursorAccount"] = {} + data["cursorAccount"]["token"] = new_token + data["cursorAccount"]["email"] = new_email + data["cursorAccount"]["plan"] = "pro" + + # 刷新机器 ID + new_machine_id = generate_machine_id() + data["telemetryMacMachineId"] = new_machine_id + data["telemetryDevDeviceId"] = new_machine_id + data["workspaceIdentifier"] = new_machine_id + data["membershipType"] = "pro" + + with open(storage_file, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + # 更新 Cursor 数据库 state.vscdb + self.log_signal.emit("📦 更新 Cursor 数据库...") + db_ok = update_vsdb_token(config_dir, new_token, new_email, self.log_signal.emit) + if not db_ok: + self.finished_signal.emit(False, "更新 Cursor 数据库失败,可能是 Cursor 进程未完全关闭,请在任务管理器中结束所有 Cursor 进程,或尝试以管理员身份运行本软件。", 0) + return + + self.log_signal.emit(f"✅ 无感换号完成!下一条 ID 推荐为: {next_id}") + self.finished_signal.emit(True, "检测换号成功!", next_id) + + except Exception as e: + self.finished_signal.emit(False, f"发生异常: {str(e)}", 0) + + def get_device_info() -> str: """获取 CPU/RAM/磁盘等设备信息""" try: @@ -1270,6 +1382,43 @@ class MainWindow(QMainWindow): self.actionUsageGuide = self.findChild(QAction, "actionUsageGuide") self.actionDonate = self.findChild(QAction, "actionDonate") self.actionAbout = self.findChild(QAction, "actionAbout") + + # 无感检测相关组件 + self.txtSilentToken = self.findChild(QLineEdit, "txtSilentToken") + self.btnSilentChange = self.findChild(QPushButton, "btnSilentChange") + self.btnSilentUnavailable = self.findChild(QPushButton, "btnSilentUnavailable") + self.btnSilentCopyToken = self.findChild(QPushButton, "btnSilentCopyToken") + self.lblSilentToken = self.findChild(QLabel, "lblSilentToken") + self.lblCurrentDetectId = self.findChild(QLabel, "lblCurrentDetectId") + self.lblCurrentDetectToken = self.findChild(QTextEdit, "lblCurrentDetectToken") + self.groupHelpSilentDetect = self.findChild(QGroupBox, "groupHelpSilentDetect") + self.lblHelpSilentDetectDesc = self.findChild(QLabel, "lblHelpSilentDetectDesc") + + # 调整无感检测界面的提示词,使其适配以 ID 检测换号的新功能 + if self.lblSilentToken: + self.lblSilentToken.setText("请输入要检测的号池 ID(如 11):") + if self.txtSilentToken: + self.txtSilentToken.setPlaceholderText("在此输入数字 ID,例如:11") + self.txtSilentToken.setMaximumWidth(150) + if self.btnSilentChange: + self.btnSilentChange.setText("无感换号") + if self.btnSilentUnavailable: + self.btnSilentUnavailable.setText("不可用") + if self.btnSilentCopyToken: + self.btnSilentCopyToken.setText("复制 Token") + if self.lblCurrentDetectId: + self.lblCurrentDetectId.setText("当前检测 ID:-") + if self.lblCurrentDetectToken: + self.lblCurrentDetectToken.setText("当前 Token:-") + if self.groupHelpSilentDetect: + self.groupHelpSilentDetect.setTitle("帮助-无感检测(ID 换号版)") + if self.lblHelpSilentDetectDesc: + self.lblHelpSilentDetectDesc.setText("无感检测:输入服务器号池的记录 ID,点击检测将自动从服务器拉取对应 Token,为您执行本地换号,并自动打开 Cursor,方便快速测试。") + + # 初始化当前检测状态变量 + self.current_detect_id = "" + self.current_detect_token = "" + self._load_cached_logs_to_ui() # 调试信息 @@ -1337,6 +1486,12 @@ 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.btnSilentChange: + self.btnSilentChange.clicked.connect(self.on_silent_detect_clicked) + if self.btnSilentUnavailable: + self.btnSilentUnavailable.clicked.connect(self.on_silent_unavailable_clicked) + if self.btnSilentCopyToken: + self.btnSilentCopyToken.clicked.connect(self.on_silent_copy_token_clicked) if self.actionExit: self.actionExit.triggered.connect(self.close) if self.actionEmergencyRepair: @@ -2468,6 +2623,99 @@ class MainWindow(QMainWindow): else: QMessageBox.critical(self, "失败", message) + @Slot(bool, str, int) + def on_silent_detect_finished(self, success, message, next_id): + if self.btnSilentChange: + self.btnSilentChange.setEnabled(True) + self.btnSilentChange.setText("无感换号") + + if success: + # 记录当前成功检测到的 ID 和 Token,并更新上方显示 + self.current_detect_id = getattr(self.detect_thread, "target_id", "") + self.current_detect_token = getattr(self.detect_thread, "new_token", "") + 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.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 on_silent_detect_clicked(self): + """执行无感检测换号。""" + id_str = self.txtSilentToken.text().strip() if self.txtSilentToken else "" + if not id_str: + QMessageBox.warning(self, "警告", "请先输入要检测的 ID!") + return + if not id_str.isdigit(): + QMessageBox.warning(self, "警告", "ID 必须为纯数字!") + 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 + else: + return + + if self.btnSilentChange: + self.btnSilentChange.setEnabled(False) + self.btnSilentChange.setText("🔍 检测换号中...") + + self.detect_thread = SilentDetectThread(id_str) + self.detect_thread.log_signal.connect(self.log) + self.detect_thread.finished_signal.connect(self.on_silent_detect_finished) + self.detect_thread.start() + + @Slot() + def on_silent_unavailable_clicked(self): + """用户反馈当前 Token 不可用占位逻辑。""" + if not getattr(self, "current_detect_id", None): + QMessageBox.warning(self, "警告", "请先成功换号/检测一个 ID!") + return + + reply = QMessageBox.question( + self, + "反馈 Token 不可用", + f"确定要反馈当前 ID: {self.current_detect_id} 对应的 Token 为不可用吗?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + if reply == QMessageBox.Yes: + self.log(f"⚠️ 用户已点击不可用按钮反馈 ID: {self.current_detect_id}。") + QMessageBox.information(self, "提示", "已收到反馈,不可用接口待后端实现。") + + @Slot() + def on_silent_copy_token_clicked(self): + """复制当前检测到的 Token 到剪贴板。""" + if not getattr(self, "current_detect_token", None): + QMessageBox.warning(self, "警告", "当前没有可复制的 Token,请先成功换号/检测一个 ID!") + return + + clipboard = QApplication.clipboard() + clipboard.setText(self.current_detect_token) + self.log("📋 已复制当前检测到的 Token 到剪贴板。") + QMessageBox.information(self, "成功", "Token 已成功复制到剪贴板!") + def main(): app = QApplication(sys.argv)