2 Commits

6 changed files with 335 additions and 119 deletions

4
.gitignore vendored
View File

@@ -17,4 +17,6 @@ venv/
env/
# Local development settings
.env
.env
testversion.txt

View File

@@ -165,14 +165,41 @@ class AccountSwitcher:
"code": code
}
response = requests.post(
self.config.get_api_url("activate"),
json=data,
headers={"Content-Type": "application/json"},
timeout=10
)
# 禁用SSL警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 设置请求参数
request_kwargs = {
"json": data,
"headers": {"Content-Type": "application/json"},
"timeout": 5, # 减少超时时间从10秒到5秒
"verify": False # 禁用SSL验证
}
# 尝试发送请求
try:
response = requests.post(
self.config.get_api_url("activate"),
**request_kwargs
)
except requests.exceptions.SSLError:
# 如果发生SSL错误创建自定义SSL上下文
import ssl
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# 使用自定义SSL上下文重试请求
session = requests.Session()
session.verify = False
response = session.post(
self.config.get_api_url("activate"),
**request_kwargs
)
result = response.json()
# 激活成功
if result["code"] == 200:
api_data = result["data"]
# 构造标准的返回数据结构
@@ -184,22 +211,18 @@ class AccountSwitcher:
"device_info": self.get_device_info()
}
return True, result["msg"], account_info
return False, result["msg"], {
"status": "inactive",
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": self.get_device_info()
}
# 激活码无效或已被使用
elif result["code"] == 400:
logging.warning(f"激活码无效或已被使用: {result.get('msg', '未知错误')}")
return False, result.get("msg", "激活码无效或已被使用"), None
# 其他错误情况
else:
logging.error(f"激活失败: {result.get('msg', '未知错误')}")
return False, result["msg"], None # 返回 None 而不是空的账号信息
except Exception as e:
return False, f"激活失败: {str(e)}", {
"status": "inactive",
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": self.get_device_info()
}
logging.error(f"激活失败: {str(e)}")
return False, f"激活失败: {str(e)}", None # 返回 None 而不是空的账号信息
def reset_machine_id(self) -> bool:
"""重置机器码"""
@@ -310,28 +333,38 @@ class AccountSwitcher:
api_url = self.config.get_api_url("status")
logging.info(f"正在检查会员状态...")
logging.info(f"API URL: {api_url}")
logging.info(f"请求数据: {data}")
response = requests.post(
api_url,
json=data,
headers={"Content-Type": "application/json"},
timeout=10
)
# 设置请求参数
request_kwargs = {
"json": data,
"headers": {"Content-Type": "application/json"},
"timeout": 2, # 减少超时时间到2秒
"verify": False
}
# 使用session来复用连接
session = requests.Session()
session.verify = False
# 尝试发送请求
try:
response = session.post(api_url, **request_kwargs)
except requests.exceptions.Timeout:
# 超时后快速重试一次
logging.warning("首次请求超时,正在重试...")
response = session.post(api_url, **request_kwargs)
result = response.json()
logging.info(f"状态检查响应: {result}")
if result.get("code") in [1, 200]: # 兼容两种响应码
if result.get("code") in [1, 200]:
api_data = result.get("data", {})
# 构造标准的返回数据结构
return {
"status": api_data.get("status", "inactive"),
"expire_time": api_data.get("expire_time", ""),
"total_days": api_data.get("total_days", 0),
"days_left": api_data.get("days_left", 0),
"device_info": self.get_device_info() # 添加设备信息
"device_info": self.get_device_info()
}
else:
error_msg = result.get("msg", "未知错误")
@@ -428,17 +461,33 @@ class AccountSwitcher:
"Content-Type": "application/json"
}
# 禁用SSL警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 设置请求参数
request_kwargs = {
"json": data,
"headers": headers,
"timeout": 30, # 增加超时时间
"verify": False # 禁用SSL验证
}
try:
# 添加SSL验证选项和超时设置
response = requests.post(
endpoint,
json=data,
headers=headers,
timeout=30, # 增加超时时间
verify=False, # 禁用SSL验证
)
# 禁用SSL警告
requests.packages.urllib3.disable_warnings()
# 尝试发送请求
try:
response = requests.post(endpoint, **request_kwargs)
except requests.exceptions.SSLError:
# 如果发生SSL错误创建自定义SSL上下文
import ssl
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
# 使用自定义SSL上下文重试请求
session = requests.Session()
session.verify = False
response = session.post(endpoint, **request_kwargs)
response_data = response.json()

