diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index bd6fd74..78dcad3 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -1,183 +1,789 @@ - input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}")import os -import shutil -import platform +import os import sys -import uuid import json -from datetime import datetime +import uuid +import hashlib +import shutil +import sqlite3 +import platform +import re +import tempfile from colorama import Fore, Style, init +from typing import Tuple +import configparser +from new_signup import get_user_documents_path +import traceback +from config import get_config # Initialize colorama init() -# Define simplified emoji constants +# Define emoji constants EMOJI = { + "FILE": "📄", + "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "🔄", - "WARNING": "⚠️" + "RESET": "��", + "WARNING": "⚠️", } -def get_confirmation(): - """Gets confirmation from the user to proceed.""" - choice = input(f"{Fore.RED}{EMOJI['WARNING']} Confirm Cursor AI reset (y/n): ").strip().lower() - return choice == "y" or choice == "" - -def remove_path(path): - """Removes a directory or file if it exists.""" - if not path or not os.path.exists(path): - return - - try: - if os.path.isfile(path): - os.remove(path) - else: - shutil.rmtree(path, ignore_errors=True) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Deleted: {path} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error deleting {path}: {str(e)} {Style.RESET_ALL}") - -def create_fake_machine_id(path): - """Creates a new machine ID file with random ID.""" - try: - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - with open(path, 'w') as f: - f.write(str(uuid.uuid4())) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created new machine ID: {path} {Style.RESET_ALL}") - except Exception: - pass - -def create_fake_trial_info(path): - """Creates fake trial information to extend trial period.""" - try: - # Generate future expiry date (90 days from now) - future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds - - # Create fake trial info - fake_trial = { - "trialStartTimestamp": datetime.now().timestamp() * 1000, - "trialEndTimestamp": future_date, - "hasUsedTrial": False, - "machineId": str(uuid.uuid4()) - } - - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - - with open(path, 'w') as f: - json.dump(fake_trial, f) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created extended trial: {path} {Style.RESET_ALL}") - except Exception: - pass - -def reset_cursor(): - """Reset Cursor AI by removing settings, caches, and creating new identifiers.""" +def get_cursor_paths(translator=None) -> Tuple[str, str]: + """ Get Cursor related paths""" system = platform.system() - home = os.path.expanduser("~") - - print(f"\n{Fore.CYAN}====== Cursor AI Reset Tool ======{Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['WARNING']} This will reset Cursor AI completely:") - print(f"• All settings, cache, and identifiers will be removed") - print(f"• Data may be unrecoverable") - print(f"• You may need to reinstall Cursor after reset{Style.RESET_ALL}\n") - if not get_confirmation(): - print(f"\n{Fore.CYAN}Reset cancelled.{Style.RESET_ALL}") - return - - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Resetting Cursor AI...{Style.RESET_ALL}") - - # Define paths based on OS - cursor_paths = [] - machine_id_paths = [] - trial_info_path = "" + # Read config file + config = configparser.ConfigParser() + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") - if system == "Windows": - cursor_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor"), - os.path.join(home, "AppData", "Local", "Cursor"), - os.path.join(home, "AppData", "Roaming", "cursor-electron"), - os.path.join(home, "AppData", "Local", "cursor-electron"), - os.path.join(home, "AppData", "Local", "CursorAI"), - os.path.join(home, "AppData", "Roaming", "CursorAI"), - os.path.join(home, "AppData", "Local", "Temp", "Cursor"), - os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - os.path.join(home, ".cursor-state.json"), - ] - machine_id_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json") + # Create config directory if it doesn't exist + if not os.path.exists(config_dir): + os.makedirs(config_dir) + + # Default paths for different systems + default_paths = { + "Darwin": "/Applications/Cursor.app/Contents/Resources/app", + "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app")] + } + + # If config doesn't exist, create it with default paths + if not os.path.exists(config_file): + for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']: + if not config.has_section(section): + config.add_section(section) - elif system == "Darwin": # macOS - cursor_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor"), - os.path.join(home, "Library", "Application Support", "cursor-electron"), - os.path.join(home, "Library", "Caches", "Cursor"), - os.path.join(home, "Library", "Caches", "cursor-electron"), - os.path.join(home, "Library", "Preferences", "Cursor"), - os.path.join(home, "Library", "Preferences", "cursor-electron"), - os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"), - os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - ] - machine_id_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json") + if system == "Darwin": + config.set('MacPaths', 'cursor_path', default_paths["Darwin"]) + elif system == "Windows": + config.set('WindowsPaths', 'cursor_path', default_paths["Windows"]) + elif system == "Linux": + # For Linux, try to find the first existing path + for path in default_paths["Linux"]: + if os.path.exists(path): + config.set('LinuxPaths', 'cursor_path', path) + break + else: + # If no path exists, use the first one as default + config.set('LinuxPaths', 'cursor_path', default_paths["Linux"][0]) - elif system == "Linux": - cursor_paths = [ - os.path.join(home, ".config", "Cursor"), - os.path.join(home, ".config", "cursor-electron"), - os.path.join(home, ".cache", "Cursor"), - os.path.join(home, ".cache", "cursor-electron"), - os.path.join(home, ".local", "share", "Cursor"), - os.path.join(home, ".local", "share", "cursor-electron"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - ] - machine_id_paths = [ - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, ".config", "Cursor", "trial_info.json") + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) else: - print(f"{Fore.RED}{EMOJI['ERROR']} Unsupported OS: {system} {Style.RESET_ALL}") - return + config.read(config_file, encoding='utf-8') + + # Get path based on system + if system == "Darwin": + section = 'MacPaths' + elif system == "Windows": + section = 'WindowsPaths' + elif system == "Linux": + section = 'LinuxPaths' + else: + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") + + if not config.has_section(section) or not config.has_option(section, 'cursor_path'): + raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑") + + base_path = config.get(section, 'cursor_path') + + # For Linux, try to find the first existing path if the configured one doesn't exist + if system == "Linux" and not os.path.exists(base_path): + for path in default_paths["Linux"]: + if os.path.exists(path): + base_path = path + # Update config with the found path + config.set(section, 'cursor_path', path) + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + break + + if not os.path.exists(base_path): + raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}") + + pkg_path = os.path.join(base_path, "package.json") + main_path = os.path.join(base_path, "out/main.js") + + # Check if files exist + if not os.path.exists(pkg_path): + raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}") + if not os.path.exists(main_path): + raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}") + + return (pkg_path, main_path) - # Remove main Cursor directories and files - for path in cursor_paths: - remove_path(path) +def get_cursor_machine_id_path(translator=None) -> str: + """ + Get Cursor machineId file path based on operating system + Returns: + str: Path to machineId file + """ + # Read configuration + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + config = configparser.ConfigParser() + + if os.path.exists(config_file): + config.read(config_file) + + if sys.platform == "win32": # Windows + if not config.has_section('WindowsPaths'): + config.add_section('WindowsPaths') + config.set('WindowsPaths', 'machine_id_path', + os.path.join(os.getenv("APPDATA"), "Cursor", "machineId")) + return config.get('WindowsPaths', 'machine_id_path') + + elif sys.platform == "linux": # Linux + if not config.has_section('LinuxPaths'): + config.add_section('LinuxPaths') + config.set('LinuxPaths', 'machine_id_path', + os.path.expanduser("~/.config/cursor/machineid")) + return config.get('LinuxPaths', 'machine_id_path') + + elif sys.platform == "darwin": # macOS + if not config.has_section('MacPaths'): + config.add_section('MacPaths') + config.set('MacPaths', 'machine_id_path', + os.path.expanduser("~/Library/Application Support/Cursor/machineId")) + return config.get('MacPaths', 'machine_id_path') + + else: + raise OSError(f"Unsupported operating system: {sys.platform}") - # Create new machine IDs - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new machine identifiers...{Style.RESET_ALL}") - for path in machine_id_paths: - create_fake_machine_id(path) + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) - # Create new trial with extended expiration - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new trial information...{Style.RESET_ALL}") - create_fake_trial_info(trial_info_path) +def get_workbench_cursor_path(translator=None) -> str: + """Get Cursor workbench.desktop.main.js path""" + system = platform.system() + + paths_map = { + "Darwin": { # macOS + "base": "/Applications/Cursor.app/Contents/Resources/app", + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Windows": { + "base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Linux": { + "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], + "main": "out/vs/workbench/workbench.desktop.main.js" + } + } - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} Cursor AI has been reset successfully!") - print(f"{Fore.CYAN}{EMOJI['INFO']} Reinstall Cursor if needed and enjoy your extended trial.{Style.RESET_ALL}") + if system not in paths_map: + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") + + if system == "Linux": + for base in paths_map["Linux"]["bases"]: + main_path = os.path.join(base, paths_map["Linux"]["main"]) + if os.path.exists(main_path): + return main_path + raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径") + + base_path = paths_map[system]["base"] + main_path = os.path.join(base_path, paths_map[system]["main"]) + + if not os.path.exists(main_path): + raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}") + + return main_path + +def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool: + """Version number check""" + version_pattern = r"^\d+\.\d+\.\d+$" + try: + if not re.match(version_pattern, version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + def parse_version(ver: str) -> Tuple[int, ...]: + return tuple(map(int, ver.split("."))) + + current = parse_version(version) + + if min_version and current < parse_version(min_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}") + return False + + if max_version and current > parse_version(max_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}") + return False + + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}") + return False + +def check_cursor_version(translator) -> bool: + """Check Cursor version""" + try: + pkg_path, _ = get_cursor_paths(translator) + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}") + + try: + with open(pkg_path, "r", encoding="utf-8") as f: + data = json.load(f) + except UnicodeDecodeError: + # If UTF-8 reading fails, try other encodings + with open(pkg_path, "r", encoding="latin-1") as f: + data = json.load(f) + + if not isinstance(data, dict): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + + if "version" not in data: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}") + return False + + version = str(data["version"]).strip() + if not version: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}") + + # Check version format + if not re.match(r"^\d+\.\d+\.\d+$", version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + # Compare versions + try: + current = tuple(map(int, version.split("."))) + min_ver = (0, 45, 0) # Use tuple directly instead of string + + if current >= min_ver: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return True + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return False + except ValueError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}") + return False + + except FileNotFoundError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}") + return False + except json.JSONDecodeError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}") + return False + +def modify_workbench_js(file_path: str, translator=None) -> bool: + """ + Modify file content + """ + try: + # Save original file permissions + original_stat = os.stat(file_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + # Create temporary file + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file: + # Read original content + with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file: + content = main_file.read() + + if sys.platform == "win32": + # Define replacement patterns + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "linux": + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "darwin": + CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + + CBadge_old_pattern = r'