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)