View File

@@ -44,6 +44,7 @@ class LoadingDialog(QDialog):
self.progress_bar = QProgressBar()
self.progress_bar.setTextVisible(False)
self.progress_bar.setRange(0, 0) # 设置为循环模式
self.progress_bar.setMinimumWidth(250) # 设置最小宽度
layout.addWidget(self.progress_bar)
self.setLayout(layout)
@@ -63,13 +64,54 @@ class LoadingDialog(QDialog):
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
min-height: 12px;
}
QProgressBar::chunk {
background-color: #0d6efd;
width: 10px;
width: 15px;
margin: 0.5px;
}
""")
# 添加定时器以自动更新进度条动画
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
self.progress_value = 0
def update_progress(self):
"""更新进度条动画"""
self.progress_value = (self.progress_value + 1) % 100
self.progress_bar.setValue(self.progress_value)
def showEvent(self, event):
"""显示时启动定时器"""
super().showEvent(event)
self.timer.start(50) # 每50毫秒更新一次
def hideEvent(self, event):
"""隐藏时停止定时器"""
self.timer.stop()
super().hideEvent(event)
def check_status(self):
"""检查会员状态从API获取"""
try:
# 只在首次检查时显示加载对话框,并设置更明确的提示
if self._activation_status is None:
self.show_loading_dialog("正在连接服务器,请稍候...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.get_member_status)
self.worker.finished.connect(self.on_status_check_complete)
self.worker.start()
except Exception as e:
if self._activation_status is None:
self.hide_loading_dialog()
QMessageBox.critical(self, "错误", f"检查状态失败: {str(e)}")
self._activation_status = False
logging.error(f"检查状态时发生错误: {str(e)}")
return False
class ApiWorker(QThread):
"""API请求工作线程"""
@@ -111,33 +153,15 @@ class MainWindow(QMainWindow):
# 设置窗口图标
icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "icon", "two.ico")
if os.path.exists(icon_path):
window_icon = QIcon(icon_path)
if not window_icon.isNull():
self.setWindowIcon(window_icon)
self.window_icon = QIcon(icon_path)
if not self.window_icon.isNull():
self.setWindowIcon(self.window_icon)
logging.info(f"成功设置窗口图标: {icon_path}")
else:
logging.warning("图标文件加载失败")
# 创建系统托盘图标
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.windowIcon())
self.tray_icon.setToolTip("听泉Cursor助手")
# 创建托盘菜单
tray_menu = QMenu()
show_action = tray_menu.addAction("显示主窗口")
show_action.triggered.connect(self.show)
quit_action = tray_menu.addAction("退出")
quit_action.triggered.connect(QApplication.instance().quit)
# 设置托盘菜单
self.tray_icon.setContextMenu(tray_menu)
# 连接托盘图标的信号
self.tray_icon.activated.connect(self.on_tray_icon_activated)
# 显示托盘图标
self.tray_icon.show()
self.create_tray_icon()
# 创建主窗口部件
central_widget = QWidget()
@@ -282,24 +306,103 @@ class MainWindow(QMainWindow):
# 启动时检查一次状态
QTimer.singleShot(0, self.check_status)
def create_tray_icon(self):
"""创建系统托盘图标"""
try:
# 确保QSystemTrayIcon可用
if not QSystemTrayIcon.isSystemTrayAvailable():
logging.error("系统托盘不可用")
return
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.window_icon)
self.tray_icon.setToolTip("听泉Cursor助手")
# 创建托盘菜单
self.tray_menu = QMenu() # 保存为实例变量
# 添加刷新重置按钮
refresh_action = self.tray_menu.addAction("刷新重置")
refresh_action.triggered.connect(self.refresh_cursor_auth)
show_action = self.tray_menu.addAction("显示主窗口")
show_action.triggered.connect(self.show_and_activate)
# 添加分隔线
self.tray_menu.addSeparator()
quit_action = self.tray_menu.addAction("退出")
quit_action.triggered.connect(self.quit_application)
# 设置托盘菜单
self.tray_icon.setContextMenu(self.tray_menu)
# 连接托盘图标的信号
self.tray_icon.activated.connect(self.on_tray_icon_activated)
# 显示托盘图标
self.tray_icon.show()
logging.info("系统托盘图标创建成功")
except Exception as e:
logging.error(f"创建系统托盘图标失败: {str(e)}")
def show_and_activate(self):
"""显示并激活窗口"""
self.show()
self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
self.activateWindow()
self.raise_() # 确保窗口在最前面
def quit_application(self):
"""退出应用程序"""
try:
# 停止定时器
if self._status_timer:
self._status_timer.stop()
# 移除托盘图标
if hasattr(self, 'tray_icon'):
self.tray_icon.setVisible(False) # 先隐藏托盘图标
self.tray_icon.deleteLater() # 删除托盘图标
# 退出应用程序
QApplication.instance().quit()
except Exception as e:
logging.error(f"退出应用程序时发生错误: {str(e)}")
QApplication.instance().quit()
def on_tray_icon_activated(self, reason):
"""处理托盘图标的点击事件"""
if reason == QSystemTrayIcon.DoubleClick:
self.show()
self.activateWindow()
if reason in (QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger):
self.show_and_activate()
def closeEvent(self, event):
"""重写关闭事件,最小化到托盘而不是退出"""
if hasattr(self, 'tray_icon') and self.tray_icon.isVisible():
event.ignore()
self.hide()
self.tray_icon.showMessage(
"听泉Cursor助手",
"程序已最小化到系统托盘",
QSystemTrayIcon.Information,
2000
)
else:
try:
if hasattr(self, 'tray_icon') and self.tray_icon.isVisible():
event.ignore()
self.hide()
# 确保托盘图标显示
self.tray_icon.show()
self.tray_icon.showMessage(
"听泉Cursor助手",
"程序已最小化到系统托盘",
QSystemTrayIcon.Information,
2000
)
else:
# 如果托盘图标不可用,则正常退出
if self._status_timer:
self._status_timer.stop()
event.accept()
except Exception as e:
logging.error(f"处理关闭事件时发生错误: {str(e)}")
# 发生错误时,接受关闭事件
if self._status_timer:
self._status_timer.stop()
event.accept()
if self._status_timer:
@@ -313,35 +416,41 @@ class MainWindow(QMainWindow):
def show_loading_dialog(self, message="请稍候..."):
"""显示加载对话框"""
self.loading_dialog = LoadingDialog(self, message)
self.loading_dialog.setStyleSheet("""
QDialog {
background-color: #f8f9fa;
}
QLabel {
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
}
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
}
QProgressBar::chunk {
background-color: #0d6efd;
width: 10px;
margin: 0.5px;
}
""")
if not hasattr(self, 'loading_dialog') or not self.loading_dialog:
self.loading_dialog = LoadingDialog(self, message)
self.loading_dialog.setStyleSheet("""
QDialog {
background-color: #f8f9fa;
}
QLabel {
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
}
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
}
QProgressBar::chunk {
background-color: #0d6efd;
width: 10px;
margin: 0.5px;
}
""")
else:
self.loading_dialog.message_label.setText(message)
self.loading_dialog.show()
QApplication.processEvents() # 确保对话框立即显示
def hide_loading_dialog(self):
"""隐藏加载对话框"""
if hasattr(self, 'loading_dialog'):
if hasattr(self, 'loading_dialog') and self.loading_dialog:
self.loading_dialog.hide()
self.loading_dialog.deleteLater()
self.loading_dialog = None
QApplication.processEvents() # 确保对话框立即隐藏
def activate_account(self):
"""激活账号"""
@@ -512,7 +621,7 @@ class MainWindow(QMainWindow):
if isinstance(data, tuple):
success, message, account_info = data
if success:
if success and account_info is not None:
# 更新会员信息显示
self.update_status_display(account_info)
# 更新激活状态缓存
@@ -603,20 +712,10 @@ class MainWindow(QMainWindow):
}
""")
msg.exec_()
# 更新显示为未激活状态
self.update_status_display(account_info)
# 所有失败情况都不更新状态显示
else:
QMessageBox.critical(self, "错误", f"激活失败: {data}")
# 更新为未激活状态
device_info = self.switcher.get_device_info()
inactive_status = {
"status": "inactive",
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": device_info
}
self.update_status_display(inactive_status)
# 更新状态显示
def update_status_display(self, status_info: dict):
"""更新状态显示"""
@@ -660,7 +759,7 @@ class MainWindow(QMainWindow):
try:
# 只在首次检查时显示加载对话框
if self._activation_status is None:
self.show_loading_dialog("正在检查会员状态,请稍候...")
self.show_loading_dialog("正在连接服务器...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.get_member_status)

21
main.py
View File

@@ -5,12 +5,17 @@ import os
import atexit
import shutil
import tempfile
import urllib3
from pathlib import Path
from PyQt5.QtWidgets import QApplication, QMessageBox, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from gui.main_window import MainWindow
# 禁用所有 SSL 相关警告
urllib3.disable_warnings()
logging.getLogger('urllib3').setLevel(logging.ERROR)
def cleanup_temp():
"""清理临时文件"""
try:
@@ -55,6 +60,15 @@ def main():
# 创建QApplication实例
app = QApplication(sys.argv)
# 检查系统托盘是否可用
if not QSystemTrayIcon.isSystemTrayAvailable():
logging.error("系统托盘不可用")
QMessageBox.critical(None, "错误", "系统托盘不可用,程序无法正常运行。")
return 1
# 设置应用程序不会在最后一个窗口关闭时退出
app.setQuitOnLastWindowClosed(False)
setup_logging()
# 检查Python版本
@@ -98,7 +112,8 @@ def main():
logging.info("正在启动主窗口...")
window.show()
sys.exit(app.exec_())
return app.exec_()
except Exception as e:
error_msg = f"程序运行出错: {str(e)}\n{traceback.format_exc()}"
@@ -107,7 +122,7 @@ def main():
if QApplication.instance() is None:
app = QApplication(sys.argv)
QMessageBox.critical(None, "错误", error_msg)
sys.exit(1)
return 1
if __name__ == "__main__":
main()
sys.exit(main())

51
testbuild.bat Normal file
View File

@@ -0,0 +1,51 @@
@echo off
chcp 65001 >nul
setlocal EnableDelayedExpansion
REM 激活虚拟环境
call venv\Scripts\activate.bat
REM 确保安装了必要的包
pip install -r requirements.txt
REM 读取当前版本号
set /p VERSION=<version.txt
echo 当前正式版本: %VERSION%
REM 读取测试版本号(如果存在)
if exist testversion.txt (
set /p TEST_VERSION=<testversion.txt
) else (
set TEST_VERSION=0
)
REM 增加测试版本号
set /a TEST_VERSION+=1
echo !TEST_VERSION!>testversion.txt
echo 测试版本号: !TEST_VERSION!
REM 组合完整版本号
set FULL_VERSION=%VERSION%.!TEST_VERSION!
echo 完整版本号: !FULL_VERSION!
REM 创建测试版本输出目录
if not exist "dist\test" mkdir "dist\test"
REM 清理旧文件
if exist "dist\听泉cursor助手%VERSION%.exe" del "dist\听泉cursor助手%VERSION%.exe"
if exist "build" rmdir /s /q "build"
REM 执行打包
venv\Scripts\python.exe -m PyInstaller build_nezha.spec --clean
REM 移动并重命名文件
move "dist\听泉cursor助手%VERSION%.exe" "dist\test\听泉cursor助手v!FULL_VERSION!.exe"
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: dist\test\听泉cursor助手v!FULL_VERSION!.exe
REM 退出虚拟环境
deactivate
pause

View File

@@ -1 +1 @@
3.3.9
3.4.0