1. 新增 CursorResetter 类,完整封装 cursor_win_id_modifier.ps1 的核心功能 2. 优化 AccountSwitcher 的重启逻辑,避免重复重启 3. 改进进程管理,移除 wmi 依赖,使用 tasklist 替代 4. 提升代码可维护性,后续只需更新 CursorResetter 即可适配脚本变更
296 lines
11 KiB
Python
296 lines
11 KiB
Python
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 |