diff --git a/cursor_account_manager.py b/cursor_account_manager.py index 8b58155..cee18fb 100644 --- a/cursor_account_manager.py +++ b/cursor_account_manager.py @@ -13,6 +13,13 @@ import go_cursor_help import patch_cursor_get_machine_id from reset_machine import MachineIDResetter from logo import print_logo +from typing import Optional, Tuple, Dict, Any +from urllib3.exceptions import InsecureRequestWarning +import urllib3 +import time + +# 禁用不安全请求警告 +requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # 定义 EMOJI 字典 EMOJI = {"ERROR": "❌", "WARNING": "⚠️", "INFO": "ℹ️"} @@ -152,6 +159,268 @@ def get_mac_unique_id() -> str: return hashlib.md5(unique_string.encode()).hexdigest() +class CursorAccountManager: + def __init__(self): + self.base_url = "https://cursorapi.nosqli.com" + self.api_endpoints = { + "activate": f"{self.base_url}/admin/api.member/activate", + "status": f"{self.base_url}/admin/api.member/status", + "get_unused": f"{self.base_url}/admin/api.account/getUnused", + "heartbeat": f"{self.base_url}/admin/api.member/heartbeat" + } + self.hardware_id = get_mac_unique_id() + + def get_device_info(self) -> dict: + """获取设备信息""" + return { + "system": platform.system(), + "device_name": platform.node(), + "ip": self._get_ip_address(), + "location": self._get_location() + } + + def _get_ip_address(self) -> str: + """获取IP地址""" + try: + response = requests.get('https://api.ipify.org?format=json', timeout=5) + return response.json()['ip'] + except: + return "未知" + + def _get_location(self) -> str: + """获取地理位置""" + try: + ip = self._get_ip_address() + if ip != "未知": + response = requests.get(f'http://ip-api.com/json/{ip}', timeout=5) + data = response.json() + if data.get('status') == 'success': + return f"{data.get('country', '')} {data.get('city', '')}" + except: + pass + return "未知" + + def check_member_status(self) -> tuple[bool, dict]: + """检查会员状态 + + Returns: + tuple[bool, dict]: (是否成功, 状态信息) + """ + try: + data = { + "machine_id": self.hardware_id + } + + api_url = self.api_endpoints["status"] + logging.info(f"正在检查会员状态...") + + request_kwargs = { + "json": data, + "headers": {"Content-Type": "application/json"}, + "timeout": 2, + "verify": False + } + + 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]: + api_data = result.get("data", {}) + status_data = { + "is_active": api_data.get("status") == "active", + "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() + } + return True, status_data + else: + error_msg = result.get("msg", "未知错误") + logging.error(f"获取状态失败: {error_msg}") + return False, { + "is_active": False, + "expire_time": "", + "total_days": 0, + "days_left": 0, + "device_info": self.get_device_info() + } + + except Exception as e: + logging.error(f"获取会员状态失败: {str(e)}") + return False, { + "is_active": False, + "expire_time": "", + "total_days": 0, + "days_left": 0, + "device_info": self.get_device_info() + } + + def check_activation_code(self, code: str) -> tuple[bool, str, dict | None]: + """检查激活码 + + Args: + code: 激活码 + + Returns: + tuple: (成功标志, 消息, 账号信息) + """ + max_retries = 3 # 最大重试次数 + retry_delay = 1 # 重试间隔(秒) + + for attempt in range(max_retries): + try: + data = { + "machine_id": self.hardware_id, + "code": code + } + + # 设置请求参数 + request_kwargs = { + "json": data, + "headers": { + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", + "Accept": "*/*", + "Connection": "keep-alive" + }, + "timeout": 10, + "verify": False + } + + # 创建session + session = requests.Session() + session.verify = False + + # 设置重试策略 + retry_strategy = urllib3.Retry( + total=3, + backoff_factor=0.5, + status_forcelist=[500, 502, 503, 504] + ) + adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy) + session.mount("http://", adapter) + session.mount("https://", adapter) + + try: + # 尝试发送请求 + response = session.post( + self.api_endpoints["activate"], + **request_kwargs + ) + response.raise_for_status() + + result = response.json() + logging.info(f"激活响应: {result}") + + # 激活成功 + if result["code"] == 200: + api_data = result["data"] + account_info = { + "status": "active", + "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() + } + return True, result["msg"], account_info + # 激活码无效或已被使用 + elif result["code"] == 400: + logging.warning(f"激活码无效或已被使用: {result.get('msg', '未知错误')}") + return False, result.get("msg", "激活码无效或已被使用"), None + # 其他错误情况 + else: + error_msg = result.get("msg", "未知错误") + if attempt < max_retries - 1: + logging.warning(f"第{attempt + 1}次尝试失败: {error_msg}, 准备重试...") + time.sleep(retry_delay) + continue + logging.error(f"激活失败: {error_msg}") + return False, error_msg, None + + except requests.exceptions.RequestException as e: + if attempt < max_retries - 1: + logging.warning(f"第{attempt + 1}次网络请求失败: {str(e)}, 准备重试...") + time.sleep(retry_delay) + continue + logging.error(f"网络请求失败: {str(e)}") + return False, f"网络连接失败: {str(e)}", None + + except Exception as e: + if attempt < max_retries - 1: + logging.warning(f"第{attempt + 1}次请求发生错误: {str(e)}, 准备重试...") + time.sleep(retry_delay) + continue + logging.error(f"激活失败: {str(e)}") + return False, f"激活失败: {str(e)}", None + + return False, "多次尝试后激活失败,请检查网络连接或稍后重试", None + + +def reset_auth_with_password(password: str = None) -> tuple[bool, str]: + """ + 封装重置授权的完整流程 + + Args: + password: 系统密码(可选) + + Returns: + tuple[bool, str]: (是否成功, 消息) + """ + try: + logging.info("\n=== 初始化重置流程 ===") + greater_than_0_45 = check_cursor_version() + + logging.info("正在从API获取账号信息...") + success, account_data = get_account_from_api() + + if not success: + return False, "获取账号信息失败" + + email = account_data.get("email", "") + access_token = account_data.get("access_token", "") + refresh_token = account_data.get("refresh_token", "") + + if not all([email, access_token, refresh_token]): + return False, "账号信息不完整" + + logging.info(f"获取到账号信息:\n邮箱: {email}") + + # 更新认证信息 + logging.info("正在更新认证信息...") + auth_manager = CursorAuthManager() + if auth_manager.update_auth(email, access_token, refresh_token): + logging.info("认证信息更新成功") + + # 重置机器码 + logging.info("正在重置机器码...") + + # 如果提供了密码,设置环境变量 + if password: + import os + os.environ['SUDO_PASSWORD'] = password + + reset_machine_id(greater_than_0_45) + logging.info("重置完成") + return True, "重置成功" + else: + return False, "更新认证信息失败" + + except Exception as e: + logging.error(f"重置过程出错: {str(e)}") + import traceback + logging.error(traceback.format_exc()) + return False, f"重置失败: {str(e)}" + + if __name__ == "__main__": print_logo() greater_than_0_45 = check_cursor_version() diff --git a/cursor_gui.py b/cursor_gui.py new file mode 100644 index 0000000..909833f --- /dev/null +++ b/cursor_gui.py @@ -0,0 +1,494 @@ +import sys +import os +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, + QLabel, QLineEdit, QPushButton, QTextEdit, QMessageBox, + QHBoxLayout, QDialog) +from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation +from PyQt6.QtGui import QIcon, QPixmap +import cursor_account_manager as backend +from logger import logging + +class SuccessDialog(QDialog): + def __init__(self, message, parent=None): + super().__init__(parent) + self.setWindowTitle("激活成功") + self.setFixedSize(400, 300) # 增加窗口大小 + self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint | Qt.WindowType.FramelessWindowHint) + + # 设置样式 + self.setStyleSheet(""" + QDialog { + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 10px; + } + QLabel { + color: #333333; + font-size: 14px; + padding: 10px; + } + QLabel#titleLabel { + font-size: 24px; + font-weight: bold; + color: #0078d4; + } + QLabel#messageLabel { + font-size: 16px; + padding: 20px; + background-color: #f8f8f8; + border-radius: 8px; + } + QPushButton { + background-color: #0078d4; + color: white; + border: none; + padding: 12px 24px; + border-radius: 6px; + font-size: 16px; + min-width: 120px; + } + QPushButton:hover { + background-color: #006cbd; + } + QPushButton:pressed { + background-color: #005ba1; + } + """) + + layout = QVBoxLayout() + layout.setSpacing(20) + layout.setContentsMargins(30, 30, 30, 30) + + # 标题 + title_label = QLabel("🎉 激活成功") + title_label.setObjectName("titleLabel") + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(title_label) + + # 消息内容 + message_label = QLabel(message) + message_label.setObjectName("messageLabel") + message_label.setAlignment(Qt.AlignmentFlag.AlignLeft) + message_label.setWordWrap(True) + layout.addWidget(message_label) + + # 确定按钮 + ok_button = QPushButton("确定") + ok_button.clicked.connect(self.accept) + ok_button.setCursor(Qt.CursorShape.PointingHandCursor) + + # 按钮容器 + button_container = QWidget() + button_layout = QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(ok_button) + button_layout.addStretch() + button_container.setLayout(button_layout) + layout.addWidget(button_container) + + self.setLayout(layout) + + # 5秒后自动关闭 + QTimer.singleShot(5000, self.accept) + + # 设置窗口位置为父窗口中心 + if parent: + self.move( + parent.x() + (parent.width() - self.width()) // 2, + parent.y() + (parent.height() - self.height()) // 2 + ) + + def showEvent(self, event): + """窗口显示时的动画效果""" + self.setWindowOpacity(0.0) + self.animation = QPropertyAnimation(self, b"windowOpacity") + self.animation.setDuration(300) # 300ms + self.animation.setStartValue(0.0) + self.animation.setEndValue(1.0) + self.animation.start() + super().showEvent(event) + + def closeEvent(self, event): + """窗口关闭时的动画效果""" + self.animation = QPropertyAnimation(self, b"windowOpacity") + self.animation.setDuration(200) # 200ms + self.animation.setStartValue(1.0) + self.animation.setEndValue(0.0) + self.animation.finished.connect(self.close) + self.animation.start() + event.ignore() + +class PasswordDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("输入系统密码") + self.setFixedSize(400, 150) + + layout = QVBoxLayout() + layout.setSpacing(15) + layout.setContentsMargins(20, 20, 20, 20) + + # 密码输入框 + self.password_input = QLineEdit() + self.password_input.setEchoMode(QLineEdit.EchoMode.Password) + self.password_input.setPlaceholderText("请输入系统密码") + layout.addWidget(self.password_input) + + # 按钮区域 + button_layout = QHBoxLayout() + ok_button = QPushButton("确定") + cancel_button = QPushButton("取消") + + ok_button.clicked.connect(self.accept) + cancel_button.clicked.connect(self.reject) + + button_layout.addWidget(ok_button) + button_layout.addWidget(cancel_button) + layout.addLayout(button_layout) + + self.setLayout(layout) + + def get_password(self): + return self.password_input.text() + +class CursorGUI(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Cursor账号管理器 v3.5.3") + self.setFixedSize(600, 600) + + # 设置整体样式 + self.setStyleSheet(""" + QMainWindow { + background-color: #f5f5f5; + } + QWidget { + background-color: #f5f5f5; + color: #333333; + } + QLabel { + color: #333333; + font-size: 14px; + font-weight: bold; + } + QLineEdit { + padding: 8px; + border: 1px solid #cccccc; + border-radius: 4px; + background-color: white; + selection-background-color: #0078d4; + } + QTextEdit { + padding: 8px; + border: 1px solid #cccccc; + border-radius: 4px; + background-color: white; + selection-background-color: #0078d4; + } + QScrollBar:vertical { + border: none; + background: #f0f0f0; + width: 10px; + margin: 0px; + } + QScrollBar::handle:vertical { + background: #c0c0c0; + min-height: 30px; + border-radius: 5px; + } + QScrollBar::handle:vertical:hover { + background: #a0a0a0; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QPushButton { + background-color: #0078d4; + color: white; + padding: 8px; + border: none; + border-radius: 4px; + font-size: 14px; + } + QPushButton:hover { + background-color: #006cbd; + } + QPushButton:pressed { + background-color: #005ba1; + } + """) + + # 创建主窗口部件 + main_widget = QWidget() + self.setCentralWidget(main_widget) + layout = QVBoxLayout() + layout.setSpacing(15) # 增加垂直间距 + layout.setContentsMargins(20, 20, 20, 20) # 设置边距 + main_widget.setLayout(layout) + + # 设备ID显示区域 + id_label = QLabel("设备识别码(勿动):") + id_layout = QHBoxLayout() + id_layout.setSpacing(10) # 设置水平间距 + self.id_text = QLineEdit() + self.id_text.setReadOnly(True) + id_copy_btn = QPushButton("复制ID") + id_copy_btn.setFixedWidth(100) + id_copy_btn.clicked.connect(self.copy_device_id) + id_layout.addWidget(self.id_text) + id_layout.addWidget(id_copy_btn) + layout.addWidget(id_label) + layout.addLayout(id_layout) + + # 会员状态显示区域 + status_label = QLabel("会员状态") + self.status_text = QTextEdit() + self.status_text.setReadOnly(True) + self.status_text.setFixedHeight(150) + layout.addWidget(status_label) + layout.addWidget(self.status_text) + + # 激活区域 + activate_label = QLabel("激活(盈加)会员,多个激活的可盈加整体时长") + activate_layout = QHBoxLayout() + activate_layout.setSpacing(10) # 设置水平间距 + self.activate_input = QLineEdit() + self.activate_input.setPlaceholderText("请输入激活码") + self.activate_btn = QPushButton("激活") + self.activate_btn.setFixedWidth(100) + self.activate_btn.clicked.connect(self.activate_account) + activate_layout.addWidget(self.activate_input) + activate_layout.addWidget(self.activate_btn) + layout.addWidget(activate_label) + layout.addLayout(activate_layout) + + # 添加一些间距 + layout.addSpacing(20) + + # 功能按钮区域 + button_style = """ + QPushButton { + background-color: #0078d4; + color: white; + padding: 12px; + border: none; + border-radius: 6px; + font-size: 15px; + font-weight: bold; + margin: 5px 0; + } + QPushButton:hover { + background-color: #006cbd; + } + QPushButton:pressed { + background-color: #005ba1; + } + QPushButton:disabled { + background-color: #cccccc; + color: #666666; + } + """ + + # 一键重置按钮 + self.refresh_btn = QPushButton("一键重置") + self.refresh_btn.setStyleSheet(button_style) + self.refresh_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.refresh_btn.clicked.connect(self.refresh_auth) + layout.addWidget(self.refresh_btn) + + # 代用2按钮 + self.patch_btn = QPushButton("代用 2") + self.patch_btn.setStyleSheet(button_style) + self.patch_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.patch_btn.clicked.connect(self.install_patch) + layout.addWidget(self.patch_btn) + + # 代用3按钮 + self.update_btn = QPushButton("代用 3") + self.update_btn.setStyleSheet(button_style) + self.update_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.update_btn.clicked.connect(self.update_cursor) + layout.addWidget(self.update_btn) + + # 初始化设备ID和状态 + self.update_device_id() + self.update_status() + + def update_device_id(self): + """更新设备ID显示""" + try: + device_id = backend.get_mac_unique_id() + self.id_text.setText(device_id) + except Exception as e: + QMessageBox.warning(self, "错误", f"获取设备ID失败: {str(e)}") + + def update_status(self): + """更新会员状态显示""" + try: + account_manager = backend.CursorAccountManager() + success, status_data = account_manager.check_member_status() + + # 获取设备信息 + device_info = status_data.get("device_info", {}) if success else {} + + # 设置状态文本 + if success and status_data.get("is_active"): + status_emoji = "✅" + status_text = "正常" + else: + status_emoji = "❌" + status_text = "未激活" + + # 格式化显示文本 + display_text = f"会员状态:{status_text} {status_emoji}\n" + display_text += f"到期时间:{status_data.get('expire_time', '--') if success else '--'}\n" + display_text += f"总天数:{status_data.get('total_days', 0) if success else 0}天\n" + display_text += f"剩余天数:{status_data.get('days_left', 0) if success else 0}天\n\n" + + # 设备信息部分 + display_text += "设备信息:\n" + display_text += f"系统:{device_info.get('system', sys.platform)}\n" + display_text += f"设备名:{device_info.get('device_name', '未知')}\n" + display_text += f"IP地址:{device_info.get('ip', '--')}\n" + display_text += f"地理位置:{device_info.get('location', '--')}" + + self.status_text.setText(display_text) + + except Exception as e: + error_text = "会员状态:未激活 ❌\n" + error_text += "到期时间:--\n" + error_text += "总天数:0天\n" + error_text += "剩余天数:0天\n\n" + error_text += "设备信息:\n" + error_text += f"系统:{sys.platform}\n" + error_text += f"设备名:{backend.platform.node()}\n" + error_text += "IP地址:--\n" + error_text += "地理位置:--" + self.status_text.setText(error_text) + + def copy_device_id(self): + """复制设备ID到剪贴板""" + clipboard = QApplication.clipboard() + clipboard.setText(self.id_text.text()) + QMessageBox.information(self, "提示", "设备ID已复制到剪贴板") + + def activate_account(self): + """激活账号""" + code = self.activate_input.text().strip() + if not code: + QMessageBox.warning(self, "警告", "请输入激活码") + return + + # 禁用激活按钮,显示加载状态 + self.activate_btn.setEnabled(False) + self.activate_btn.setText("激活中...") + QApplication.processEvents() + + try: + account_manager = backend.CursorAccountManager() + success, message, account_info = account_manager.check_activation_code(code) + + if success: + # 更新状态显示 + self.update_status() + + # 构建成功消息 + success_message = ( + f"🎉 激活成功!\n\n" + f"激活码:{code}\n" + f"到期时间:{account_info.get('expire_time', '--')}\n" + f"总天数:{account_info.get('total_days', 0)}天\n" + f"剩余天数:{account_info.get('days_left', 0)}天" + ) + + # 显示成功弹窗 + dialog = SuccessDialog(success_message, self) + dialog.exec() + + # 清空输入框 + self.activate_input.clear() + else: + QMessageBox.warning(self, "错误", message) + except Exception as e: + QMessageBox.warning(self, "错误", f"激活过程出错: {str(e)}") + finally: + # 恢复激活按钮状态 + self.activate_btn.setEnabled(True) + self.activate_btn.setText("激活") + + def refresh_auth(self): + """刷新授权""" + # 显示密码输入对话框 + dialog = PasswordDialog(self) + if dialog.exec() == QDialog.DialogCode.Accepted: + password = dialog.get_password() + + # 显示加载状态 + self.refresh_btn.setEnabled(False) + self.refresh_btn.setText("重置中...") + QApplication.processEvents() + + try: + success, message = backend.reset_auth_with_password(password) + if success: + QMessageBox.information(self, "成功", "重置成功") + self.update_status() + else: + QMessageBox.warning(self, "错误", message) + except Exception as e: + QMessageBox.warning(self, "错误", f"重置失败: {str(e)}") + finally: + # 恢复按钮状态 + self.refresh_btn.setEnabled(True) + self.refresh_btn.setText("一键重置") + else: + QMessageBox.warning(self, "取消", "重置操作已取消") + + def install_patch(self): + """安装补丁""" + try: + # 检查版本 + greater_than_0_45 = backend.check_cursor_version() + + # 显示密码输入对话框 + dialog = PasswordDialog(self) + if dialog.exec() == QDialog.DialogCode.Accepted: + password = dialog.get_password() + + # 显示加载状态 + self.patch_btn.setEnabled(False) + self.patch_btn.setText("安装中...") + QApplication.processEvents() + + try: + # 设置环境变量 + os.environ['SUDO_PASSWORD'] = password + + # 执行重置 + backend.reset_machine_id(greater_than_0_45) + QMessageBox.information(self, "成功", "补丁安装成功") + self.update_status() + except Exception as e: + QMessageBox.warning(self, "错误", f"安装失败: {str(e)}") + finally: + # 恢复按钮状态 + self.patch_btn.setEnabled(True) + self.patch_btn.setText("代用 2") + else: + QMessageBox.warning(self, "取消", "安装操作已取消") + except Exception as e: + QMessageBox.warning(self, "错误", f"安装补丁失败: {str(e)}") + + def update_cursor(self): + """更新Cursor版本""" + # TODO: 实现版本更新逻辑 + QMessageBox.information(self, "提示", "更新功能开发中") + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = CursorGUI() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/go_cursor_help.py b/go_cursor_help.py index 47ed69b..8348155 100644 --- a/go_cursor_help.py +++ b/go_cursor_help.py @@ -10,9 +10,19 @@ def go_cursor_help(): base_url = "https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run" if system == "Darwin": # macOS - cmd = f'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | sudo bash' + # 从环境变量获取密码 + sudo_password = os.environ.get('SUDO_PASSWORD') + if sudo_password: + # 使用echo传递密码给sudo + cmd = f'echo "{sudo_password}" | sudo -S bash -c \'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | bash\'' + else: + cmd = f'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | sudo bash' + logging.info("执行macOS命令") - os.system(cmd) + result = os.system(cmd) + + if result != 0: + raise Exception("执行命令失败,请检查密码是否正确") elif system == "Linux": cmd = f'curl -fsSL {base_url}/cursor_linux_id_modifier.sh | sudo bash' logging.info("执行Linux命令") diff --git a/icons/app.svg b/icons/app.svg new file mode 100644 index 0000000..9780654 --- /dev/null +++ b/icons/app.svg @@ -0,0 +1,5 @@ + + + + C + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b1577d7..863daa6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ DrissionPage==4.1.0.9 colorama==0.4.6 python-dotenv==1.0.0 -pyinstaller \ No newline at end of file +pyinstaller +PyQt6==6.6.1 +PyQt6-Qt6==6.6.1 +PyQt6-sip==13.6.0 \ No newline at end of file