import os import winreg import logging import shutil from pathlib import Path import uuid from datetime import datetime import json import hashlib import ctypes from typing import Optional class CursorRegistry: """Cursor注册表操作工具类""" def __init__(self): self.cursor_path = Path(os.path.expanduser("~")) / "AppData" / "Local" / "Programs" / "Cursor" self.app_path = self.cursor_path / "resources" / "app" self.backup_dir = Path(os.getenv('APPDATA')) / "Cursor" / "User" / "globalStorage" / "backups" def get_random_hex(self, length: int) -> str: """生成安全的随机十六进制字符串 Args: length: 需要生成的字节长度 Returns: str: 十六进制字符串 """ import secrets return secrets.token_hex(length) def new_standard_machine_id(self) -> str: """生成标准格式的机器ID Returns: str: 标准格式的机器ID """ template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" def replace_char(match): import random r = random.randint(0, 15) v = r if match == 'x' else (r & 0x3 | 0x8) return hex(v)[2:] return ''.join(replace_char(c) for c in template) def is_admin(self) -> bool: """检查是否具有管理员权限 Returns: bool: 是否具有管理员权限 """ try: return ctypes.windll.shell32.IsUserAnAdmin() != 0 except: return False def backup_file(self, source_path: Path, backup_name: Optional[str] = None) -> Optional[Path]: """备份文件 Args: source_path: 源文件路径 backup_name: 备份文件名(可选) Returns: Optional[Path]: 备份文件路径,失败返回None """ try: if not source_path.exists(): return None self.backup_dir.mkdir(parents=True, exist_ok=True) if backup_name is None: backup_name = f"{source_path.name}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" backup_path = self.backup_dir / backup_name shutil.copy2(source_path, backup_path) logging.info(f"已备份文件: {source_path} -> {backup_path}") return backup_path except Exception as e: logging.error(f"备份文件失败 {source_path}: {str(e)}") return None def update_machine_guid(self) -> bool: """更新系统的 MachineGuid Returns: bool: 是否成功 """ if not self.is_admin(): logging.error("需要管理员权限来修改 MachineGuid") return False try: new_guid = str(uuid.uuid4()) registry_path = r"SOFTWARE\Microsoft\Cryptography" try: # 备份原始值 with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as key: original_guid = winreg.QueryValueEx(key, "MachineGuid")[0] # 备份原始 GUID backup_path = self.backup_dir / f"MachineGuid.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" self.backup_dir.mkdir(parents=True, exist_ok=True) with open(backup_path, 'w', encoding='utf-8') as f: f.write(original_guid) logging.info(f"已备份 MachineGuid: {backup_path}") # 更新 GUID with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path, 0, winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY) as key: winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid) logging.info(f"已更新系统 MachineGuid: {new_guid}") return True except WindowsError as e: logging.error(f"注册表操作失败: {str(e)}") return False except Exception as e: logging.error(f"更新 MachineGuid 失败: {str(e)}") return False def clean_registry(self) -> bool: """清理Cursor相关的注册表项 Returns: bool: 是否成功 """ try: # 需要清理的注册表路径列表 registry_paths = [ r"Software\Classes\Directory\Background\shell\Cursor\command", r"Software\Classes\Directory\Background\shell\Cursor", r"Software\Cursor\Auth", r"Software\Cursor\Updates", r"Software\Cursor" ] for path in registry_paths: try: winreg.DeleteKey(winreg.HKEY_CURRENT_USER, path) logging.info(f"删除注册表项成功: {path}") except WindowsError as e: if e.winerror == 2: # 找不到注册表项 logging.info(f"注册表项不存在,无需清理: {path}") else: logging.error(f"清理注册表失败: {path}, 错误: {str(e)}") # 更新系统 MachineGuid self.update_machine_guid() return True except Exception as e: logging.error(f"清理注册表过程出错: {str(e)}") return False def clean_cursor_files(self) -> bool: """清理Cursor相关的文件和目录,但保留重要的配置和历史记录 Returns: bool: 是否成功 """ try: storage_path = Path(os.getenv('APPDATA')) / "Cursor" / "User" / "globalStorage" / "storage.json" global_storage_dir = storage_path.parent # 备份 storage.json if storage_path.exists(): if not self.backup_file(storage_path): return False # 备份其他重要文件 if global_storage_dir.exists(): for item in global_storage_dir.iterdir(): if item.name not in ["storage.json", "backups"]: try: backup_item_dir = self.backup_dir / f"other_files_{datetime.now().strftime('%Y%m%d_%H%M%S')}" backup_item_dir.mkdir(exist_ok=True) if item.is_file(): shutil.copy2(item, backup_item_dir / item.name) elif item.is_dir(): shutil.copytree(item, backup_item_dir / item.name) logging.info(f"已备份: {item}") except Exception as e: logging.error(f"备份失败 {item}: {str(e)}") # 更新 storage.json if storage_path.exists(): try: with open(storage_path, "r", encoding="utf-8") as f: storage_data = json.load(f) if "telemetry.machineId" in storage_data: new_machine_id = self.get_random_hex(32) storage_data["telemetry.machineId"] = new_machine_id logging.info(f"已更新 machineId: {new_machine_id}") # 使用 UTF-8 无 BOM 编码保存 with open(storage_path, "w", encoding="utf-8", newline='\n') as f: json.dump(storage_data, f, indent=2) except Exception as e: logging.error(f"更新 storage.json 失败: {str(e)}") return False # 处理更新程序 updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater" if updater_path.exists(): try: if updater_path.is_dir(): shutil.rmtree(updater_path) else: updater_path.unlink() except Exception as e: logging.error(f"删除更新程序失败: {str(e)}") return False return True except Exception as e: logging.error(f"清理文件失败: {str(e)}") return False def disable_auto_update(self) -> bool: """禁用自动更新功能 Returns: bool: 是否成功 """ try: updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater" # 删除现有目录/文件 if updater_path.exists(): if updater_path.is_dir(): shutil.rmtree(updater_path) else: updater_path.unlink() # 创建空文件 updater_path.touch() # 设置只读属性 import stat updater_path.chmod(stat.S_IREAD) # 设置文件权限(仅Windows) if os.name == 'nt': import subprocess subprocess.run( f'icacls "{updater_path}" /inheritance:r /grant:r "{os.getenv("USERNAME")}:(R)"', shell=True, check=True ) logging.info("已禁用自动更新") return True except Exception as e: logging.error(f"禁用自动更新失败: {str(e)}") return False def fix_cursor_startup(self) -> bool: """修复 Cursor 启动警告""" try: # 1. 修改 package.json 中的更新相关配置 if self.app_path.exists(): package_json = self.app_path / "package.json" if package_json.exists(): with open(package_json, "r", encoding="utf-8") as f: data = json.load(f) # 只修改更新相关配置,与 GitHub 脚本保持一致 data["updateUrl"] = "" # 清空更新 URL data["disableUpdate"] = True # 禁用更新 with open(package_json, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) logging.info("已修复 Cursor 启动配置") return True except Exception as e: logging.error(f"修复 Cursor 启动配置失败: {str(e)}") return False