feat: 添加图形界面和自动化重置功能 - 新增 PyQt6 图形界面,优化密码输入和 sudo 权限处理,改进重置机器码流程,添加应用图标和打包配置,更新依赖项

This commit is contained in:
ruisu
2025-02-19 16:14:01 +08:00
parent 0e4087dd28
commit 6a00193333
5 changed files with 784 additions and 3 deletions

View File

@@ -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()

494
cursor_gui.py Normal file
View File

@@ -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())

View File

@@ -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命令")

5
icons/app.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<rect width="1024" height="1024" rx="200" fill="#0078d4"/>
<text x="512" y="640" font-family="Arial" font-size="600" text-anchor="middle" fill="white">C</text>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -1,4 +1,7 @@
DrissionPage==4.1.0.9
colorama==0.4.6
python-dotenv==1.0.0
pyinstaller
pyinstaller
PyQt6==6.6.1
PyQt6-Qt6==6.6.1
PyQt6-sip==13.6.0