Files
nezhacursor/utils/cursor_registry.py
huangzhenpc 8b2fbef54a feat(v3.4.7): 重构重置功能和优化重启逻辑
1. 新增 CursorResetter 类,完整封装 cursor_win_id_modifier.ps1 的核心功能

2. 优化 AccountSwitcher 的重启逻辑,避免重复重启

3. 改进进程管理,移除 wmi 依赖,使用 tasklist 替代

4. 提升代码可维护性,后续只需更新 CursorResetter 即可适配脚本变更
2025-02-14 15:06:05 +08:00

296 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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