This commit is contained in:
李志强 2026-06-16 18:14:51 +08:00
parent 1b54447ccb
commit 0011b1b558
3 changed files with 324 additions and 20 deletions

Binary file not shown.

View File

@ -850,19 +850,47 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="lblSilentToken"> <widget class="QLineEdit" name="txtSilentToken">
<property name="text"> <property name="maximumSize">
<string>请输入新 Token (兼容纯 token / Cookie 串)</string> <size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="placeholderText">
<string>在此输入 ID例如11</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTextEdit" name="txtSilentToken"> <widget class="QLabel" name="lblCurrentDetectId">
<property name="placeholderText"> <property name="text">
<string>在此粘贴 Token格式支持 Workos... 或原始 token</string> <string>当前检测 ID-</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QTextEdit" name="lblCurrentDetectToken">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;当前 Token-&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_silentButtons">
<item> <item>
<widget class="QPushButton" name="btnSilentChange"> <widget class="QPushButton" name="btnSilentChange">
<property name="minimumSize"> <property name="minimumSize">
@ -876,6 +904,34 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="btnSilentUnavailable">
<property name="minimumSize">
<size>
<width>0</width>
<height>42</height>
</size>
</property>
<property name="text">
<string>不可用</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSilentCopyToken">
<property name="minimumSize">
<size>
<width>0</width>
<height>42</height>
</size>
</property>
<property name="text">
<string>复制 Token</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

248
main.py
View File

@ -892,6 +892,118 @@ class OneClickRenewalThread(QThread):
self.finished_signal.emit(False, str(e), "") 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: def get_device_info() -> str:
"""获取 CPU/RAM/磁盘等设备信息""" """获取 CPU/RAM/磁盘等设备信息"""
try: try:
@ -1270,6 +1382,43 @@ class MainWindow(QMainWindow):
self.actionUsageGuide = self.findChild(QAction, "actionUsageGuide") self.actionUsageGuide = self.findChild(QAction, "actionUsageGuide")
self.actionDonate = self.findChild(QAction, "actionDonate") self.actionDonate = self.findChild(QAction, "actionDonate")
self.actionAbout = self.findChild(QAction, "actionAbout") 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() 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)) self.btnRefreshMemberStatus.clicked.connect(lambda: self.on_refresh_member_status_clicked(show_popup=True))
if self.btnOneClickRenewal: if self.btnOneClickRenewal:
self.btnOneClickRenewal.clicked.connect(self.on_one_click_renewal_clicked) 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: if self.actionExit:
self.actionExit.triggered.connect(self.close) self.actionExit.triggered.connect(self.close)
if self.actionEmergencyRepair: if self.actionEmergencyRepair:
@ -2468,6 +2623,99 @@ class MainWindow(QMainWindow):
else: else:
QMessageBox.critical(self, "失败", message) 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(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)