import sys from pathlib import Path import logging import os from PIL import Image from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFrame, QTextEdit, QMessageBox, QApplication, QSystemTrayIcon, QMenu, QDialog, QProgressBar) from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal from PyQt5.QtGui import QIcon, QPixmap sys.path.append(str(Path(__file__).parent.parent)) from utils.config import Config from account_switcher import AccountSwitcher def get_version(): try: version_file = Path(__file__).parent.parent / 'version.txt' with open(version_file, 'r', encoding='utf-8') as f: return f.read().strip() except Exception as e: logging.error(f"读取版本号失败: {str(e)}") return "未知版本" class LoadingDialog(QDialog): """加载对话框""" def __init__(self, parent=None, message="请稍候..."): super().__init__(parent) self.setWindowTitle("处理中") self.setFixedSize(300, 100) self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) layout = QVBoxLayout() # 添加消息标签 self.message_label = QLabel(message) self.message_label.setAlignment(Qt.AlignCenter) layout.addWidget(self.message_label) # 添加进度条 self.progress_bar = QProgressBar() self.progress_bar.setTextVisible(False) self.progress_bar.setRange(0, 0) # 设置为循环模式 layout.addWidget(self.progress_bar) self.setLayout(layout) # 设置样式 self.setStyleSheet(""" QDialog { background-color: #f8f9fa; } QLabel { color: #333333; font-size: 14px; padding: 10px; } QProgressBar { border: 2px solid #e9ecef; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: #0d6efd; width: 10px; margin: 0.5px; } """) class ApiWorker(QThread): """API请求工作线程""" finished = pyqtSignal(tuple) # 发送结果信号 def __init__(self, func, *args, **kwargs): super().__init__() self.func = func self.args = args self.kwargs = kwargs def run(self): try: result = self.func(*self.args, **self.kwargs) self.finished.emit((True, result)) except Exception as e: self.finished.emit((False, str(e))) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.config = Config() self.switcher = AccountSwitcher() # 添加激活状态缓存 self._activation_status = None # 缓存的激活状态 self._status_timer = None # 状态更新定时器 version = get_version() cursor_version = self.switcher.get_cursor_version() self.setWindowTitle(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})") self.setMinimumSize(600, 500) # 设置窗口图标 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) logging.info(f"成功设置窗口图标: {icon_path}") # 创建系统托盘图标 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() # 创建主窗口部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 创建主布局 main_layout = QVBoxLayout(central_widget) # 设备ID区域 device_frame = QFrame() device_layout = QHBoxLayout(device_frame) device_layout.addWidget(QLabel("设备识别码(勿动):")) self.hardware_id_edit = QLineEdit(self.switcher.hardware_id) self.hardware_id_edit.setReadOnly(True) device_layout.addWidget(self.hardware_id_edit) copy_btn = QPushButton("复制ID") copy_btn.clicked.connect(self.copy_device_id) device_layout.addWidget(copy_btn) main_layout.addWidget(device_frame) # 会员状态区域 status_frame = QFrame() status_layout = QVBoxLayout(status_frame) status_layout.addWidget(QLabel("会员状态")) self.status_text = QTextEdit() self.status_text.setReadOnly(True) self.status_text.setMinimumHeight(100) status_layout.addWidget(self.status_text) main_layout.addWidget(status_frame) # 激活区域 activation_frame = QFrame() activation_layout = QVBoxLayout(activation_frame) activation_layout.addWidget(QLabel("激活(叠加)会员,多个激活码可叠加整体时长")) input_frame = QFrame() input_layout = QHBoxLayout(input_frame) input_layout.addWidget(QLabel("激活码:")) self.activation_edit = QLineEdit() input_layout.addWidget(self.activation_edit) activate_btn = QPushButton("激活") activate_btn.clicked.connect(self.activate_account) input_layout.addWidget(activate_btn) activation_layout.addWidget(input_frame) main_layout.addWidget(activation_frame) # 操作按钮区域 btn_frame = QFrame() btn_layout = QVBoxLayout(btn_frame) # 设置按钮样式 button_style = """ QPushButton { background-color: #0d6efd; color: white; border: none; padding: 15px; border-radius: 6px; font-size: 14px; min-width: 200px; margin: 5px; } QPushButton:hover { background-color: #0b5ed7; } QPushButton:pressed { background-color: #0a58ca; } """ # 刷新授权按钮 refresh_btn = QPushButton("刷新Cursor编辑器授权") refresh_btn.setStyleSheet(button_style) refresh_btn.clicked.connect(self.refresh_cursor_auth) refresh_btn.setMinimumHeight(50) btn_layout.addWidget(refresh_btn) # 突破限制按钮 bypass_btn = QPushButton("突破Cursor0.45.x限制") bypass_btn.setStyleSheet(button_style.replace("#0d6efd", "#198754").replace("#0b5ed7", "#157347").replace("#0a58ca", "#146c43")) bypass_btn.clicked.connect(self.dummy_function) bypass_btn.setMinimumHeight(50) btn_layout.addWidget(bypass_btn) # 禁用更新按钮 disable_update_btn = QPushButton("禁用Cursor版本更新") disable_update_btn.setStyleSheet(button_style.replace("#0d6efd", "#dc3545").replace("#0b5ed7", "#bb2d3b").replace("#0a58ca", "#b02a37")) disable_update_btn.clicked.connect(self.disable_cursor_update) disable_update_btn.setMinimumHeight(50) btn_layout.addWidget(disable_update_btn) # 设置按钮间距 btn_layout.setSpacing(10) btn_layout.setContentsMargins(20, 10, 20, 10) main_layout.addWidget(btn_frame) # 启动时检查一次状态 QTimer.singleShot(0, self.check_status) def on_tray_icon_activated(self, reason): """处理托盘图标的点击事件""" if reason == QSystemTrayIcon.DoubleClick: self.show() self.activateWindow() 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: event.accept() if self._status_timer: self._status_timer.stop() super().closeEvent(event) def copy_device_id(self): """复制设备ID到剪贴板""" if not self.check_status(): return QApplication.clipboard().setText(self.hardware_id_edit.text()) QMessageBox.information(self, "提示", "设备ID已复制到剪贴板") def show_loading_dialog(self, message="请稍候..."): """显示加载对话框""" self.loading_dialog = LoadingDialog(self, message) self.loading_dialog.show() def hide_loading_dialog(self): """隐藏加载对话框""" if hasattr(self, 'loading_dialog'): self.loading_dialog.hide() self.loading_dialog.deleteLater() def activate_account(self): """激活账号""" code = self.activation_edit.text().strip() if not code: msg = QMessageBox(self) msg.setWindowTitle("提示") msg.setText("请输入激活码") msg.setIcon(QMessageBox.Warning) msg.setStyleSheet(""" QMessageBox { background-color: #f8f9fa; min-width: 400px; padding: 20px; } QMessageBox QLabel { color: #333333; font-size: 14px; min-height: 40px; padding: 10px; qproperty-alignment: AlignCenter; } QPushButton { background-color: #0d6efd; color: white; border: none; padding: 8px 25px; border-radius: 4px; font-size: 13px; min-width: 100px; margin: 10px; } QPushButton:hover { background-color: #0b5ed7; } """) msg.exec_() return try: # 显示加载对话框 self.show_loading_dialog("正在验证激活码,请稍候...") # 创建工作线程 self.worker = ApiWorker(self.switcher.check_activation_code, code) self.worker.finished.connect(self.on_activation_complete) self.worker.start() except Exception as e: self.hide_loading_dialog() msg = QMessageBox(self) msg.setWindowTitle("错误") msg.setText(f"激活失败: {str(e)}") msg.setIcon(QMessageBox.Critical) msg.setStyleSheet(""" QMessageBox { background-color: #f8f9fa; min-width: 450px; padding: 20px; } QMessageBox QLabel { color: #333333; font-size: 14px; padding: 10px; margin: 10px; } QMessageBox QLabel:first-child { font-weight: bold; color: #dc3545; } QPushButton { background-color: #dc3545; color: white; border: none; padding: 8px 25px; border-radius: 4px; font-size: 13px; min-width: 100px; margin: 10px; } QPushButton:hover { background-color: #bb2d3b; } """) msg.exec_() def on_activation_complete(self, result): """激活完成回调""" success, data = result self.hide_loading_dialog() if isinstance(data, tuple): success, message, account_info = data if success: # 更新会员信息显示 self.update_status_display(account_info) # 更新激活状态缓存 self._activation_status = True # 更新状态定时器 days_left = account_info.get('days_left', 0) seconds_left = days_left * 24 * 60 * 60 if self._status_timer: self._status_timer.stop() self._status_timer = QTimer(self) self._status_timer.setSingleShot(True) self._status_timer.timeout.connect(self.check_status) update_interval = min(seconds_left - 60, 24 * 60 * 60) # 最长1天更新一次 if update_interval > 0: self._status_timer.start(update_interval * 1000) # 转换为毫秒 msg = QMessageBox(self) msg.setWindowTitle("激活成功") msg.setText("激活成功!") msg.setInformativeText(message) msg.setIcon(QMessageBox.Information) msg.setStyleSheet(""" QMessageBox { background-color: #f8f9fa; min-width: 500px; padding: 20px; } QMessageBox QLabel { color: #333333; font-size: 14px; padding: 10px; margin: 10px; } QMessageBox QLabel:first-child { font-weight: bold; font-size: 16px; color: #198754; } QPushButton { background-color: #198754; color: white; border: none; padding: 8px 25px; border-radius: 4px; font-size: 13px; min-width: 100px; margin: 10px; } QPushButton:hover { background-color: #157347; } """) msg.exec_() # 清空激活码输入框 self.activation_edit.clear() else: msg = QMessageBox(self) msg.setWindowTitle("激活失败") msg.setText(message) msg.setIcon(QMessageBox.Critical) msg.setStyleSheet(""" QMessageBox { background-color: #f8f9fa; min-width: 450px; padding: 20px; } QMessageBox QLabel { color: #333333; font-size: 14px; padding: 10px; margin: 10px; } QMessageBox QLabel:first-child { font-weight: bold; color: #dc3545; } QPushButton { background-color: #dc3545; color: white; border: none; padding: 8px 25px; border-radius: 4px; font-size: 13px; min-width: 100px; margin: 10px; } QPushButton:hover { background-color: #bb2d3b; } """) msg.exec_() else: QMessageBox.critical(self, "错误", f"激活失败: {data}") # 激活后检查一次状态 self.check_status() def update_status_display(self, status_info: dict): """更新状态显示""" # 打印API返回的原始数据 logging.info("=== API返回数据 ===") logging.info(f"状态信息: {status_info}") if 'activation_records' in status_info: logging.info("激活记录:") for record in status_info['activation_records']: logging.info(f"- 记录: {record}") # 更新状态文本 status_map = { "active": "正常", "inactive": "未激活", "expired": "已过期" } status_text = status_map.get(status_info.get('status', 'inactive'), "未知") # 构建状态文本 status_lines = [ f"会员状态:{status_text}", f"到期时间:{status_info.get('expire_time', '')}", f"总天数:{status_info.get('total_days', 0)}天", f"剩余天数:{status_info.get('days_left', 0)}天" ] # 如果有激活记录,显示最近一次激活信息 activation_records = status_info.get('activation_records', []) if activation_records: latest_record = activation_records[-1] # 获取最新的激活记录 device_info = latest_record.get('device_info', {}) status_lines.extend([ "", "最近激活信息:", f"激活码:{latest_record.get('code', '')}", f"激活时间:{latest_record.get('activation_time', '')}", f"增加天数:{latest_record.get('days', 0)}天", "", "设备信息:", f"系统:{device_info.get('os', '')}", f"设备名:{device_info.get('device_name', '')}", f"IP地址:{device_info.get('ip', '')}", f"地区:{device_info.get('location', '--')}" ]) # 更新状态文本 self.status_text.setPlainText("\n".join(status_lines)) def check_status(self): """检查会员状态(从API获取)""" try: # 显示加载对话框 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: self.hide_loading_dialog() QMessageBox.critical(self, "错误", f"检查状态失败: {str(e)}") self._activation_status = False return False def on_status_check_complete(self, result): """状态检查完成回调""" success, data = result self.hide_loading_dialog() if success and data: self.update_status_display(data) # 更新缓存的激活状态 self._activation_status = data.get('status') == 'active' # 设置状态更新定时器 if self._activation_status: # 获取剩余时间(秒) days_left = data.get('days_left', 0) seconds_left = days_left * 24 * 60 * 60 # 创建定时器,在到期前1分钟更新状态 if self._status_timer: self._status_timer.stop() self._status_timer = QTimer(self) self._status_timer.setSingleShot(True) self._status_timer.timeout.connect(self.check_status) update_interval = min(seconds_left - 60, 24 * 60 * 60) # 最长1天更新一次 if update_interval > 0: self._status_timer.start(update_interval * 1000) # 转换为毫秒 return self._activation_status else: # 更新为未激活状态 inactive_status = { "hardware_id": self.switcher.hardware_id, "expire_time": "", "days_left": 0, "total_days": 0, "status": "inactive", "activation_records": [] } self.update_status_display(inactive_status) self._activation_status = False return False def check_activation_status(self) -> bool: """检查是否已激活(使用缓存) Returns: bool: 是否已激活 """ if self._activation_status is None: # 首次检查,从API获取 return self.check_status() if not self._activation_status: msg = QMessageBox(self) msg.setWindowTitle("会员未激活") msg.setText("您还未激活会员或会员已过期") msg.setInformativeText( "获取会员激活码,请通过以下方式:\n\n" " • 官方自助网站:cursor.nosqli.com\n" " • 微信客服:behikcigar\n" " • 闲鱼店铺:xxx\n\n" "————————————————————\n" "诚招代理商,欢迎加盟合作!" ) msg.setIcon(QMessageBox.Information) msg.setStyleSheet(""" QMessageBox { background-color: #f8f9fa; min-width: 400px; padding: 20px; } QMessageBox QLabel { color: #333333; font-size: 14px; padding: 10px; margin: 10px; } QMessageBox QLabel:first-child { font-weight: bold; font-size: 16px; color: #0d6efd; qproperty-alignment: AlignCenter; } QMessageBox QLabel:last-child { font-family: 'Microsoft YaHei', sans-serif; font-size: 14px; color: #333333; padding: 15px; margin: 5px; line-height: 1.8; qproperty-alignment: AlignLeft; } QPushButton { background-color: #0d6efd; color: white; border: none; padding: 8px 25px; border-radius: 4px; font-size: 13px; min-width: 100px; margin: 10px; } QPushButton:hover { background-color: #0b5ed7; } """) msg.exec_() return self._activation_status def refresh_cursor_auth(self): """刷新Cursor授权""" if not self.check_activation_status(): return try: # 显示加载对话框 self.show_loading_dialog("正在刷新授权,请稍候...") # 创建工作线程 self.worker = ApiWorker(self.switcher.refresh_cursor_auth) self.worker.finished.connect(self.on_refresh_auth_complete) self.worker.start() except Exception as e: self.hide_loading_dialog() QMessageBox.critical(self, "错误", f"刷新授权失败: {str(e)}") def on_refresh_auth_complete(self, result): """刷新授权完成回调""" success, data = result self.hide_loading_dialog() if isinstance(data, tuple): success, message = data if success: QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "失败", message) else: QMessageBox.critical(self, "错误", f"刷新授权失败: {data}") def disable_cursor_update(self): """禁用Cursor更新""" if not self.check_activation_status(): return try: success, message = self.switcher.disable_cursor_update() if success: QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "失败", message) except Exception as e: QMessageBox.critical(self, "错误", f"禁用更新失败: {str(e)}") def dummy_function(self): """突破版本限制""" if not self.check_activation_status(): return QMessageBox.information(self, "提示", "此功能暂未实现")