批量更新
This commit is contained in:
parent
655d40760d
commit
dbd28458b8
@ -4,3 +4,10 @@ python -m PyInstaller -w -F main.py
|
|||||||
|
|
||||||
# 编译成exe
|
# 编译成exe
|
||||||
python -m PyInstaller build.spec --clean
|
python -m PyInstaller build.spec --clean
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# 关于 Win + Mac 同时构建(后续)
|
||||||
|
- PyInstaller 不能在 Windows 本机直接产出 macOS 程序。
|
||||||
|
- 当前命令在 Windows 只能生成 `.exe`,在 macOS 才能生成 `.app`。
|
||||||
|
- 后续可用 GitHub Actions 做双平台构建:本地打 Windows,云端 `macos-latest` 打 macOS。
|
||||||
|
|||||||
BIN
__pycache__/main.cpython-313.pyc
Normal file
BIN
__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
assets/images/donate/info.png
Normal file
BIN
assets/images/donate/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 423 KiB |
@ -55,6 +55,38 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnAutoCursorPath">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>88</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>自动查找</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnOpenCursor">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>110</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>110</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>打开Cursor</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -110,6 +142,12 @@
|
|||||||
<height>50</height>
|
<height>50</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>50</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<pointsize>12</pointsize>
|
<pointsize>12</pointsize>
|
||||||
@ -128,21 +166,40 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btnQueryUsage">
|
<widget class="QPushButton" name="btnOnlineShop">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>90</width>
|
<width>110</width>
|
||||||
<height>50</height>
|
<height>50</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>90</width>
|
<width>110</width>
|
||||||
<height>50</height>
|
<height>50</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>📊 查询额度(暂时无用)</string>
|
<string>在线商城</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnAiRelay">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>110</width>
|
||||||
|
<height>50</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>110</width>
|
||||||
|
<height>50</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>AI中转站</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -166,7 +223,17 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btnEmergencyRepair">
|
<widget class="QPushButton" name="btnEmergencyRepair">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>🔧 应急检修,用户勿点</string>
|
<string>🔧 应急检修</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimumHeight">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnUsageGuide">
|
||||||
|
<property name="text">
|
||||||
|
<string>📖 使用说明</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumHeight">
|
<property name="minimumHeight">
|
||||||
<number>30</number>
|
<number>30</number>
|
||||||
|
|||||||
704
main.py
704
main.py
@ -1,26 +1,33 @@
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import base64
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import re
|
||||||
import os
|
import os
|
||||||
|
import webbrowser
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import ctypes
|
||||||
|
import socket
|
||||||
import psutil
|
import psutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import requests
|
import requests
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__VERSION__ = "0.0.3"
|
__VERSION__ = "0.0.6"
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QTextEdit,
|
QTextEdit,
|
||||||
|
QInputDialog,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
@ -31,6 +38,7 @@ from PySide6.QtWidgets import (
|
|||||||
QDialog,
|
QDialog,
|
||||||
QScrollArea,
|
QScrollArea,
|
||||||
QSplashScreen,
|
QSplashScreen,
|
||||||
|
QStyle,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import QThread, Signal, Qt, QTimer, QMetaObject, Q_ARG, Slot
|
from PySide6.QtCore import QThread, Signal, Qt, QTimer, QMetaObject, Q_ARG, Slot
|
||||||
from PySide6.QtUiTools import QUiLoader
|
from PySide6.QtUiTools import QUiLoader
|
||||||
@ -595,6 +603,9 @@ class ChangeTokenThread(QThread):
|
|||||||
data["cursorAuth"]["accessToken"] = self.new_token
|
data["cursorAuth"]["accessToken"] = self.new_token
|
||||||
data["cursorAuth"]["refreshToken"] = self.new_token
|
data["cursorAuth"]["refreshToken"] = self.new_token
|
||||||
data["cursorAuth"]["cachedEmail"] = self.new_email
|
data["cursorAuth"]["cachedEmail"] = self.new_email
|
||||||
|
data["cursorAuth"]["plan"] = "pro"
|
||||||
|
data["cursorAuth"]["stripeMembershipType"] = "pro"
|
||||||
|
data["cursorAuth"]["membershipType"] = "pro"
|
||||||
|
|
||||||
# 修改 cursorAccount
|
# 修改 cursorAccount
|
||||||
self.log_signal.emit("🔑 替换 cursorAccount...")
|
self.log_signal.emit("🔑 替换 cursorAccount...")
|
||||||
@ -602,6 +613,7 @@ class ChangeTokenThread(QThread):
|
|||||||
data["cursorAccount"] = {}
|
data["cursorAccount"] = {}
|
||||||
data["cursorAccount"]["token"] = self.new_token
|
data["cursorAccount"]["token"] = self.new_token
|
||||||
data["cursorAccount"]["email"] = self.new_email
|
data["cursorAccount"]["email"] = self.new_email
|
||||||
|
data["cursorAccount"]["plan"] = "pro"
|
||||||
|
|
||||||
# 刷新机器ID
|
# 刷新机器ID
|
||||||
self.log_signal.emit("🔧 刷新机器ID...")
|
self.log_signal.emit("🔧 刷新机器ID...")
|
||||||
@ -610,6 +622,9 @@ class ChangeTokenThread(QThread):
|
|||||||
data["telemetryDevDeviceId"] = new_machine_id
|
data["telemetryDevDeviceId"] = new_machine_id
|
||||||
data["workspaceIdentifier"] = new_machine_id
|
data["workspaceIdentifier"] = new_machine_id
|
||||||
|
|
||||||
|
# membershipType pro
|
||||||
|
data["membershipType"] = "pro"
|
||||||
|
|
||||||
self.log_signal.emit(f"📧 新邮箱: {self.new_email}")
|
self.log_signal.emit(f"📧 新邮箱: {self.new_email}")
|
||||||
|
|
||||||
# 保存文件
|
# 保存文件
|
||||||
@ -776,18 +791,29 @@ class MainWindow(QMainWindow):
|
|||||||
self.cursor_path = get_default_cursor_path()
|
self.cursor_path = get_default_cursor_path()
|
||||||
self.backup_path = ""
|
self.backup_path = ""
|
||||||
self._update_download_thread = None
|
self._update_download_thread = None
|
||||||
|
self._emergency_dialog = None
|
||||||
|
self._usage_guide_dialog = None
|
||||||
|
self._token_extract_dialog = None
|
||||||
|
self._token_display = None
|
||||||
|
self._current_extracted_token = ""
|
||||||
|
self.log_file_path = self._prepare_log_file_path()
|
||||||
|
|
||||||
# 重新查找组件 - 从self查找
|
# 重新查找组件 - 从self查找
|
||||||
self.txtToken = self.findChild(QTextEdit, "txtToken")
|
self.txtToken = self.findChild(QTextEdit, "txtToken")
|
||||||
self.txtLog = self.findChild(QTextEdit, "txtLog")
|
self.txtLog = self.findChild(QTextEdit, "txtLog")
|
||||||
self.txtCursorPath = self.findChild(QLineEdit, "txtCursorPath")
|
self.txtCursorPath = self.findChild(QLineEdit, "txtCursorPath")
|
||||||
self.btnChange = self.findChild(QPushButton, "btnChange")
|
self.btnChange = self.findChild(QPushButton, "btnChange")
|
||||||
|
self.btnOpenCursor = self.findChild(QPushButton, "btnOpenCursor")
|
||||||
|
self.btnOnlineShop = self.findChild(QPushButton, "btnOnlineShop")
|
||||||
self.btnBrowseCursor = self.findChild(QPushButton, "btnBrowseCursor")
|
self.btnBrowseCursor = self.findChild(QPushButton, "btnBrowseCursor")
|
||||||
|
self.btnAutoCursorPath = self.findChild(QPushButton, "btnAutoCursorPath")
|
||||||
|
self.btnAiRelay = self.findChild(QPushButton, "btnAiRelay")
|
||||||
self.btnClearLog = self.findChild(QPushButton, "btnClearLog")
|
self.btnClearLog = self.findChild(QPushButton, "btnClearLog")
|
||||||
self.btnDonate = self.findChild(QPushButton, "btnDonate")
|
self.btnDonate = self.findChild(QPushButton, "btnDonate")
|
||||||
self.btnCheckUpdate = self.findChild(QPushButton, "btnCheckUpdate")
|
self.btnCheckUpdate = self.findChild(QPushButton, "btnCheckUpdate")
|
||||||
self.btnEmergencyRepair = self.findChild(QPushButton, "btnEmergencyRepair")
|
self.btnEmergencyRepair = self.findChild(QPushButton, "btnEmergencyRepair")
|
||||||
self.btnQueryUsage = self.findChild(QPushButton, "btnQueryUsage")
|
self.btnUsageGuide = self.findChild(QPushButton, "btnUsageGuide")
|
||||||
|
self._load_cached_logs_to_ui()
|
||||||
|
|
||||||
# 调试信息
|
# 调试信息
|
||||||
print(f"txtToken: {self.txtToken}")
|
print(f"txtToken: {self.txtToken}")
|
||||||
@ -798,16 +824,34 @@ class MainWindow(QMainWindow):
|
|||||||
print(f"btnClearLog: {self.btnClearLog}")
|
print(f"btnClearLog: {self.btnClearLog}")
|
||||||
print(f"btnDonate: {self.btnDonate}")
|
print(f"btnDonate: {self.btnDonate}")
|
||||||
print(f"btnCheckUpdate: {self.btnCheckUpdate}")
|
print(f"btnCheckUpdate: {self.btnCheckUpdate}")
|
||||||
|
print(f"btnUsageGuide: {self.btnUsageGuide}")
|
||||||
|
|
||||||
# 设置默认Cursor路径
|
# 设置默认Cursor路径
|
||||||
if self.txtCursorPath:
|
if self.txtCursorPath:
|
||||||
self.txtCursorPath.setText(get_default_cursor_path())
|
self.txtCursorPath.setText(get_default_cursor_path())
|
||||||
|
|
||||||
|
# 为在线商城按钮设置图标(使用 Qt 内置图标,避免依赖外部资源)
|
||||||
|
if self.btnOnlineShop:
|
||||||
|
shop_icon = QIcon.fromTheme("shopping-cart")
|
||||||
|
if shop_icon.isNull():
|
||||||
|
shop_icon = QIcon.fromTheme("emblem-sales")
|
||||||
|
if shop_icon.isNull():
|
||||||
|
shop_icon = self.style().standardIcon(QStyle.SP_DialogOpenButton)
|
||||||
|
self.btnOnlineShop.setIcon(shop_icon)
|
||||||
|
|
||||||
# 信号连接
|
# 信号连接
|
||||||
if self.btnChange:
|
if self.btnChange:
|
||||||
self.btnChange.clicked.connect(self.on_change_clicked)
|
self.btnChange.clicked.connect(self.on_change_clicked)
|
||||||
|
if self.btnOpenCursor:
|
||||||
|
self.btnOpenCursor.clicked.connect(self.on_open_cursor_clicked)
|
||||||
|
if self.btnOnlineShop:
|
||||||
|
self.btnOnlineShop.clicked.connect(self.on_open_online_shop_clicked)
|
||||||
|
if self.btnAiRelay:
|
||||||
|
self.btnAiRelay.clicked.connect(self.on_open_ai_relay_clicked)
|
||||||
if self.btnBrowseCursor:
|
if self.btnBrowseCursor:
|
||||||
self.btnBrowseCursor.clicked.connect(self.on_browse_cursor)
|
self.btnBrowseCursor.clicked.connect(self.on_browse_cursor)
|
||||||
|
if self.btnAutoCursorPath:
|
||||||
|
self.btnAutoCursorPath.clicked.connect(self.on_auto_config_cursor)
|
||||||
if self.btnClearLog:
|
if self.btnClearLog:
|
||||||
self.btnClearLog.clicked.connect(self.on_clear_log)
|
self.btnClearLog.clicked.connect(self.on_clear_log)
|
||||||
if self.btnDonate:
|
if self.btnDonate:
|
||||||
@ -816,10 +860,16 @@ class MainWindow(QMainWindow):
|
|||||||
self.btnCheckUpdate.clicked.connect(self.on_check_update_clicked)
|
self.btnCheckUpdate.clicked.connect(self.on_check_update_clicked)
|
||||||
if self.btnEmergencyRepair:
|
if self.btnEmergencyRepair:
|
||||||
self.btnEmergencyRepair.clicked.connect(self.on_emergency_repair_clicked)
|
self.btnEmergencyRepair.clicked.connect(self.on_emergency_repair_clicked)
|
||||||
if self.btnQueryUsage:
|
if self.btnUsageGuide:
|
||||||
self.btnQueryUsage.clicked.connect(self.on_query_usage_clicked)
|
self.btnUsageGuide.clicked.connect(self.on_usage_guide_clicked)
|
||||||
|
# 设置状态栏:左侧显示QQ群,右侧显示版本号
|
||||||
# 设置版本号显示在状态栏右侧
|
qq_icon_label = QLabel()
|
||||||
|
qq_icon = QIcon.fromTheme("im-qq")
|
||||||
|
if qq_icon.isNull():
|
||||||
|
qq_icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
|
||||||
|
qq_icon_label.setPixmap(qq_icon.pixmap(16, 16))
|
||||||
|
self.statusBar().addWidget(qq_icon_label)
|
||||||
|
self.statusBar().addWidget(QLabel("QQ群:720797421"))
|
||||||
self.statusBar().addPermanentWidget(QLabel(f"Version: {__VERSION__}"))
|
self.statusBar().addPermanentWidget(QLabel(f"Version: {__VERSION__}"))
|
||||||
|
|
||||||
self.log("🚀 程序启动成功")
|
self.log("🚀 程序启动成功")
|
||||||
@ -976,13 +1026,99 @@ class MainWindow(QMainWindow):
|
|||||||
self.log(f"❌ 检查更新失败: {error_msg}")
|
self.log(f"❌ 检查更新失败: {error_msg}")
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
line = f"[{ts}] {message}"
|
||||||
if self.txtLog:
|
if self.txtLog:
|
||||||
self.txtLog.append(message)
|
self.txtLog.append(line)
|
||||||
self.statusBar().showMessage(message)
|
self._scroll_log_to_bottom()
|
||||||
|
if self.log_file_path:
|
||||||
|
try:
|
||||||
|
with open(self.log_file_path, "a", encoding="utf-8") as f:
|
||||||
|
f.write(line + "\n")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _scroll_log_to_bottom(self):
|
||||||
|
"""让日志文本框始终滚动到最底部,跟踪最新日志。"""
|
||||||
|
if not self.txtLog:
|
||||||
|
return
|
||||||
|
scroll_bar = self.txtLog.verticalScrollBar()
|
||||||
|
if scroll_bar:
|
||||||
|
scroll_bar.setValue(scroll_bar.maximum())
|
||||||
|
|
||||||
|
def _prepare_log_file_path(self) -> Optional[Path]:
|
||||||
|
"""准备当前日志文件路径;优先沿用当天最新日志,失败时返回 None。"""
|
||||||
|
try:
|
||||||
|
self.log_dir = Path.home() / ".cursortokenlogin" / "logs"
|
||||||
|
self.log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
latest = self._get_latest_log_file_path()
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
return latest or (self.log_dir / f"{today}-0001.log")
|
||||||
|
except OSError:
|
||||||
|
self.log_dir = None
|
||||||
|
return None
|
||||||
|
|
||||||
def on_clear_log(self):
|
def on_clear_log(self):
|
||||||
if self.txtLog:
|
if self.txtLog:
|
||||||
self.txtLog.clear()
|
self.txtLog.clear()
|
||||||
|
next_log = self._get_next_log_file_path()
|
||||||
|
if next_log:
|
||||||
|
self.log_file_path = next_log
|
||||||
|
try:
|
||||||
|
# 立即创建新的空日志文件,确保重启后读取的是清空后的新会话文件。
|
||||||
|
self.log_file_path.touch(exist_ok=True)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _load_cached_logs_to_ui(self):
|
||||||
|
"""启动时把最近日志文件内容回显到界面。"""
|
||||||
|
if not self.txtLog or not self.log_file_path or not self.log_file_path.exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(self.log_file_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read().strip()
|
||||||
|
if content:
|
||||||
|
self.txtLog.setPlainText(content)
|
||||||
|
self._scroll_log_to_bottom()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_latest_log_file_path(self) -> Optional[Path]:
|
||||||
|
if not getattr(self, "log_dir", None):
|
||||||
|
return None
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
prefix = f"{today}-"
|
||||||
|
try:
|
||||||
|
files = sorted(self.log_dir.glob(f"{prefix}*.log"))
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
if not files:
|
||||||
|
return None
|
||||||
|
numbered = []
|
||||||
|
for p in files:
|
||||||
|
stem = p.stem
|
||||||
|
if not stem.startswith(prefix):
|
||||||
|
continue
|
||||||
|
seq = stem[len(prefix):]
|
||||||
|
if seq.isdigit():
|
||||||
|
numbered.append((int(seq), p))
|
||||||
|
if not numbered:
|
||||||
|
return None
|
||||||
|
numbered.sort(key=lambda x: x[0])
|
||||||
|
return numbered[-1][1]
|
||||||
|
|
||||||
|
def _get_next_log_file_path(self) -> Optional[Path]:
|
||||||
|
if not getattr(self, "log_dir", None):
|
||||||
|
return None
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
prefix = f"{today}-"
|
||||||
|
latest = self._get_latest_log_file_path()
|
||||||
|
if latest and latest.stem.startswith(prefix):
|
||||||
|
seq = latest.stem[len(prefix):]
|
||||||
|
next_no = int(seq) + 1 if seq.isdigit() else 1
|
||||||
|
else:
|
||||||
|
next_no = 1
|
||||||
|
return self.log_dir / f"{today}-{next_no:04d}.log"
|
||||||
|
|
||||||
def on_browse_cursor(self):
|
def on_browse_cursor(self):
|
||||||
"""浏览Cursor路径"""
|
"""浏览Cursor路径"""
|
||||||
@ -1001,6 +1137,156 @@ class MainWindow(QMainWindow):
|
|||||||
self.txtCursorPath.setText(file_path)
|
self.txtCursorPath.setText(file_path)
|
||||||
self.cursor_path = file_path
|
self.cursor_path = file_path
|
||||||
|
|
||||||
|
def on_auto_config_cursor(self):
|
||||||
|
"""自动查找并填充 Cursor 安装路径。"""
|
||||||
|
cursor_path = get_default_cursor_path()
|
||||||
|
if cursor_path and Path(cursor_path).exists():
|
||||||
|
if self.txtCursorPath:
|
||||||
|
self.txtCursorPath.setText(cursor_path)
|
||||||
|
self.cursor_path = cursor_path
|
||||||
|
self.log(f"✅ 已自动查找 Cursor 路径: {cursor_path}")
|
||||||
|
return
|
||||||
|
self.log("❌ 自动查找失败:未找到 Cursor 安装目录,请手动浏览选择。")
|
||||||
|
QMessageBox.warning(self, "提示", "未自动找到 Cursor 安装目录,请点击“浏览”手动选择。")
|
||||||
|
|
||||||
|
def _launch_cursor(self, cursor_path: str) -> bool:
|
||||||
|
"""按当前平台启动 Cursor。"""
|
||||||
|
try:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
os.startfile(cursor_path)
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
subprocess.Popen(["open", cursor_path])
|
||||||
|
else:
|
||||||
|
subprocess.Popen([cursor_path])
|
||||||
|
self.log("✅ Cursor已启动")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 打开Cursor失败: {str(e)}")
|
||||||
|
QMessageBox.warning(self, "警告", f"打开Cursor失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _resolve_cursor_path_or_prompt(self) -> str:
|
||||||
|
"""
|
||||||
|
返回可用的 Cursor 路径;当未配置/无效时,提示用户手动配置或自动查找。
|
||||||
|
"""
|
||||||
|
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
||||||
|
if cursor_path and Path(cursor_path).exists():
|
||||||
|
return cursor_path
|
||||||
|
|
||||||
|
msg_box = QMessageBox(self)
|
||||||
|
msg_box.setWindowTitle("Cursor路径未配置")
|
||||||
|
msg_box.setText("未检测到有效的 Cursor 路径,请先配置。")
|
||||||
|
msg_box.setIcon(QMessageBox.Warning)
|
||||||
|
btn_manual = msg_box.addButton("手动配置", QMessageBox.ActionRole)
|
||||||
|
btn_auto = msg_box.addButton("自动查找", QMessageBox.ActionRole)
|
||||||
|
btn_cancel = msg_box.addButton("取消", QMessageBox.RejectRole)
|
||||||
|
msg_box.setDefaultButton(btn_auto)
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
|
clicked = msg_box.clickedButton()
|
||||||
|
if clicked == btn_manual:
|
||||||
|
self.on_browse_cursor()
|
||||||
|
elif clicked == btn_auto:
|
||||||
|
self.on_auto_config_cursor()
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
||||||
|
if cursor_path and Path(cursor_path).exists():
|
||||||
|
return cursor_path
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def on_open_cursor_clicked(self):
|
||||||
|
"""点击“打开Cursor”按钮:优先使用已配置路径,否则引导配置。"""
|
||||||
|
cursor_path = self._resolve_cursor_path_or_prompt()
|
||||||
|
if not cursor_path:
|
||||||
|
return
|
||||||
|
self.log("🚀 正在打开Cursor...")
|
||||||
|
self._launch_cursor(cursor_path)
|
||||||
|
|
||||||
|
def on_open_online_shop_clicked(self):
|
||||||
|
"""打开在线商城页面。"""
|
||||||
|
shop_url = "https://shop.yunzer.cn/"
|
||||||
|
try:
|
||||||
|
opened = webbrowser.open(shop_url)
|
||||||
|
if opened:
|
||||||
|
self.log(f"🌐 已打开在线商城: {shop_url}")
|
||||||
|
else:
|
||||||
|
self.log(f"⚠️ 未能自动打开浏览器,请手动访问: {shop_url}")
|
||||||
|
QMessageBox.warning(self, "提示", f"无法自动打开浏览器,请手动访问:\n{shop_url}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 打开在线商城失败: {str(e)}")
|
||||||
|
QMessageBox.warning(self, "警告", f"打开在线商城失败: {str(e)}")
|
||||||
|
|
||||||
|
def on_open_ai_relay_clicked(self):
|
||||||
|
"""打开 AI 中转站页面。"""
|
||||||
|
relay_url = "https://api.yunzer.com.cn/"
|
||||||
|
try:
|
||||||
|
opened = webbrowser.open(relay_url)
|
||||||
|
if opened:
|
||||||
|
self.log(f"🌐 已打开AI中转站: {relay_url}")
|
||||||
|
else:
|
||||||
|
self.log(f"⚠️ 未能自动打开浏览器,请手动访问: {relay_url}")
|
||||||
|
QMessageBox.warning(self, "提示", f"无法自动打开浏览器,请手动访问:\n{relay_url}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 打开AI中转站失败: {str(e)}")
|
||||||
|
QMessageBox.warning(self, "警告", f"打开AI中转站失败: {str(e)}")
|
||||||
|
|
||||||
|
def _extract_session_token(self, raw_value: str) -> str:
|
||||||
|
"""从输入文本中提取 SessionToken,兼容纯 token / Cookie 串。"""
|
||||||
|
s = (raw_value or "").strip().strip('"').strip("'")
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
m = re.search(r"SessionToken\s*=\s*([^;\s]+)", s, flags=re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
return m.group(1).strip().strip('"').strip("'")
|
||||||
|
if ";" in s:
|
||||||
|
first = s.split(";", 1)[0].strip()
|
||||||
|
if "=" in first:
|
||||||
|
k, v = first.split("=", 1)
|
||||||
|
if k.strip().lower() in ("workoscursorsessiontoken", "sessiontoken"):
|
||||||
|
return v.strip().strip('"').strip("'")
|
||||||
|
return s.replace("SessionToken=", "").strip()
|
||||||
|
|
||||||
|
def _show_change_success_countdown_dialog(self, seconds: int = 4):
|
||||||
|
"""换号成功后显示倒计时提示框,倒计时结束自动关闭。"""
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle("提示")
|
||||||
|
dialog.setModal(True)
|
||||||
|
dialog.setFixedSize(420, 160)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
tip_label = QLabel("换号成功咯!请及时去确认收货哦!万分感谢!", dialog)
|
||||||
|
tip_label.setWordWrap(True)
|
||||||
|
tip_label.setAlignment(Qt.AlignCenter)
|
||||||
|
layout.addStretch()
|
||||||
|
layout.addWidget(tip_label)
|
||||||
|
|
||||||
|
countdown_label = QLabel("", dialog)
|
||||||
|
countdown_label.setAlignment(Qt.AlignCenter)
|
||||||
|
layout.addWidget(countdown_label)
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
remain = {"value": max(1, int(seconds))}
|
||||||
|
|
||||||
|
def refresh_text():
|
||||||
|
countdown_label.setText(f"{remain['value']} 秒后自动关闭")
|
||||||
|
|
||||||
|
refresh_text()
|
||||||
|
timer = QTimer(dialog)
|
||||||
|
|
||||||
|
def on_timeout():
|
||||||
|
remain["value"] -= 1
|
||||||
|
if remain["value"] <= 0:
|
||||||
|
timer.stop()
|
||||||
|
dialog.accept()
|
||||||
|
return
|
||||||
|
refresh_text()
|
||||||
|
|
||||||
|
timer.timeout.connect(on_timeout)
|
||||||
|
timer.start(1000)
|
||||||
|
dialog.exec()
|
||||||
|
|
||||||
def on_change_clicked(self):
|
def on_change_clicked(self):
|
||||||
token = self.txtToken.toPlainText().strip() if self.txtToken else ""
|
token = self.txtToken.toPlainText().strip() if self.txtToken else ""
|
||||||
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
||||||
@ -1042,16 +1328,6 @@ class MainWindow(QMainWindow):
|
|||||||
new_email = generate_random_email()
|
new_email = generate_random_email()
|
||||||
self.log(f"📧 生成新邮箱: {new_email}")
|
self.log(f"📧 生成新邮箱: {new_email}")
|
||||||
|
|
||||||
# 确认对话框
|
|
||||||
reply = QMessageBox.question(
|
|
||||||
self,
|
|
||||||
"确认",
|
|
||||||
f"确定要换号吗?\n新邮箱: {new_email}\n原账号数据将备份。",
|
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
|
||||||
)
|
|
||||||
if reply != QMessageBox.Yes:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 禁用按钮
|
# 禁用按钮
|
||||||
if self.btnChange:
|
if self.btnChange:
|
||||||
self.btnChange.setEnabled(False)
|
self.btnChange.setEnabled(False)
|
||||||
@ -1068,8 +1344,253 @@ class MainWindow(QMainWindow):
|
|||||||
dialog = DonateDialog(self)
|
dialog = DonateDialog(self)
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|
||||||
|
def on_usage_guide_clicked(self):
|
||||||
|
"""打开使用说明图片(非模态,可与其他窗口并行)。"""
|
||||||
|
if self._usage_guide_dialog and self._usage_guide_dialog.isVisible():
|
||||||
|
self._usage_guide_dialog.raise_()
|
||||||
|
self._usage_guide_dialog.activateWindow()
|
||||||
|
return
|
||||||
|
|
||||||
|
image_path = get_resource_path(os.path.join("assets", "images", "donate", "info.png"))
|
||||||
|
if not Path(image_path).is_file():
|
||||||
|
QMessageBox.warning(self, "提示", f"未找到使用说明图片:{image_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle("使用说明")
|
||||||
|
dialog.setMinimumSize(900, 700)
|
||||||
|
dialog.setModal(False)
|
||||||
|
dialog.setWindowModality(Qt.NonModal)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
tip = QLabel("可使用鼠标滚轮查看长图内容,可通过按钮缩放图片。")
|
||||||
|
tip.setAlignment(Qt.AlignCenter)
|
||||||
|
layout.addWidget(tip)
|
||||||
|
|
||||||
|
zoom_row = QHBoxLayout()
|
||||||
|
zoom_row.addStretch()
|
||||||
|
btn_zoom_out = QPushButton("缩小 -", dialog)
|
||||||
|
btn_zoom_reset = QPushButton("100%", dialog)
|
||||||
|
btn_zoom_in = QPushButton("放大 +", dialog)
|
||||||
|
zoom_label = QLabel("100%", dialog)
|
||||||
|
zoom_row.addWidget(btn_zoom_out)
|
||||||
|
zoom_row.addWidget(btn_zoom_reset)
|
||||||
|
zoom_row.addWidget(btn_zoom_in)
|
||||||
|
zoom_row.addWidget(zoom_label)
|
||||||
|
layout.addLayout(zoom_row)
|
||||||
|
|
||||||
|
scroll = QScrollArea(dialog)
|
||||||
|
scroll.setWidgetResizable(True)
|
||||||
|
|
||||||
|
label = QLabel(scroll)
|
||||||
|
original_pixmap = QPixmap(image_path)
|
||||||
|
zoom_state = {"scale": 1.0}
|
||||||
|
|
||||||
|
def apply_zoom():
|
||||||
|
if original_pixmap.isNull():
|
||||||
|
return
|
||||||
|
target_w = max(1, int(original_pixmap.width() * zoom_state["scale"]))
|
||||||
|
target_h = max(1, int(original_pixmap.height() * zoom_state["scale"]))
|
||||||
|
scaled = original_pixmap.scaled(
|
||||||
|
target_w,
|
||||||
|
target_h,
|
||||||
|
Qt.KeepAspectRatio,
|
||||||
|
Qt.SmoothTransformation,
|
||||||
|
)
|
||||||
|
label.setPixmap(scaled)
|
||||||
|
label.resize(scaled.size())
|
||||||
|
zoom_label.setText(f"{int(zoom_state['scale'] * 100)}%")
|
||||||
|
|
||||||
|
def zoom_in():
|
||||||
|
zoom_state["scale"] = min(3.0, zoom_state["scale"] * 1.2)
|
||||||
|
apply_zoom()
|
||||||
|
|
||||||
|
def zoom_out():
|
||||||
|
zoom_state["scale"] = max(0.2, zoom_state["scale"] / 1.2)
|
||||||
|
apply_zoom()
|
||||||
|
|
||||||
|
def zoom_reset():
|
||||||
|
zoom_state["scale"] = 1.0
|
||||||
|
apply_zoom()
|
||||||
|
|
||||||
|
btn_zoom_in.clicked.connect(zoom_in)
|
||||||
|
btn_zoom_out.clicked.connect(zoom_out)
|
||||||
|
btn_zoom_reset.clicked.connect(zoom_reset)
|
||||||
|
|
||||||
|
apply_zoom()
|
||||||
|
label.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
|
||||||
|
|
||||||
|
scroll.setWidget(label)
|
||||||
|
layout.addWidget(scroll, 1)
|
||||||
|
|
||||||
|
btn_close = QPushButton("关闭", dialog)
|
||||||
|
btn_close.clicked.connect(dialog.accept)
|
||||||
|
btn_row = QHBoxLayout()
|
||||||
|
btn_row.addStretch()
|
||||||
|
btn_row.addWidget(btn_close)
|
||||||
|
layout.addLayout(btn_row)
|
||||||
|
|
||||||
|
self._usage_guide_dialog = dialog
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
def on_emergency_repair_clicked(self):
|
def on_emergency_repair_clicked(self):
|
||||||
"""应急检修:下载工具到桌面"""
|
"""应急检修:弹出工具面板。"""
|
||||||
|
if self._emergency_dialog and self._emergency_dialog.isVisible():
|
||||||
|
self._emergency_dialog.raise_()
|
||||||
|
self._emergency_dialog.activateWindow()
|
||||||
|
return
|
||||||
|
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle("应急检修")
|
||||||
|
dialog.setMinimumSize(400, 300)
|
||||||
|
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
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def on_token_extract_clicked(self):
|
||||||
|
"""密码通过后打开 Token 提取窗口。"""
|
||||||
|
pwd, ok = QInputDialog.getText(
|
||||||
|
self,
|
||||||
|
"Token提取",
|
||||||
|
"请输入密码:",
|
||||||
|
QLineEdit.Password,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
if pwd != "920103":
|
||||||
|
self.log("❌ Token提取密码错误")
|
||||||
|
QMessageBox.warning(self, "提示", "密码错误,无法使用 Token 提取。")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._token_extract_dialog and self._token_extract_dialog.isVisible():
|
||||||
|
self._token_extract_dialog.raise_()
|
||||||
|
self._token_extract_dialog.activateWindow()
|
||||||
|
return
|
||||||
|
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle("Token提取")
|
||||||
|
dialog.setMinimumSize(700, 420)
|
||||||
|
dialog.setModal(False)
|
||||||
|
dialog.setWindowModality(Qt.NonModal)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
layout.addWidget(QLabel("Token:"))
|
||||||
|
|
||||||
|
token_display = QTextEdit(dialog)
|
||||||
|
token_display.setReadOnly(True)
|
||||||
|
token_display.setPlaceholderText("点击“读取Token”后将在此显示。")
|
||||||
|
layout.addWidget(token_display, 1)
|
||||||
|
|
||||||
|
btn_row = QHBoxLayout()
|
||||||
|
btn_read = QPushButton("读取Token", dialog)
|
||||||
|
btn_save = QPushButton("另存桌面", dialog)
|
||||||
|
btn_close = QPushButton("关闭", dialog)
|
||||||
|
btn_row.addWidget(btn_read)
|
||||||
|
btn_row.addWidget(btn_save)
|
||||||
|
btn_row.addStretch()
|
||||||
|
btn_row.addWidget(btn_close)
|
||||||
|
layout.addLayout(btn_row)
|
||||||
|
|
||||||
|
self._token_extract_dialog = dialog
|
||||||
|
self._token_display = token_display
|
||||||
|
|
||||||
|
btn_read.clicked.connect(self.read_cursor_token_for_dialog)
|
||||||
|
btn_save.clicked.connect(self.save_extracted_token_to_desktop)
|
||||||
|
btn_close.clicked.connect(dialog.close)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def _read_current_cursor_token(self) -> str:
|
||||||
|
"""从 Cursor 的 storage.json 读取当前 token。"""
|
||||||
|
config_dir = get_cursor_config_path()
|
||||||
|
storage_file = config_dir / "storage.json"
|
||||||
|
if not storage_file.exists():
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
with open(storage_file, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
data = json.loads(content) if content.strip() else {}
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
token = (
|
||||||
|
data.get("cursorAuth", {}).get("accessToken")
|
||||||
|
or data.get("cursorAuth", {}).get("refreshToken")
|
||||||
|
or data.get("cursorAccount", {}).get("token")
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
return str(token).strip()
|
||||||
|
|
||||||
|
def read_cursor_token_for_dialog(self):
|
||||||
|
token = self._read_current_cursor_token()
|
||||||
|
if not token:
|
||||||
|
self._current_extracted_token = ""
|
||||||
|
if self._token_display:
|
||||||
|
self._token_display.setPlainText("")
|
||||||
|
self.log("⚠️ 未读取到 Token")
|
||||||
|
QMessageBox.warning(self, "提示", "未读取到 Token,请确认 Cursor 配置是否存在。")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._current_extracted_token = token
|
||||||
|
if self._token_display:
|
||||||
|
self._token_display.setPlainText(token)
|
||||||
|
self.log("✅ 已读取当前 Token")
|
||||||
|
|
||||||
|
def save_extracted_token_to_desktop(self):
|
||||||
|
token = self._current_extracted_token
|
||||||
|
if not token:
|
||||||
|
token = self._read_current_cursor_token()
|
||||||
|
if token:
|
||||||
|
self._current_extracted_token = token
|
||||||
|
if self._token_display:
|
||||||
|
self._token_display.setPlainText(token)
|
||||||
|
if not token:
|
||||||
|
QMessageBox.warning(self, "提示", "没有可保存的 Token,请先点击“读取Token”。")
|
||||||
|
return
|
||||||
|
|
||||||
|
desktop = Path.home() / "Desktop"
|
||||||
|
save_path = desktop / "token.txt"
|
||||||
|
try:
|
||||||
|
with open(save_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(token)
|
||||||
|
self.log(f"✅ Token 已保存到桌面: {save_path.name}")
|
||||||
|
QMessageBox.information(self, "成功", "Token 已覆盖保存到桌面 token.txt")
|
||||||
|
except OSError as e:
|
||||||
|
self.log(f"❌ Token 保存失败: {e}")
|
||||||
|
QMessageBox.warning(self, "失败", f"保存失败:{e}")
|
||||||
|
|
||||||
|
def download_db_tool(self):
|
||||||
|
"""下载 DB Browser 到桌面。"""
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@ -1087,88 +1608,109 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
threading.Thread(target=download, daemon=True).start()
|
threading.Thread(target=download, daemon=True).start()
|
||||||
|
|
||||||
def on_query_usage_clicked(self):
|
def clear_cursor_cache(self):
|
||||||
"""查询当前token的额度使用情况"""
|
"""清除 %APPDATA%\\Cursor 目录。"""
|
||||||
import threading
|
if is_cursor_running():
|
||||||
|
msg_box = QMessageBox(self)
|
||||||
|
msg_box.setWindowTitle("Cursor正在运行")
|
||||||
|
msg_box.setText("检测到 Cursor 正在运行。\n请先关闭 Cursor 后再清除缓存。")
|
||||||
|
msg_box.setIcon(QMessageBox.Warning)
|
||||||
|
btn_force_close = msg_box.addButton("💀 强制关闭", QMessageBox.ActionRole)
|
||||||
|
btn_cancel = msg_box.addButton("取消", QMessageBox.RejectRole)
|
||||||
|
msg_box.setDefaultButton(btn_cancel)
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
token = self.txtToken.toPlainText().strip() if self.txtToken else ""
|
if msg_box.clickedButton() == btn_force_close:
|
||||||
if not token:
|
self.log("💀 正在强制关闭Cursor...")
|
||||||
QMessageBox.warning(self, "提示", "请先在输入框中填入Token")
|
if kill_cursor():
|
||||||
|
self.log("✅ Cursor已关闭")
|
||||||
|
else:
|
||||||
|
self.log("⚠️ 未找到运行中的Cursor进程")
|
||||||
|
QMessageBox.warning(self, "警告", "未找到运行中的Cursor进程")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.log("ℹ️ 已取消清除缓存。")
|
||||||
|
return
|
||||||
|
|
||||||
|
cursor_dir = Path(os.environ.get("APPDATA", "")) / "Cursor"
|
||||||
|
if not cursor_dir.exists():
|
||||||
|
self.log("ℹ️ 未找到 Cursor 缓存目录,无需清理。")
|
||||||
|
QMessageBox.information(self, "提示", "未找到 Cursor 缓存目录,无需清理。")
|
||||||
return
|
return
|
||||||
|
|
||||||
def query():
|
|
||||||
try:
|
try:
|
||||||
clean_token = token.strip().replace('"', '').replace("SessionToken=", "")
|
shutil.rmtree(cursor_dir)
|
||||||
resp = requests.get(
|
self.log("✅ Cursor 缓存清除成功")
|
||||||
"https://cursor.com/api/usage",
|
QMessageBox.information(self, "完成", "Cursor 缓存已清除成功。")
|
||||||
headers={
|
return
|
||||||
"Cookie": f"SessionToken={clean_token}",
|
except PermissionError:
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
|
self.log("⚠️ 权限不足,尝试请求管理员权限删除缓存...")
|
||||||
"Accept": "application/json",
|
|
||||||
},
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
if resp.status_code == 401:
|
|
||||||
msg = "查询失败:Token 已失效 (401 Unauthorized)。请检查账号是否已退出或被封。"
|
|
||||||
else:
|
|
||||||
resp.raise_for_status()
|
|
||||||
data = resp.json()
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
premium = data.get("premiumUsage", {})
|
|
||||||
if premium:
|
|
||||||
used = premium.get("numRequestsTotal", 0)
|
|
||||||
limit = premium.get("maxRequestUsage", "无限制")
|
|
||||||
lines.append(f"高级模型 (GPT-4/Claude): 已用 {used} / {limit}")
|
|
||||||
|
|
||||||
for model_key, model_data in data.items():
|
|
||||||
if isinstance(model_data, dict) and "numRequestsTotal" in model_data:
|
|
||||||
used = model_data.get("numRequestsTotal", 0)
|
|
||||||
limit = model_data.get("maxRequestUsage")
|
|
||||||
limit_str = str(limit) if limit is not None else "无限制"
|
|
||||||
lines.append(f"{model_key}: 已用 {used} / {limit_str}")
|
|
||||||
|
|
||||||
msg = "\n".join(lines) if lines else "暂无额度数据,请检查账号状态。"
|
|
||||||
|
|
||||||
QMetaObject.invokeMethod(
|
|
||||||
self, "_show_usage_result",
|
|
||||||
Qt.ConnectionType.QueuedConnection,
|
|
||||||
Q_ARG(str, msg)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMetaObject.invokeMethod(
|
self.log(f"❌ 删除缓存失败: {e}")
|
||||||
self, "_show_usage_result",
|
QMessageBox.warning(self, "失败", f"删除缓存失败:{e}")
|
||||||
Qt.ConnectionType.QueuedConnection,
|
return
|
||||||
Q_ARG(str, f"网络请求错误:{str(e)}")
|
|
||||||
)
|
|
||||||
|
|
||||||
threading.Thread(target=query, daemon=True).start()
|
if sys.platform != "win32":
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"失败",
|
||||||
|
"权限不足,无法删除缓存目录。请手动使用管理员权限删除。",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 提权删除:弹 UAC 运行 PowerShell 删除 APPDATA 下的 Cursor 目录。
|
||||||
|
ps_cmd = (
|
||||||
|
"Remove-Item -LiteralPath \"$env:APPDATA\\Cursor\" -Recurse -Force "
|
||||||
|
"-ErrorAction Stop"
|
||||||
|
)
|
||||||
|
rc = ctypes.windll.shell32.ShellExecuteW(
|
||||||
|
None,
|
||||||
|
"runas",
|
||||||
|
"powershell.exe",
|
||||||
|
f"-NoProfile -ExecutionPolicy Bypass -Command \"{ps_cmd}\"",
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
if rc <= 32:
|
||||||
|
self.log("❌ 提权删除未启动,请手动以管理员身份操作。")
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"权限不足",
|
||||||
|
"无法自动提权。\n\n"
|
||||||
|
"请手动执行:\n"
|
||||||
|
"1. 关闭 Cursor\n"
|
||||||
|
"2. 右键 PowerShell 选择“以管理员身份运行”\n"
|
||||||
|
"3. 执行命令:Remove-Item -LiteralPath \"$env:APPDATA\\Cursor\" -Recurse -Force",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("ℹ️ 已发起管理员删除请求,请在 UAC 窗口中确认。")
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"已请求管理员权限",
|
||||||
|
"已发起管理员权限删除请求。\n请在弹出的 UAC 窗口中确认后完成清理。",
|
||||||
|
)
|
||||||
|
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def _show_usage_result(self, msg):
|
def _show_usage_result(self, msg):
|
||||||
QMessageBox.information(self, "额度查询结果", msg)
|
QMessageBox.information(self, "额度查询结果", msg)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
@Slot(bool, str)
|
||||||
|
def on_change_finished(self, success, message):
|
||||||
if self.btnChange:
|
if self.btnChange:
|
||||||
self.btnChange.setEnabled(True)
|
self.btnChange.setEnabled(True)
|
||||||
self.btnChange.setText("🚀 开始换号")
|
self.btnChange.setText("🚀 开始换号")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.backup_path = message
|
self.backup_path = message
|
||||||
QMessageBox.information(self, "成功", "换号完成!\n即将打开Cursor...")
|
self.log("✅ 换号完成")
|
||||||
|
self._show_change_success_countdown_dialog(4)
|
||||||
self.log("🚀 正在打开Cursor...")
|
self.log("🚀 正在打开Cursor...")
|
||||||
try:
|
|
||||||
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
cursor_path = self.txtCursorPath.text().strip() if self.txtCursorPath else ""
|
||||||
if sys.platform == "win32":
|
self._launch_cursor(cursor_path)
|
||||||
os.startfile(cursor_path)
|
|
||||||
elif sys.platform == "darwin":
|
|
||||||
subprocess.Popen(["open", cursor_path])
|
|
||||||
else:
|
|
||||||
subprocess.Popen([cursor_path])
|
|
||||||
self.log("✅ Cursor已启动")
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f"❌ 打开Cursor失败: {str(e)}")
|
|
||||||
QMessageBox.warning(self, "警告", f"打开Cursor失败: {str(e)}")
|
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "失败", message)
|
QMessageBox.critical(self, "失败", message)
|
||||||
|
|
||||||
|
|||||||
2012
main_backup.py
Normal file
2012
main_backup.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user