import os import sys import json import logging import subprocess import uuid from pathlib import Path from datetime import datetime from typing import Optional, Tuple, Dict class CursorResetter: """Cursor重置工具类,封装PowerShell脚本的核心功能""" def __init__(self): self.appdata = os.getenv('APPDATA') self.localappdata = os.getenv('LOCALAPPDATA') self.storage_file = Path(self.appdata) / "Cursor" / "User" / "globalStorage" / "storage.json" self.backup_dir = Path(self.appdata) / "Cursor" / "User" / "globalStorage" / "backups" self.cursor_path = Path(self.localappdata) / "Programs" / "cursor" self.app_path = self.cursor_path / "resources" / "app" self.package_json = self.app_path / "package.json" def get_random_hex(self, length: int) -> str: """生成安全的随机十六进制字符串""" import secrets return secrets.token_hex(length) def new_standard_machine_id(self) -> str: """生成标准格式的机器ID""" template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" import random def replace_char(match): 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 generate_ids(self) -> Dict[str, str]: """生成所有需要的ID""" # 生成标准格式的ID mac_machine_id = self.new_standard_machine_id() uuid_str = str(uuid.uuid4()) # 生成带前缀的machineId prefix = "auth0|user_" prefix_hex = ''.join(hex(b)[2:].zfill(2) for b in prefix.encode()) random_part = self.get_random_hex(32) machine_id = f"{prefix_hex}{random_part}" # 生成大写的SQM ID sqm_id = "{" + str(uuid.uuid4()).upper() + "}" return { "mac_machine_id": mac_machine_id, "uuid": uuid_str, "machine_id": machine_id, "sqm_id": sqm_id } def backup_file(self, file_path: Path) -> Optional[Path]: """备份文件""" try: if not file_path.exists(): return None self.backup_dir.mkdir(parents=True, exist_ok=True) backup_name = f"{file_path.name}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" backup_path = self.backup_dir / backup_name import shutil shutil.copy2(file_path, backup_path) logging.info(f"已备份文件: {backup_path}") return backup_path except Exception as e: logging.error(f"备份文件失败: {str(e)}") return None def update_machine_guid(self) -> bool: """更新系统MachineGuid""" try: import winreg new_guid = str(uuid.uuid4()) registry_path = r"SOFTWARE\Microsoft\Cryptography" # 备份原始值 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] # 备份到文件 self.backup_dir.mkdir(parents=True, exist_ok=True) backup_path = self.backup_dir / f"MachineGuid.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" with open(backup_path, 'w', encoding='utf-8') as f: f.write(original_guid) # 更新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 Exception as e: logging.error(f"更新MachineGuid失败: {str(e)}") return False def update_storage_json(self) -> bool: """更新storage.json文件""" try: if not self.storage_file.exists(): logging.error(f"未找到配置文件: {self.storage_file}") return False # 备份文件 if not self.backup_file(self.storage_file): logging.warning("配置文件备份失败") # 生成新ID ids = self.generate_ids() # 读取并更新配置 with open(self.storage_file, "r", encoding="utf-8") as f: config = json.load(f) # 更新ID config['telemetry.machineId'] = ids['machine_id'] config['telemetry.macMachineId'] = ids['mac_machine_id'] config['telemetry.devDeviceId'] = ids['uuid'] config['telemetry.sqmId'] = ids['sqm_id'] # 保存更新 with open(self.storage_file, "w", encoding="utf-8", newline='\n') as f: json.dump(config, f, indent=2) logging.info("已更新配置文件") return True except Exception as e: logging.error(f"更新配置文件失败: {str(e)}") return False def disable_auto_update(self) -> Tuple[bool, str]: """禁用自动更新 Returns: Tuple[bool, str]: (是否成功, 消息) """ try: updater_path = Path(self.localappdata) / "cursor-updater" # 1. 先尝试删除现有文件/目录 try: if updater_path.exists(): if updater_path.is_dir(): import shutil shutil.rmtree(str(updater_path)) logging.info("已删除updater目录") else: updater_path.unlink() logging.info("已删除updater文件") except Exception as e: logging.warning(f"删除现有updater文件/目录失败: {str(e)}") # 2. 创建空文件 try: # 如果文件已存在,先移除它 if updater_path.exists(): try: os.remove(str(updater_path)) except: pass # 创建空文件 with open(updater_path, 'w') as f: pass logging.info("已创建updater空文件") except Exception as e: logging.error(f"创建updater文件失败: {str(e)}") return False, f"创建updater文件失败: {str(e)}" # 3. 设置文件权限 try: import stat # 设置文件为只读 os.chmod(str(updater_path), stat.S_IREAD) logging.info("已设置只读属性") if os.name == 'nt': # 使用takeown获取文件所有权 subprocess.run(['takeown', '/f', str(updater_path)], check=True, capture_output=True) logging.info("已获取文件所有权") # 使用icacls设置权限 username = os.getenv("USERNAME") subprocess.run( ['icacls', str(updater_path), '/inheritance:r', '/grant:r', f'{username}:(R)'], check=True, capture_output=True ) logging.info("已设置文件权限") except Exception as e: logging.error(f"设置文件权限失败: {str(e)}") return False, f"设置文件权限失败: {str(e)}" # 4. 修改package.json try: if self.package_json.exists(): with open(self.package_json, "r", encoding="utf-8") as f: data = json.load(f) data["updateUrl"] = "" data["disableUpdate"] = True with open(self.package_json, "w", encoding="utf-8", newline='\n') as f: json.dump(data, f, indent=2) logging.info("已修改package.json配置") except Exception as e: logging.warning(f"修改package.json失败: {str(e)}") # 5. 验证文件权限 try: if not updater_path.exists(): return False, "updater文件不存在" if os.access(str(updater_path), os.W_OK): return False, "文件权限设置失败" except Exception as e: logging.error(f"验证文件权限失败: {str(e)}") return False, f"验证文件权限失败: {str(e)}" logging.info("已禁用自动更新") return True, "Cursor更新已禁用" except Exception as e: error_msg = f"禁用自动更新失败: {str(e)}" logging.error(error_msg) return False, error_msg def reset_cursor(self, disable_update: bool = True) -> Tuple[bool, str]: """重置Cursor Args: disable_update: 是否禁用自动更新 Returns: Tuple[bool, str]: (是否成功, 消息) """ try: # 1. 检查管理员权限 if os.name == 'nt': import ctypes if not ctypes.windll.shell32.IsUserAnAdmin(): return False, "需要管理员权限来执行重置操作" # 2. 更新配置文件 if not self.update_storage_json(): return False, "更新配置文件失败" # 3. 更新系统MachineGuid if not self.update_machine_guid(): return False, "更新系统MachineGuid失败" # 4. 禁用自动更新(如果需要) # 5. 修改package.json if self.package_json.exists(): try: with open(self.package_json, "r", encoding="utf-8") as f: data = json.load(f) if "machineId" in data: del data["machineId"] data["updateUrl"] = "" data["disableUpdate"] = True with open(self.package_json, "w", encoding="utf-8", newline='\n') as f: json.dump(data, f, indent=2) except Exception as e: logging.warning(f"修改package.json失败: {str(e)}") return True, "Cursor重置成功" except Exception as e: logging.error(f"重置过程出错: {str(e)}") return False, f"重置失败: {str(e)}"