diff --git a/.env b/.env index 95a7d79..1199388 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.7.17 -VERSION=1.7.17 +version=1.8.05 +VERSION=1.8.05 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd90d39..b27e0d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: version: description: 'Version number (e.g. 1.0.9)' required: true - default: '1.7.17' + default: '1.8.04' permissions: contents: write diff --git a/.gitignore b/.gitignore index 654a570..2e4e1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ build.py build.sh ENV/ test.py +new_tempemail_smail.py +new_tempemail_api.py install.bat run.bat diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b9d6f..3f9bfcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,49 @@ # Change Log -## v1.7.18 (Pre-Release) -1. Fix: No Write Permission | 修復沒有寫入權限 +## v1.8.05 +1. Fix: Linux Path Not Found | 修復linuxpath問題 +2. Add: support for detecting both 150/150 and 50/50 usage limits | 添加偵測50 或者150的使用量 +3. Improve: usage parsing and validation | 檢測使用量 + +## v1.8.04 +1. Update totally_reset_cursor.py | 更新 totally_reset_cursor.py +2. Fix: improve Linux Chrome visibility and root user handling | 修復 Linux Chrome 可見性以及 root 用戶處理 +3. Fix: improve Linux path handling and fix permission issues | 修復 Linux 路徑處理以及修復權限問題 +4. Fix: Some Issues | 修復一些問題 + +## v1.8.03 +1. Fix: Improve Linux path handling and add case-insensitive Cursor directory detection | 修復Linux系統路徑錯誤以及添加cursor 路徑偵測 2. Fix: Some Issues | 修復一些問題 +## v1.8.02 +1. Add: New Temp Email | 增加新臨時郵箱 +2. Add: Config Options | 增加配置選項 +3. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID +4. Add: Contributors Options | 增加貢獻者選項 +5. Add: Check update enable Options In config | 增加在 config 中檢查更新選項 +6. Add: Show account info enabled options in config | 增加在 config 中顯示賬號信息選項 +7. Optimize Row & Colume Options | 優化行與列選項 +8. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用 +9. Fix: Disable Auto Update | 修復禁用自動更新 +10. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開 +11. Fix: Some Issues | 修復一些問題 + +## v1.8.01 +1. Add: Cursor Account Info | 增加 Cursor 賬號信息 +2. Fix: Disable Auto Update | 修復禁用自動更新 +3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持 +4. Revert: Totally Reser Cursor to Beta | 恢復完全重置 Cursor 到 Beta +5. Reopen: Totally Reset Cursor | 重新開啟完全重置 Cursor +6. Fix: Logo.py Center | 修復 Logo.py 居中 +7. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開 +8. Fix: Some Issues | 修復一些問題 + +## v1.7.18 +1. Fix: No Write Permission | 修復沒有寫入權限 +2. Fix: Improve Linux path detection and config handling | 修正 linux 路徑和config寫入讀取 +3. Fix: Locale path_no_exist missing | 修正 path_no_exist 語言遺失 +4. Fix: Some Issues | 修復一些問題 + ## v1.7.17 1. Fix: Remove 10 options Totally Reset Cursor | 修復完全重置 Cursor 選項 diff --git a/README.md b/README.md index 825144c..1f62b46 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,16 @@ [![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a1)](https://github.com/yeongpin/cursor-free-vip/releases/latest)

-

Support Latest 0.47.x Version | 支持最新 0.47.x 版本

+

Support Latest 0.48.x Version | 支持最新 0.48.x 版本

+ +This tool registers accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes, resets and wipes Cursor data and hardware info. + +Supports Windows, macOS and Linux. + +For optimal performance, run with privileges and always stay up to date. + +Always clean your browser's cache and cookies. If possible, use a VPN to create new accounts. -This is a tool to automatically register, support Windows and macOS systems, complete Auth verification, and reset -Cursor's configuration. 這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。 @@ -94,7 +100,7 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese -2. If you want to stop the script, please press Ctrl+C
要停止腳本,請按 Ctrl+C +If you want to stop the script, please press Ctrl+C
要停止腳本,請按 Ctrl+C ## ❗ Note | 注意事項 @@ -109,9 +115,9 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe [Turnstile] -# Handle Tuenstile Wait Time | 等待人機驗證時間 +# Handle Turnstile Wait Time | 等待人機驗證時間 handle_turnstile_time = 2 -# Handle Tuenstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合) +# Handle Turnstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合) handle_turnstile_random_time = 1-3 [OSPaths] @@ -152,11 +158,17 @@ failed_retry_time = 0.5-1 retry_interval = 8-12 # Max Timeout | 最大超時時間 max_timeout = 160 + +[Utils] +# Check Update | 檢查更新 +check_update = True +# Show Account Info | 顯示賬號信息 +show_account_info = True ``` -* Use administrator to run the script
請使用管理員身份運行腳本 +* Use administrator privileges to run the script
請使用管理員身份運行腳本 * Confirm that Cursor is closed before running the script
請確保在運行腳本前已經關閉 Cursor
diff --git a/block_domain.txt b/block_domain.txt index ea56852..e3412a6 100644 --- a/block_domain.txt +++ b/block_domain.txt @@ -16,3 +16,7 @@ ikomail.com mailpull.com drewzen.com begemail.com +dugmail.com +solerbe.net +corhash.net +mailshou.com \ No newline at end of file diff --git a/config.py b/config.py index 8720412..cf481c0 100644 --- a/config.py +++ b/config.py @@ -4,6 +4,18 @@ import configparser from colorama import Fore, Style from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path +EMOJI = { + "INFO": "ℹ️", + "WARNING": "⚠️", + "ERROR": "❌", + "SUCCESS": "✅", + "ADMIN": "🔒", + "ARROW": "➡️", + "USER": "👤", + "KEY": "🔑", + "SETTINGS": "⚙️" +} + def setup_config(translator=None): """Setup configuration file and return config object""" try: @@ -37,6 +49,10 @@ def setup_config(translator=None): 'failed_retry_time': '0.5-1', 'retry_interval': '8-12', 'max_timeout': '160' + }, + 'Utils': { + 'enabled_update_check': 'True', + 'enabled_account_info': 'True' } } @@ -49,26 +65,127 @@ def setup_config(translator=None): 'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"), 'machine_id_path': os.path.join(appdata, "Cursor", "machineId"), 'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"), - 'updater_path': os.path.join(localappdata, "cursor-updater") + 'updater_path': os.path.join(localappdata, "cursor-updater"), + 'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml") } + # Create storage directory + os.makedirs(os.path.dirname(default_config['WindowsPaths']['storage_path']), exist_ok=True) + elif sys.platform == "darwin": default_config['MacPaths'] = { 'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")), 'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")), 'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"), 'cursor_path': "/Applications/Cursor.app/Contents/Resources/app", - 'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater") + 'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"), + 'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml" } - elif sys.platform == "linux": - sudo_user = os.environ.get('SUDO_USER') - actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") + # Create storage directory + os.makedirs(os.path.dirname(default_config['MacPaths']['storage_path']), exist_ok=True) + elif sys.platform == "linux": + # Get the actual user's home directory, handling both sudo and normal cases + sudo_user = os.environ.get('SUDO_USER') + current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME')) + + if not current_user: + current_user = os.path.expanduser('~').split('/')[-1] + + # Handle sudo case + if sudo_user: + actual_home = f"/home/{sudo_user}" + root_home = "/root" + else: + actual_home = f"/home/{current_user}" + root_home = None + + if not os.path.exists(actual_home): + actual_home = os.path.expanduser("~") + + # Define base config directory + config_base = os.path.join(actual_home, ".config") + + # Try both "Cursor" and "cursor" directory names in both user and root locations + cursor_dir = None + possible_paths = [ + os.path.join(config_base, "Cursor"), + os.path.join(config_base, "cursor"), + os.path.join(root_home, ".config", "Cursor") if root_home else None, + os.path.join(root_home, ".config", "cursor") if root_home else None + ] + + for path in possible_paths: + if path and os.path.exists(path): + cursor_dir = path + break + + if not cursor_dir: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.neither_cursor_nor_cursor_directory_found', config_base=config_base) if translator else f'Neither Cursor nor cursor directory found in {config_base}'}{Style.RESET_ALL}") + if root_home: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.also_checked', path=f'{root_home}/.config') if translator else f'Also checked {root_home}/.config'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}") + + # Define Linux paths using the found cursor directory + storage_path = os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/storage.json")) if cursor_dir else "" + storage_dir = os.path.dirname(storage_path) if storage_path else "" + + # Verify paths and permissions + try: + # Check storage directory + if storage_dir and not os.path.exists(storage_dir): + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_directory_not_found', storage_dir=storage_dir) if translator else f'Storage directory not found: {storage_dir}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}") + + # Check storage.json with more detailed verification + if storage_path and os.path.exists(storage_path): + # Get file stats + try: + stat = os.stat(storage_path) + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.storage_file_found', storage_path=storage_path) if translator else f'Storage file found: {storage_path}'}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', size=stat.st_size) if translator else f'File size: {stat.st_size} bytes'}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', permissions=oct(stat.st_mode & 0o777)) if translator else f'File permissions: {oct(stat.st_mode & 0o777)}'}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', owner=stat.st_uid) if translator else f'File owner: {stat.st_uid}'}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', group=stat.st_gid) if translator else f'File group: {stat.st_gid}'}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_getting_file_stats', error=str(e)) if translator else f'Error getting file stats: {str(e)}'}{Style.RESET_ALL}") + + # Check if file is readable and writable + if not os.access(storage_path, os.R_OK | os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.permission_denied', storage_path=storage_path) if translator else f'Permission denied: {storage_path}'}{Style.RESET_ALL}") + if sudo_user: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {sudo_user}:{sudo_user} {storage_path}') if translator else f'Try running: chown {sudo_user}:{sudo_user} {storage_path}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {current_user}:{current_user} {storage_path}') if translator else f'Try running: chown {current_user}:{current_user} {storage_path}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}") + + # Try to read the file to verify it's not corrupted + try: + with open(storage_path, 'r') as f: + content = f.read() + if not content.strip(): + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_is_empty', storage_path=storage_path) if translator else f'Storage file is empty: {storage_path}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted, please reinstall Cursor'}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data') if translator else 'Storage file is valid and contains data'}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_reading_storage_file', error=str(e)) if translator else f'Error reading storage file: {str(e)}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted. Please reinstall Cursor'}{Style.RESET_ALL}") + elif storage_path: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_not_found', storage_path=storage_path) if translator else f'Storage file not found: {storage_path}'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}") + + except (OSError, IOError) as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_checking_linux_paths', error=str(e)) if translator else f'Error checking Linux paths: {str(e)}'}{Style.RESET_ALL}") + + # Define all paths using the found cursor directory default_config['LinuxPaths'] = { - 'storage_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")), - 'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")), - 'machine_id_path': os.path.expanduser("~/.config/cursor/machineid"), + 'storage_path': storage_path, + 'sqlite_path': os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/state.vscdb")) if cursor_dir else "", + 'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "", 'cursor_path': get_linux_cursor_path(), - 'updater_path': os.path.expanduser("~/.config/cursor-updater") + 'updater_path': os.path.join(config_base, "cursor-updater"), + 'update_yml_path': os.path.join(cursor_dir, "resources/app-update.yml") if cursor_dir else "" } # Read existing configuration and merge @@ -85,13 +202,13 @@ def setup_config(translator=None): config.set(section, option, str(value)) config_modified = True if translator: - print(f"{Fore.YELLOW}ℹ️ {translator.get('register.config_option_added', option=f'{section}.{option}')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.config_option_added', option=f'{section}.{option}') if translator else f'Config option added: {section}.{option}'}{Style.RESET_ALL}") if config_modified: with open(config_file, 'w', encoding='utf-8') as f: config.write(f) if translator: - print(f"{Fore.GREEN}✅ {translator.get('register.config_updated')}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_updated') if translator else 'Config updated'}{Style.RESET_ALL}") else: for section, options in default_config.items(): config.add_section(section) @@ -101,16 +218,41 @@ def setup_config(translator=None): with open(config_file, 'w', encoding='utf-8') as f: config.write(f) if translator: - print(f"{Fore.GREEN}✅ {translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_created', config_file=config_file) if translator else f'Config created: {config_file}'}{Style.RESET_ALL}") return config except Exception as e: if translator: - print(f"{Fore.RED}❌ {translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.config_setup_error', error=str(e)) if translator else f'Error setting up config: {str(e)}'}{Style.RESET_ALL}") return None + +def print_config(config, translator=None): + """Print configuration in a readable format""" + if not config: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available') if translator else 'Configuration not available'}{Style.RESET_ALL}") + return + + print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration') if translator else 'Configuration'}:{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") + for section in config.sections(): + print(f"{Fore.GREEN}[{section}]{Style.RESET_ALL}") + for key, value in config.items(section): + # 对布尔值进行特殊处理,使其显示为彩色 + if value.lower() in ('true', 'yes', 'on', '1'): + value_display = f"{Fore.GREEN}{translator.get('config.enabled') if translator else 'Enabled'}{Style.RESET_ALL}" + elif value.lower() in ('false', 'no', 'off', '0'): + value_display = f"{Fore.RED}{translator.get('config.disabled') if translator else 'Disabled'}{Style.RESET_ALL}" + else: + value_display = value + + print(f" {key} = {value_display}") + + print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip", "config.ini") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory') if translator else 'Config Directory'}: {config_dir}{Style.RESET_ALL}") + + print() def get_config(translator=None): """Get existing config or create new one""" diff --git a/cursor_acc_info.py b/cursor_acc_info.py new file mode 100644 index 0000000..5ce679c --- /dev/null +++ b/cursor_acc_info.py @@ -0,0 +1,552 @@ +import os +import sys +import json +import requests +import sqlite3 +from typing import Dict, Optional +import platform +from colorama import Fore, Style, init +import logging +import re + +# Initialize colorama +init() + +# Setup logger +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# Define emoji constants +EMOJI = { + "USER": "👤", + "USAGE": "📊", + "PREMIUM": "⭐", + "BASIC": "📝", + "SUBSCRIPTION": "💳", + "INFO": "ℹ️", + "ERROR": "❌", + "SUCCESS": "✅", + "WARNING": "⚠️", + "TIME": "🕒" +} + +class Config: + """Config""" + NAME_LOWER = "cursor" + NAME_CAPITALIZE = "Cursor" + BASE_HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Accept": "application/json", + "Content-Type": "application/json" + } + +class UsageManager: + """Usage Manager""" + + @staticmethod + def get_proxy(): + """get proxy""" + # from config import get_config + proxy = os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY") + if proxy: + return {"http": proxy, "https": proxy} + return None + + @staticmethod + def get_usage(token: str) -> Optional[Dict]: + """get usage""" + url = f"https://www.{Config.NAME_LOWER}.com/api/usage" + headers = Config.BASE_HEADERS.copy() + headers.update({"Cookie": f"Workos{Config.NAME_CAPITALIZE}SessionToken=user_01OOOOOOOOOOOOOOOOOOOOOOOO%3A%3A{token}"}) + try: + proxies = UsageManager.get_proxy() + response = requests.get(url, headers=headers, timeout=10, proxies=proxies) + response.raise_for_status() + data = response.json() + + # get Premium usage and limit + gpt4_data = data.get("gpt-4", {}) + premium_usage = gpt4_data.get("numRequestsTotal", 0) + max_premium_usage = gpt4_data.get("maxRequestUsage", 999) + + # get Basic usage, but set limit to "No Limit" + gpt35_data = data.get("gpt-3.5-turbo", {}) + basic_usage = gpt35_data.get("numRequestsTotal", 0) + + return { + 'premium_usage': premium_usage, + 'max_premium_usage': max_premium_usage, + 'basic_usage': basic_usage, + 'max_basic_usage': "No Limit" # set Basic limit to "No Limit" + } + except requests.RequestException as e: + # only log error + logger.error(f"Get usage info failed: {str(e)}") + return None + except Exception as e: + # catch all other exceptions + logger.error(f"Get usage info failed: {str(e)}") + return None + + @staticmethod + def get_stripe_profile(token: str) -> Optional[Dict]: + """get user subscription info""" + url = f"https://api2.{Config.NAME_LOWER}.sh/auth/full_stripe_profile" + headers = Config.BASE_HEADERS.copy() + headers.update({"Authorization": f"Bearer {token}"}) + try: + proxies = UsageManager.get_proxy() + response = requests.get(url, headers=headers, timeout=10, proxies=proxies) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + logger.error(f"Get subscription info failed: {str(e)}") + return None + +def get_token_from_config(): + """get path info from config""" + try: + from config import get_config + config = get_config() + if not config: + return None + + system = platform.system() + if system == "Windows" and config.has_section('WindowsPaths'): + return { + 'storage_path': config.get('WindowsPaths', 'storage_path'), + 'sqlite_path': config.get('WindowsPaths', 'sqlite_path'), + 'session_path': os.path.join(os.getenv("APPDATA"), "Cursor", "Session Storage") + } + elif system == "Darwin" and config.has_section('MacPaths'): # macOS + return { + 'storage_path': config.get('MacPaths', 'storage_path'), + 'sqlite_path': config.get('MacPaths', 'sqlite_path'), + 'session_path': os.path.expanduser("~/Library/Application Support/Cursor/Session Storage") + } + elif system == "Linux" and config.has_section('LinuxPaths'): + return { + 'storage_path': config.get('LinuxPaths', 'storage_path'), + 'sqlite_path': config.get('LinuxPaths', 'sqlite_path'), + 'session_path': os.path.expanduser("~/.config/Cursor/Session Storage") + } + except Exception as e: + logger.error(f"Get config path failed: {str(e)}") + + return None + +def get_token_from_storage(storage_path): + """get token from storage.json""" + if not os.path.exists(storage_path): + return None + + try: + with open(storage_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # try to get accessToken + if 'cursorAuth/accessToken' in data: + return data['cursorAuth/accessToken'] + + # try other possible keys + for key in data: + if 'token' in key.lower() and isinstance(data[key], str) and len(data[key]) > 20: + return data[key] + except Exception as e: + logger.error(f"get token from storage.json failed: {str(e)}") + + return None + +def get_token_from_sqlite(sqlite_path): + """get token from sqlite""" + if not os.path.exists(sqlite_path): + return None + + try: + conn = sqlite3.connect(sqlite_path) + cursor = conn.cursor() + cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%token%'") + rows = cursor.fetchall() + conn.close() + + for row in rows: + try: + value = row[0] + if isinstance(value, str) and len(value) > 20: + return value + # try to parse JSON + data = json.loads(value) + if isinstance(data, dict) and 'token' in data: + return data['token'] + except: + continue + except Exception as e: + logger.error(f"get token from sqlite failed: {str(e)}") + + return None + +def get_token_from_session(session_path): + """get token from session""" + if not os.path.exists(session_path): + return None + + try: + # try to find all possible session files + for file in os.listdir(session_path): + if file.endswith('.log'): + file_path = os.path.join(session_path, file) + try: + with open(file_path, 'rb') as f: + content = f.read().decode('utf-8', errors='ignore') + # find token pattern + token_match = re.search(r'"token":"([^"]+)"', content) + if token_match: + return token_match.group(1) + except: + continue + except Exception as e: + logger.error(f"get token from session failed: {str(e)}") + + return None + +def get_token(): + """get Cursor token""" + # get path from config + paths = get_token_from_config() + if not paths: + return None + + # try to get token from different locations + token = get_token_from_storage(paths['storage_path']) + if token: + return token + + token = get_token_from_sqlite(paths['sqlite_path']) + if token: + return token + + token = get_token_from_session(paths['session_path']) + if token: + return token + + return None + +def format_subscription_type(subscription_data: Dict) -> str: + """format subscription type""" + if not subscription_data: + return "Free" + + # handle new API response format + if "membershipType" in subscription_data: + membership_type = subscription_data.get("membershipType", "").lower() + subscription_status = subscription_data.get("subscriptionStatus", "").lower() + + if subscription_status == "active": + if membership_type == "pro": + return "Pro" + elif membership_type == "free_trial": + return "Free Trial" + elif membership_type == "pro_trial": + return "Pro Trial" + elif membership_type == "team": + return "Team" + elif membership_type == "enterprise": + return "Enterprise" + elif membership_type: + return membership_type.capitalize() + else: + return "Active Subscription" + elif subscription_status: + return f"{membership_type.capitalize()} ({subscription_status})" + + # compatible with old API response format + subscription = subscription_data.get("subscription") + if subscription: + plan = subscription.get("plan", {}).get("nickname", "Unknown") + status = subscription.get("status", "unknown") + + if status == "active": + if "pro" in plan.lower(): + return "Pro" + elif "pro_trial" in plan.lower(): + return "Pro Trial" + elif "free_trial" in plan.lower(): + return "Free Trial" + elif "team" in plan.lower(): + return "Team" + elif "enterprise" in plan.lower(): + return "Enterprise" + else: + return plan + else: + return f"{plan} ({status})" + + return "Free" + +def get_email_from_storage(storage_path): + """get email from storage.json""" + if not os.path.exists(storage_path): + return None + + try: + with open(storage_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # try to get email + if 'cursorAuth/cachedEmail' in data: + return data['cursorAuth/cachedEmail'] + + # try other possible keys + for key in data: + if 'email' in key.lower() and isinstance(data[key], str) and '@' in data[key]: + return data[key] + except Exception as e: + logger.error(f"get email from storage.json failed: {str(e)}") + + return None + +def get_email_from_sqlite(sqlite_path): + """get email from sqlite""" + if not os.path.exists(sqlite_path): + return None + + try: + conn = sqlite3.connect(sqlite_path) + cursor = conn.cursor() + # try to query records containing email + cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%email%' OR key LIKE '%cursorAuth%'") + rows = cursor.fetchall() + conn.close() + + for row in rows: + try: + value = row[0] + # if it's a string and contains @, it might be an email + if isinstance(value, str) and '@' in value: + return value + + # try to parse JSON + try: + data = json.loads(value) + if isinstance(data, dict): + # check if there's an email field + if 'email' in data: + return data['email'] + # check if there's a cachedEmail field + if 'cachedEmail' in data: + return data['cachedEmail'] + except: + pass + except: + continue + except Exception as e: + logger.error(f"get email from sqlite failed: {str(e)}") + + return None + +def display_account_info(translator=None): + """display account info""" + print(f"\n{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['USER']} {translator.get('account_info.title') if translator else 'Cursor Account Information'}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") + + # get token + token = get_token() + if not token: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.token_not_found') if translator else 'Token not found. Please login to Cursor first.'}{Style.RESET_ALL}") + return + + # get path info + paths = get_token_from_config() + if not paths: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.config_not_found') if translator else 'Configuration not found.'}{Style.RESET_ALL}") + return + + # get email info - try multiple sources + email = get_email_from_storage(paths['storage_path']) + + # if not found in storage, try from sqlite + if not email: + email = get_email_from_sqlite(paths['sqlite_path']) + + # get subscription info + try: + subscription_info = UsageManager.get_stripe_profile(token) + except Exception as e: + logger.error(f"Get subscription info failed: {str(e)}") + subscription_info = None + + # if not found in storage and sqlite, try from subscription info + if not email and subscription_info: + # try to get email from subscription info + if 'customer' in subscription_info and 'email' in subscription_info['customer']: + email = subscription_info['customer']['email'] + + # get usage info - silently handle errors + try: + usage_info = UsageManager.get_usage(token) + except Exception as e: + logger.error(f"Get usage info failed: {str(e)}") + usage_info = None + + # Prepare left and right info + left_info = [] + right_info = [] + + # Left side shows account info + if email: + left_info.append(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}") + else: + left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}") + + # Add an empty line + # left_info.append("") + + # Show subscription type + if subscription_info: + subscription_type = format_subscription_type(subscription_info) + left_info.append(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}") + + # Show remaining trial days + days_remaining = subscription_info.get("daysRemainingOnTrial") + if days_remaining is not None and days_remaining > 0: + left_info.append(f"{Fore.GREEN}{EMOJI['TIME']} {translator.get('account_info.trial_remaining') if translator else 'Remaining Pro Trial'}: {Fore.WHITE}{days_remaining} {translator.get('account_info.days') if translator else 'days'}{Style.RESET_ALL}") + else: + left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}") + + # Right side shows usage info - only if available + if usage_info: + right_info.append(f"{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}") + + # Premium usage + premium_usage = usage_info.get('premium_usage', 0) + max_premium_usage = usage_info.get('max_premium_usage', "No Limit") + + # make sure the value is not None + if premium_usage is None: + premium_usage = 0 + + # handle "No Limit" case + if isinstance(max_premium_usage, str) and max_premium_usage == "No Limit": + premium_color = Fore.GREEN # when there is no limit, use green + premium_display = f"{premium_usage}/{max_premium_usage}" + else: + # calculate percentage when the value is a number + if max_premium_usage is None or max_premium_usage == 0: + max_premium_usage = 999 + premium_percentage = 0 + else: + premium_percentage = (premium_usage / max_premium_usage) * 100 + + # select color based on usage percentage + premium_color = Fore.GREEN + if premium_percentage > 70: + premium_color = Fore.YELLOW + if premium_percentage > 90: + premium_color = Fore.RED + + premium_display = f"{premium_usage}/{max_premium_usage} ({premium_percentage:.1f}%)" + + right_info.append(f"{Fore.YELLOW}{EMOJI['PREMIUM']} {translator.get('account_info.premium_usage') if translator else 'Fast Response'}: {premium_color}{premium_display}{Style.RESET_ALL}") + + # Slow Response + basic_usage = usage_info.get('basic_usage', 0) + max_basic_usage = usage_info.get('max_basic_usage', "No Limit") + + # make sure the value is not None + if basic_usage is None: + basic_usage = 0 + + # handle "No Limit" case + if isinstance(max_basic_usage, str) and max_basic_usage == "No Limit": + basic_color = Fore.GREEN # when there is no limit, use green + basic_display = f"{basic_usage}/{max_basic_usage}" + else: + # calculate percentage when the value is a number + if max_basic_usage is None or max_basic_usage == 0: + max_basic_usage = 999 + basic_percentage = 0 + else: + basic_percentage = (basic_usage / max_basic_usage) * 100 + + # select color based on usage percentage + basic_color = Fore.GREEN + if basic_percentage > 70: + basic_color = Fore.YELLOW + if basic_percentage > 90: + basic_color = Fore.RED + + basic_display = f"{basic_usage}/{max_basic_usage} ({basic_percentage:.1f}%)" + + right_info.append(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}") + else: + # if get usage info failed, only log in log, not show in interface + # you can choose to not show any usage info, or show a simple prompt + # right_info.append(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_info.usage_unavailable') if translator else 'Usage information unavailable'}{Style.RESET_ALL}") + pass # not show any usage info + + # Calculate the maximum display width of left info + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + + def get_display_width(s): + """Calculate the display width of a string, considering Chinese characters and emojis""" + # Remove ANSI color codes + clean_s = ansi_escape.sub('', s) + width = 0 + for c in clean_s: + # Chinese characters and some emojis occupy two character widths + if ord(c) > 127: + width += 2 + else: + width += 1 + return width + + max_left_width = 0 + for item in left_info: + width = get_display_width(item) + max_left_width = max(max_left_width, width) + + # Set the starting position of right info + fixed_spacing = 4 # Fixed spacing + right_start = max_left_width + fixed_spacing + + # Calculate the number of spaces needed for right info + spaces_list = [] + for i in range(len(left_info)): + if i < len(left_info): + left_item = left_info[i] + left_width = get_display_width(left_item) + spaces = right_start - left_width + spaces_list.append(spaces) + + # Print info + max_rows = max(len(left_info), len(right_info)) + + for i in range(max_rows): + # Print left info + if i < len(left_info): + left_item = left_info[i] + print(left_item, end='') + + # Use pre-calculated spaces + spaces = spaces_list[i] + else: + # If left side has no items, print only spaces + spaces = right_start + print('', end='') + + # Print right info + if i < len(right_info): + print(' ' * spaces + right_info[i]) + else: + print() # Change line + + print(f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") + +def main(translator=None): + """main function""" + try: + display_account_info(translator) + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.error') if translator else 'Error'}: {str(e)}{Style.RESET_ALL}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/disable_auto_update.py b/disable_auto_update.py index 046bf4d..f997e4f 100644 --- a/disable_auto_update.py +++ b/disable_auto_update.py @@ -5,6 +5,8 @@ import shutil from colorama import Fore, Style, init import subprocess from config import get_config +import re +import tempfile # Initialize colorama init() @@ -31,10 +33,13 @@ class AutoUpdateDisabler: if config: if self.system == "Windows": self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater")) + self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml")) elif self.system == "Darwin": self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater")) + self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml") elif self.system == "Linux": self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater")) + self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml")) else: # If configuration loading fails, use default paths self.updater_paths = { @@ -43,6 +48,52 @@ class AutoUpdateDisabler: "Linux": os.path.expanduser("~/.config/cursor-updater") } self.updater_path = self.updater_paths.get(self.system) + + self.update_yml_paths = { + "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"), + "Darwin": "/Applications/Cursor.app/Contents/Resources/app-update.yml", + "Linux": os.path.expanduser("~/.config/cursor/resources/app-update.yml") + } + self.update_yml_path = self.update_yml_paths.get(self.system) + + def _change_main_js(self): + """Change main.js""" + try: + main_path = get_config(self.translator).get('main_js_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/main.js")) + original_stat = os.stat(main_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: + with open(main_path, "r", encoding="utf-8") as main_file: + content = main_file.read() + + patterns = { + r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"", + } + + for pattern, replacement in patterns.items(): + content = re.sub(pattern, replacement, content) + + tmp_file.write(content) + tmp_path = tmp_file.name + + shutil.copy2(main_path, main_path + ".old") + shutil.move(tmp_path, main_path) + + os.chmod(main_path, original_mode) + if os.name != "nt": + os.chown(main_path, original_uid, original_gid) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") + if "tmp_path" in locals(): + os.unlink(tmp_path) + return False def _kill_cursor_processes(self): """End all Cursor processes""" @@ -81,27 +132,70 @@ class AutoUpdateDisabler: except Exception as e: print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}") + # 即使删除失败,也返回 True,继续执行下一步 + return True + + def _clear_update_yml_file(self): + """Clear update.yml file""" + try: + update_yml_path = self.update_yml_path + if not update_yml_path: + raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") + + print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}") + + if os.path.exists(update_yml_path): + # 清空文件内容 + with open(update_yml_path, 'w') as f: + f.write('') + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}") + return True + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.clear_update_yml_failed', error=str(e)) if self.translator else f'清空更新配置文件失败: {e}'}{Style.RESET_ALL}") return False def _create_blocking_file(self): - """Create blocking file""" + """Create blocking files""" try: + # 检查 updater_path updater_path = self.updater_path if not updater_path: raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}") - # Create empty file + # 创建 updater_path 阻止文件 + os.makedirs(os.path.dirname(updater_path), exist_ok=True) open(updater_path, 'w').close() - # Set read-only attribute + # 设置 updater_path 为只读 if self.system == "Windows": os.system(f'attrib +r "{updater_path}"') else: - os.chmod(updater_path, 0o444) # Set to read-only + os.chmod(updater_path, 0o444) # 设置为只读 + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}") + + # 检查 update_yml_path + update_yml_path = self.update_yml_path + if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)): + # 创建 update_yml_path 阻止文件 + with open(update_yml_path, 'w') as f: + f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n') - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}{Style.RESET_ALL}") + # 设置 update_yml_path 为只读 + if self.system == "Windows": + os.system(f'attrib +r "{update_yml_path}"') + else: + os.chmod(update_yml_path, 0o444) # 设置为只读 + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}") + return True except Exception as e: @@ -117,14 +211,21 @@ class AutoUpdateDisabler: if not self._kill_cursor_processes(): return False - # 2. Delete directory - if not self._remove_updater_directory(): + # 2. Delete directory - 即使失败也继续执行 + self._remove_updater_directory() + + # 3. Clear update.yml file + if not self._clear_update_yml_file(): return False - # 3. Create blocking file + # 4. Create blocking file if not self._create_blocking_file(): return False + # 5. Change main.js + if not self._change_main_js(): + return False + print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}") return True diff --git a/locales/en.json b/locales/en.json index 6054903..f5e84a8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -22,7 +22,9 @@ "admin_required": "Running as executable, administrator privileges required.", "admin_required_continue": "Continuing without administrator privileges.", "coming_soon": "Coming Soon", - "fixed_soon": "Fixed Soon" + "fixed_soon": "Fixed Soon", + "contribute": "Contribute to the Project", + "config": "Show Config" }, "languages": { "en": "English", @@ -104,7 +106,12 @@ "check_version_failed": "Check Version Failed: {error}", "stack_trace": "Stack Trace", "version_too_low": "Cursor Version Too Low: {version} < 0.45.0", - "no_write_permission": "No Write Permission: {path}" + "no_write_permission": "No Write Permission: {path}", + "path_not_found": "Path Not Found: {path}", + "modify_file_failed": "Modify File Failed: {error}", + "windows_machine_id_updated": "Windows Machine ID Updated Successfully", + "update_windows_machine_id_failed": "Update Windows Machine ID Failed: {error}", + "update_windows_machine_guid_failed": "Update Windows Machine GUID Failed: {error}" }, "register": { "title": "Cursor Registration Tool", @@ -273,7 +280,13 @@ "available_domains_loaded": "Available Domains Loaded: {count}", "domains_filtered": "Domains Filtered: {count}", "trying_to_create_email": "Trying to create email: {email}", - "domain_blocked": "Domain Blocked: {domain}" + "domain_blocked": "Domain Blocked: {domain}", + "using_chrome_profile": "Using Chrome profile from: {user_data_dir}", + "no_display_found": "No display found. Make sure X server is running.", + "try_export_display": "Try: export DISPLAY=:0", + "extension_load_error": "Extension Load Error: {error}", + "make_sure_chrome_chromium_is_properly_installed": "Make sure Chrome/Chromium is properly installed", + "try_install_chromium": "Try: sudo apt install chromium-browser" }, "update": { "title": "Disable Cursor Auto Update", @@ -286,7 +299,14 @@ "removing_directory": "Removing Directory", "directory_removed": "Directory Removed", "creating_block_file": "Creating Block File", - "block_file_created": "Block File Created" + "block_file_created": "Block File Created", + "clearing_update_yml": "Clearing update.yml file", + "update_yml_cleared": "update.yml file cleared", + "update_yml_not_found": "update.yml file not found", + "clear_update_yml_failed": "Failed to clear update.yml file: {error}", + "unsupported_os": "Unsupported OS: {system}", + "remove_directory_failed": "Failed to remove directory: {error}", + "create_block_file_failed": "Failed to create block file: {error}" }, "updater": { "checking": "Checking for updates...", @@ -299,7 +319,8 @@ "update_skipped": "Skipping update.", "invalid_choice": "Invalid choice. Please enter 'Y' or 'n'.", "development_version": "Development Version {current} > {latest}", - "changelog_title": "Changelog" + "changelog_title": "Changelog", + "rate_limit_exceeded": "GitHub API rate limit exceeded. Skipping update check." }, "totally_reset": { "title": "Totally Reset Cursor", @@ -439,5 +460,126 @@ "completed_successfully": "GitHub + Cursor registration completed successfully!", "registration_encountered_issues": "GitHub + Cursor registration encountered issues.", "check_browser_windows_for_manual_intervention_or_try_again_later": "Check browser windows for manual intervention or try again later." - } -} \ No newline at end of file + }, + "account_info": { + "subscription": "Subscription", + "trial_remaining": "Remaining Pro Trial", + "days": "days", + "subscription_not_found": "Subscription information not found", + "email_not_found": "Email not found", + "failed_to_get_account": "Failed to get account information", + "config_not_found": "Configuration not found.", + "failed_to_get_usage": "Failed to get usage information", + "failed_to_get_subscription": "Failed to get subscription information", + "failed_to_get_email": "Failed to get email address", + "failed_to_get_token": "Failed to get token", + "failed_to_get_account_info": "Failed to get account information", + "title": "Account Information", + "email": "Email", + "token": "Token", + "usage": "Usage", + "subscription_type": "Subscription Type", + "remaining_trial": "Remaining Trial", + "days_remaining": "Days Remaining", + "premium": "Premium", + "pro": "Pro", + "pro_trial": "Pro Trial", + "team": "Team", + "enterprise": "Enterprise", + "free": "Free", + "active": "Active", + "inactive": "Inactive", + "premium_usage": "Premium Usage", + "basic_usage": "Basic Usage", + "usage_not_found": "Usage not found", + "lifetime_access_enabled": "Lifetime Access Enabled" + }, + "config": { + "config_not_available": "Configuration not available", + "configuration": "Configuration", + "enabled": "Enabled", + "disabled": "Disabled", + "config_directory": "Config Directory", + "neither_cursor_nor_cursor_directory_found": "Neither Cursor nor Cursor directory found in {config_base}", + "please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "Please make sure Cursor is installed and has been run at least once", + "storage_directory_not_found": "Storage directory not found: {storage_dir}", + "storage_file_found": "Storage file found: {storage_path}", + "file_size": "File size: {size} bytes", + "file_permissions": "File permissions: {permissions}", + "file_owner": "File owner: {owner}", + "file_group": "File group: {group}", + "error_getting_file_stats": "Error getting file stats: {error}", + "permission_denied": "Permission denied: {storage_path}", + "try_running": "Try running: {command}", + "and": "And", + "storage_file_is_empty": "Storage file is empty: {storage_path}", + "the_file_might_be_corrupted_please_reinstall_cursor": "The file might be corrupted, please reinstall Cursor", + "storage_file_not_found": "Storage file not found: {storage_path}", + "error_checking_linux_paths": "Error checking Linux paths: {error}", + "config_option_added": "Config option added: {option}", + "config_updated": "Config updated", + "config_created": "Config created: {config_file}", + "config_setup_error": "Error setting up config: {error}", + "storage_file_is_valid_and_contains_data": "Storage file is valid and contains data", + "error_reading_storage_file": "Error reading storage file: {error}", + "also_checked": "Also checked {path}" + }, + "oauth": { + "authentication_button_not_found": "Authentication button not found", + "authentication_failed": "Authentication failed: {error}", + "found_cookies": "Found {count} cookies", + "token_extraction_error": "Token extraction error: {error}", + "authentication_successful": "Authentication successful - Email: {email}", + "missing_authentication_data": "Missing authentication data: {data}", + "failed_to_delete_account": "Failed to delete account: {error}", + "invalid_authentication_type": "Invalid authentication type", + "auth_update_success": "Auth update success", + "browser_closed": "Browser closed", + "auth_update_failed": "Auth update failed", + "google_start": "Google start", + "github_start": "Github start", + "usage_count": "Usage count: {usage}", + "account_has_reached_maximum_usage": "Account has reached maximum usage, {deleting}", + "starting_new_authentication_process": "Starting new authentication process...", + "failed_to_delete_expired_account": "Failed to delete expired account", + "could_not_check_usage_count": "Could not check usage count: {error}", + "found_email": "Found email: {email}", + "could_not_find_email": "Could not find email: {error}", + "could_not_find_usage_count": "Could not find usage count: {error}", + "already_on_settings_page": "Already on settings page!", + "failed_to_extract_auth_info": "Failed to extract auth info: {error}", + "no_chrome_profiles_found": "No Chrome profiles found, using Default", + "found_default_chrome_profile": "Found Default Chrome profile", + "using_first_available_chrome_profile": "Using first available Chrome profile: {profile}", + "error_finding_chrome_profile": "Error finding Chrome profile, using Default: {error}", + "initializing_browser_setup": "Initializing browser setup...", + "detected_platform": "Detected platform: {platform}", + "running_as_root_warning": "Running as root is not recommended for browser automation", + "consider_running_without_sudo": "Consider running the script without sudo", + "no_compatible_browser_found": "No compatible browser found. Please install Google Chrome or Chromium.", + "supported_browsers": "Supported browsers for {platform}", + "using_browser_profile": "Using browser profile: {profile}", + "starting_browser": "Starting browser at: {path}", + "browser_setup_completed": "Browser setup completed successfully", + "browser_setup_failed": "Browser setup failed: {error}", + "try_running_without_sudo_admin": "Try running without sudo/administrator privileges", + "redirecting_to_authenticator_cursor_sh": "Redirecting to authenticator.cursor.sh...", + "starting_google_authentication": "Starting Google authentication...", + "starting_github_authentication": "Starting GitHub authentication...", + "waiting_for_authentication": "Waiting for authentication...", + "page_changed_checking_auth": "Page changed, checking auth...", + "status_check_error": "Status check error: {error}", + "authentication_timeout": "Authentication timeout", + "account_is_still_valid": "Account is still valid (Usage: {usage})", + "starting_re_authentication_process": "Starting re-authentication process...", + "starting_new_google_authentication": "Starting new Google authentication...", + "failed_to_delete_account_or_re_authenticate": "Failed to delete account or re-authenticate: {error}", + "navigating_to_authentication_page": "Navigating to authentication page...", + "please_select_your_google_account_to_continue": "Please select your Google account to continue...", + "found_browser_data_directory": "Found browser data directory: {path}", + "authentication_successful_getting_account_info": "Authentication successful, getting account info...", + "warning_could_not_kill_existing_browser_processes": "Warning: Could not kill existing browser processes: {error}", + "browser_failed_to_start": "Browser failed to start: {error}", + "browser_failed": "Browser failed to start: {error}" + } +} \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 992e854..1a4d304 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -22,7 +22,9 @@ "admin_required": "运行可执行文件,需要管理员权限", "admin_required_continue": "继续使用当前版本...", "coming_soon": "即将推出", - "fixed_soon": "即将修复" + "fixed_soon": "即将修复", + "contribute": "贡献项目", + "config": "显示配置" }, "languages": { "en": "英语", @@ -104,7 +106,12 @@ "check_version_failed": "检查版本失败: {error}", "stack_trace": "堆栈跟踪", "version_too_low": "Cursor版本太低: {version} < 0.45.0", - "no_write_permission": "没有写入权限: {path}" + "no_write_permission": "没有写入权限: {path}", + "path_not_found": "路径未找到: {path}", + "modify_file_failed": "修改文件失败: {error}", + "windows_machine_id_updated": "Windows机器ID更新成功", + "update_windows_machine_id_failed": "更新Windows机器ID失败: {error}", + "update_windows_machine_guid_failed": "更新Windows机器GUID失败: {error}" }, "register": { "title": "Cursor 注册工具", @@ -268,7 +275,13 @@ "available_domains_loaded": "获取到 {count} 个可用域名", "domains_filtered": "过滤后剩餘 {count} 個可用域名", "trying_to_create_email": "尝试创建邮箱: {email}", - "domain_blocked": "域名被屏蔽: {domain}" + "domain_blocked": "域名被屏蔽: {domain}", + "using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}", + "no_display_found": "未找到显示器。确保 X 服务器正在运行。", + "try_export_display": "尝试: export DISPLAY=:0", + "extension_load_error": "加载插件失败: {error}", + "make_sure_chrome_chromium_is_properly_installed": "确保 Chrome/Chromium 已正确安装", + "try_install_chromium": "尝试: sudo apt install chromium-browser" }, "update": { "title": "禁用 Cursor 自动更新", @@ -281,7 +294,14 @@ "removing_directory": "删除目录", "directory_removed": "目录已删除", "creating_block_file": "创建阻止文件", - "block_file_created": "阻止文件已创建" + "block_file_created": "阻止文件已创建", + "clearing_update_yml": "清空 update.yml 文件", + "update_yml_cleared": "update.yml 文件已清空", + "update_yml_not_found": "update.yml 文件未找到", + "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", + "unsupported_os": "不支持的操作系统: {system}", + "remove_directory_failed": "删除目录失败: {error}", + "create_block_file_failed": "创建阻止文件失败: {error}" }, "updater": { "checking": "检查更新...", @@ -294,7 +314,8 @@ "update_skipped": "跳过更新。", "invalid_choice": "选择无效。请输入 'Y' 或 'n'.", "development_version": "开发版本 {current} > {latest}", - "changelog_title": "更新日志" + "changelog_title": "更新日志", + "rate_limit_exceeded": "GitHub API 速率限制超过。跳过更新检查。" }, "totally_reset": { "title": "完全重置 Cursor", @@ -434,5 +455,127 @@ "completed_successfully": "GitHub + Cursor 注册成功", "registration_encountered_issues": "GitHub + Cursor 注册遇到问题", "check_browser_windows_for_manual_intervention_or_try_again_later": "检查浏览器窗口进行手动干预或稍后再试" + }, + "account_info": { + "subscription": "订阅", + "trial_remaining": "剩余试用", + "days": "天", + "subscription_not_found": "订阅信息未找到", + "email_not_found": "邮箱未找到", + "failed_to_get_account": "获取账户信息失败", + "config_not_found": "配置未找到。", + "failed_to_get_usage": "获取使用信息失败", + "failed_to_get_subscription": "获取订阅信息失败", + "failed_to_get_email": "获取邮箱地址失败", + "failed_to_get_token": "获取 token 失败", + "failed_to_get_account_info": "获取账户信息失败", + "title": "账户信息", + "email": "邮箱", + "token": "Token", + "usage": "使用量", + "subscription_type": "订阅类型", + "remaining_trial": "剩余试用", + "days_remaining": "剩余天数", + "premium": "高级", + "pro": "专业", + "pro_trial": "专业试用", + "team": "团队", + "enterprise": "企业", + "free": "免费", + "active": "活跃", + "inactive": "非活跃", + "premium_usage": "高级使用量", + "basic_usage": "基础使用量", + "usage_not_found": "使用量未找到", + "lifetime_access_enabled": "永久访问已启用" + }, + "config": { + "config_not_available": "配置未找到。", + "configuration": "配置", + "enabled": "已启用", + "disabled": "已禁用", + "config_directory": "配置目录", + "neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目录", + "please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "请确保 Cursor 已安装并至少运行一次", + "storage_directory_not_found": "未找到存储目录", + "storage_file_found": "找到存储文件", + "file_size": "文件大小", + "file_permissions": "文件权限", + "file_owner": "文件所有者", + "file_group": "文件组", + "error_getting_file_stats": "获取文件统计信息时出错", + "permission_denied": "权限拒绝", + "try_running": "尝试运行: {command}", + "and": "和", + "storage_file_is_empty": "存储文件为空", + "the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已损坏,请重新安装 Cursor", + "storage_file_not_found": "未找到存储文件", + "error_checking_linux_paths": "检查 Linux 路径时出错", + "config_option_added": "添加配置选项", + "config_updated": "配置更新", + "config_created": "配置已创建", + "config_setup_error": "配置设置错误", + "storage_file_is_valid_and_contains_data": "存储文件有效且包含数据", + "error_reading_storage_file": "读取存储文件时出错", + "also_checked": "也检查了 {path}" + }, + "oauth": { + "authentication_button_not_found": "未找到认证按钮", + "authentication_failed": "认证失败: {error}", + "found_cookies": "找到 {count} 个 Cookie", + "token_extraction_error": "Token 提取错误: {error}", + "authentication_successful": "认证成功 - 邮箱: {email}", + "missing_authentication_data": "缺少认证数据: {data}", + "failed_to_delete_account": "删除账户失败: {error}", + "invalid_authentication_type": "无效的认证类型", + "auth_update_success": "认证更新成功", + "browser_closed": "浏览器已关闭", + "auth_update_failed": "认证更新失败", + "google_start": "Google 开始", + "github_start": "Github 开始", + "usage_count": "使用次数: {usage}", + "account_has_reached_maximum_usage": "账户已达到最大使用量, {deleting}", + "starting_new_authentication_process": "开始新的认证过程...", + "failed_to_delete_expired_account": "删除过期账户失败", + "could_not_check_usage_count": "无法检查使用次数: {error}", + "found_email": "找到邮箱: {email}", + "could_not_find_email": "未找到邮箱: {error}", + "could_not_find_usage_count": "未找到使用次数: {error}", + "already_on_settings_page": "已处于设置页面", + "failed_to_extract_auth_info": "提取认证信息失败: {error}", + "no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默认配置文件", + "found_default_chrome_profile": "找到默认 Chrome 配置文件", + "using_first_available_chrome_profile": "使用第一个可用的 Chrome 配置文件: {profile}", + "error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默认配置文件: {error}", + "initializing_browser_setup": "初始化浏览器设置...", + "detected_platform": "检测平台: {platform}", + "running_as_root_warning": "以 root 运行不推荐用于浏览器自动化", + "consider_running_without_sudo": "考虑不使用 sudo 运行脚本", + "no_compatible_browser_found": "未找到兼容的浏览器。请安装 Google Chrome 或 Chromium。", + "supported_browsers": "支持的浏览器: {platform}", + "using_browser_profile": "使用浏览器配置文件: {profile}", + "starting_browser": "正在启动浏览器: {path}", + "browser_setup_completed": "浏览器设置完成", + "browser_setup_failed": "浏览器设置失败: {error}", + "try_running_without_sudo_admin": "尝试不使用 sudo/管理员权限运行", + "redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...", + "starting_google_authentication": "开始 Google 认证...", + "starting_github_authentication": "开始 Github 认证...", + "waiting_for_authentication": "等待认证...", + "page_changed_checking_auth": "页面改变, 检查认证...", + "status_check_error": "状态检查错误: {error}", + "authentication_timeout": "认证超时", + "account_is_still_valid": "账户仍然有效 (使用量: {usage})", + "starting_re_authentication_process": "开始重新认证过程...", + "starting_new_google_authentication": "开始新的 Google 认证...", + "failed_to_delete_account_or_re_authenticate": "删除账户或重新认证失败: {error}", + "navigating_to_authentication_page": "正在导航到认证页面...", + "please_select_your_google_account_to_continue": "请选择您的 Google 账户以继续...", + "found_browser_data_directory": "找到浏览器数据目录: {path}", + "authentication_successful_getting_account_info": "认证成功, 获取账户信息...", + "warning_could_not_kill_existing_browser_processes": "警告: 无法杀死现有浏览器进程: {error}", + "browser_failed_to_start": "浏览器启动失败: {error}", + "browser_failed": "浏览器启动失败: {error}" } + } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 75b178e..ab97eed 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -20,7 +20,9 @@ "admin_required": "運行可執行文件,需要管理員權限", "admin_required_continue": "繼續使用當前版本...", "coming_soon": "即將推出", - "fixed_soon": "即將修復" + "fixed_soon": "即將修復", + "contribute": "貢獻項目", + "config": "顯示配置" }, "languages": { "en": "英文", @@ -102,7 +104,12 @@ "check_version_failed": "檢查版本失敗: {error}", "stack_trace": "堆疊跟踪", "version_too_low": "Cursor版本太低: {version} < 0.45.0", - "no_write_permission": "沒有寫入權限: {path}" + "no_write_permission": "沒有寫入權限: {path}", + "path_not_found": "路徑未找到: {path}", + "modify_file_failed": "修改文件失敗: {error}", + "windows_machine_id_updated": "Windows機器ID更新成功", + "update_windows_machine_id_failed": "更新Windows機器ID失敗: {error}", + "update_windows_machine_guid_failed": "更新Windows機器GUID失敗: {error}" }, "register": { @@ -247,7 +254,14 @@ "blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}", "available_domains_loaded": "獲取到 {count} 個可用域名", "domains_filtered": "過濾後剩餘 {count} 個可用域名", - "trying_to_create_email": "嘗試創建郵箱: {email}" + "trying_to_create_email": "嘗試創建郵箱: {email}", + "domain_blocked": "域名被屏蔽: {domain}", + "using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}", + "no_display_found": "未找到顯示器。確保 X 伺服器正在運行。", + "try_export_display": "嘗試: export DISPLAY=:0", + "extension_load_error": "加載插件失敗: {error}", + "make_sure_chrome_chromium_is_properly_installed": "確保 Chrome/Chromium 已正確安裝", + "try_install_chromium": "嘗試: sudo apt install chromium-browser" }, "update": { "title": "禁用 Cursor 自动更新", @@ -260,7 +274,14 @@ "removing_directory": "刪除目錄", "directory_removed": "目錄已刪除", "creating_block_file": "創建阻止文件", - "block_file_created": "阻止文件已創建" + "block_file_created": "阻止文件已創建", + "clearing_update_yml": "清空 update.yml 文件", + "update_yml_cleared": "update.yml 文件已清空", + "update_yml_not_found": "update.yml 文件未找到", + "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", + "unsupported_os": "不支持的操作系统: {system}", + "remove_directory_failed": "刪除目錄失败: {error}", + "create_block_file_failed": "創建阻止文件失败: {error}" }, "updater": { "checking": "檢查更新...", @@ -273,7 +294,8 @@ "update_skipped": "跳過更新。", "invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.", "development_version": "開發版本 {current} > {latest}", - "changelog_title": "更新日誌" + "changelog_title": "更新日誌", + "rate_limit_exceeded": "GitHub API 速率限制超過。跳過更新檢查。" }, "totally_reset": { "title": "完全重置 Cursor", @@ -413,5 +435,126 @@ "completed_successfully": "GitHub + Cursor 註冊成功", "registration_encountered_issues": "GitHub + Cursor 註冊遇到問題", "check_browser_windows_for_manual_intervention_or_try_again_later": "檢查瀏覽器視窗進行手動干預或稍後再試" + }, + "account_info": { + "subscription": "訂閱", + "trial_remaining": "剩餘試用", + "days": "天", + "subscription_not_found": "訂閱信息未找到", + "email_not_found": "郵箱未找到", + "failed_to_get_account": "獲取帳戶信息失敗", + "config_not_found": "配置未找到。", + "failed_to_get_usage": "獲取使用信息失敗", + "failed_to_get_subscription": "獲取訂閱信息失敗", + "failed_to_get_email": "獲取郵箱地址失敗", + "failed_to_get_token": "獲取 token 失敗", + "failed_to_get_account_info": "獲取帳戶信息失敗", + "title": "帳戶信息", + "email": "郵箱", + "token": "Token", + "usage": "使用量", + "subscription_type": "訂閱類型", + "remaining_trial": "剩餘試用", + "days_remaining": "剩餘天數", + "premium": "高級", + "pro": "專業", + "pro_trial": "專業試用", + "team": "團隊", + "enterprise": "企業", + "free": "免費", + "active": "活躍", + "inactive": "非活躍", + "premium_usage": "高級使用量", + "basic_usage": "基礎使用量", + "usage_not_found": "使用量未找到", + "lifetime_access_enabled": "永久訪問已啟用" + }, + "config": { + "config_not_available": "配置未找到。", + "configuration": "配置", + "enabled": "已啟用", + "disabled": "已禁用", + "config_directory": "配置目錄", + "neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目錄", + "please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "請確保 Cursor 已安裝並至少運行一次", + "storage_directory_not_found": "未找到儲存目錄", + "storage_file_found": "找到儲存文件", + "file_size": "文件大小", + "file_permissions": "文件權限", + "file_owner": "文件所有者", + "file_group": "文件組", + "error_getting_file_stats": "獲取文件統計信息時出錯", + "permission_denied": "權限拒絕", + "try_running": "嘗試運行: {command}", + "and": "和", + "storage_file_is_empty": "儲存文件為空", + "the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已損壞,請重新安裝 Cursor", + "storage_file_not_found": "未找到儲存文件", + "error_checking_linux_paths": "檢查 Linux 路徑時出錯", + "config_option_added": "添加配置選項", + "config_updated": "配置更新", + "config_created": "配置已創建", + "config_setup_error": "配置設置錯誤", + "storage_file_is_valid_and_contains_data": "儲存文件有效且包含數據", + "error_reading_storage_file": "讀取儲存文件時出錯", + "also_checked": "也檢查了 {path}" + + }, + "oauth": { + "authentication_button_not_found": "未找到認證按鈕", + "authentication_failed": "認證失敗: {error}", + "found_cookies": "找到 {count} 個 Cookie", + "token_extraction_error": "Token 提取錯誤: {error}", + "authentication_successful": "認證成功 - 郵箱: {email}", + "missing_authentication_data": "缺少認證數據: {data}", + "failed_to_delete_account": "刪除帳戶失敗: {error}", + "invalid_authentication_type": "無效的認證類型", + "auth_update_success": "認證更新成功", + "browser_closed": "瀏覽器已關閉", + "auth_update_failed": "認證更新失敗", + "google_start": "Google 開始", + "github_start": "Github 開始", + "usage_count": "使用量: {usage}", + "account_has_reached_maximum_usage": "帳戶已達到最大使用量, {deleting}", + "starting_new_authentication_process": "開始新的認證過程...", + "failed_to_delete_expired_account": "刪除過期帳戶失敗", + "could_not_check_usage_count": "無法檢查使用量: {error}", + "found_email": "找到郵箱: {email}", + "could_not_find_email": "未找到郵箱: {error}", + "could_not_find_usage_count": "未找到使用量: {erro r}", + "already_on_settings_page": "已處於設置頁面", + "failed_to_extract_auth_info": "提取認證信息失敗: {error}", + "no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默認配置文件", + "found_default_chrome_profile": "找到默認 Chrome 配置文件", + "using_first_available_chrome_profile": "使用第一個可用的 Chrome 配置文件: {profile}", + "error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默認配置文件: {error}", + "initializing_browser_setup": "初始化瀏覽器設置...", + "detected_platform": "檢測平台: {platform}", + "running_as_root_warning": "以 root 運行不推薦用於瀏覽器自動化", + "consider_running_without_sudo": "考慮不使用 sudo 運行腳本", + "no_compatible_browser_found": "未找到兼容的瀏覽器。請安裝 Google Chrome 或 Chromium。", + "supported_browsers": "支持的瀏覽器: {platform}", + "using_browser_profile": "使用瀏覽器配置文件: {profile}", + "starting_browser": "正在啟動瀏覽器: {path}", + "browser_setup_completed": "瀏覽器設置完成成功", + "browser_setup_failed": "瀏覽器設置失敗: {error}", + "try_running_without_sudo_admin": "嘗試不使用 sudo/管理員權限運行", + "redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...", + "starting_github_authentication": "開始 Github 認證...", + "waiting_for_authentication": "等待認證...", + "page_changed_checking_auth": "頁面改變, 檢查認證...", + "status_check_error": "狀態檢查錯誤: {error}", + "authentication_timeout": "認證超時", + "account_is_still_valid": "帳戶仍然有效 (使用量: {usage})", + "starting_re_authentication_process": "開始重新認證過程...", + "starting_new_google_authentication": "開始新的 Google 認證...", + "failed_to_delete_account_or_re_authenticate": "刪除帳戶或重新認證失敗: {error}", + "navigating_to_authentication_page": "正在導航到認證頁面...", + "please_select_your_google_account_to_continue": "請選擇您的 Google 帳戶以繼續...", + "found_browser_data_directory": "找到瀏覽器數據目錄: {path}", + "authentication_successful_getting_account_info": "認證成功, 獲取帳戶信息...", + "warning_could_not_kill_existing_browser_processes": "警告: 無法殺死現有瀏覽器進程: {error}", + "browser_failed_to_start": "瀏覽器啟動失敗: {error}", + "browser_failed": "瀏覽器啟動失敗: {error}" } } \ No newline at end of file diff --git a/logo.py b/logo.py index 7a0faa6..f054571 100644 --- a/logo.py +++ b/logo.py @@ -20,7 +20,7 @@ init() # get terminal width def get_terminal_width(): try: - columns, _ = shutil.get_terminal_size() + columns, _ = shutil.get_terminal_size()/2 return columns except: return 80 # default width @@ -34,7 +34,7 @@ def center_multiline_text(text, handle_chinese=False): for line in lines: # calculate actual display width (remove ANSI color codes) clean_line = line - for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Style.RESET_ALL]: + for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]: clean_line = clean_line.replace(color, '') # remove all ANSI escape sequences to get the actual length @@ -83,7 +83,7 @@ muhammedfurkan plamkatawe """ OTHER_INFO_TEXT = f"""{Fore.YELLOW} Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED} -Press 7 to change language | 按下 7 键切换语言{Style.RESET_ALL}""" +Press 8 to change language | 按下 8 键切换语言{Style.RESET_ALL}""" # center display LOGO and DESCRIPTION CURSOR_LOGO = center_multiline_text(LOGO_TEXT, handle_chinese=False) @@ -94,8 +94,8 @@ CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True) def print_logo(): print(CURSOR_LOGO) print(CURSOR_DESCRIPTION) - print(CURSOR_CONTRIBUTORS) + # print(CURSOR_CONTRIBUTORS) print(CURSOR_OTHER_INFO) if __name__ == "__main__": - print_logo() \ No newline at end of file + print_logo() diff --git a/main.py b/main.py index 9cee1f3..ba7ca89 100644 --- a/main.py +++ b/main.py @@ -9,12 +9,14 @@ import locale import platform import requests import subprocess -from config import get_config +from config import get_config +import shutil +import re # Only import windll on Windows systems if platform.system() == 'Windows': import ctypes - # 只在 Windows 上导入 windll + # Only import windll on Windows systems from ctypes import windll # Initialize colorama @@ -32,7 +34,13 @@ EMOJI = { "ARROW": "➜", "LANG": "🌐", "UPDATE": "🔄", - "ADMIN": "🔐" + "ADMIN": "🔐", + "AIRDROP": "💰", + "ROCKET": "🚀", + "STAR": "⭐", + "SUN": "🌟", + "CONTRIBUTE": "🤝", + "SETTINGS": "⚙️" } # Function to check if running as frozen executable @@ -242,22 +250,111 @@ translator = Translator() def print_menu(): """Print menu options""" + try: + config = get_config() + if config.getboolean('Utils', 'enabled_account_info'): + import cursor_acc_info + cursor_acc_info.display_account_info(translator) + except Exception as e: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.account_info_error', error=str(e))}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}") - print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}") - print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}") - print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})") - print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}") - print(f"{Fore.YELLOW} ┗━━ 🔥 {translator.get('menu.lifetime_access_enabled')} 🔥{Style.RESET_ALL}") - print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}") - print(f"{Fore.YELLOW} ┗━━ 🚀 {translator.get('menu.lifetime_access_enabled')} 🚀{Style.RESET_ALL}") - print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}") - print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}") - print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}") - print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}") - print(f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}") - print(f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}") - print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}") + if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw': + print(f"{Fore.YELLOW}{'─' * 70}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{'─' * 110}{Style.RESET_ALL}") + + # Get terminal width + try: + terminal_width = shutil.get_terminal_size().columns + except: + terminal_width = 80 # Default width + + # Define all menu items + menu_items = { + 0: f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}", + 1: f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}", + 2: f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})", + 3: f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUN']} {translator.get('menu.register_google')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})", + 4: f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['STAR']} {translator.get('menu.register_github')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})", + 5: f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}", + 6: f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}", + 7: f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}", + 8: f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}", + 9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}", + 10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}", + 11: f"{Fore.GREEN}11{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}", + 12: f"{Fore.GREEN}12{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}" + } + + # Automatically calculate the number of menu items in the left and right columns + total_items = len(menu_items) + left_column_count = (total_items + 1) // 2 # The number of options displayed on the left (rounded up) + + # Build left and right columns of menus + sorted_indices = sorted(menu_items.keys()) + left_menu = [menu_items[i] for i in sorted_indices[:left_column_count]] + right_menu = [menu_items[i] for i in sorted_indices[left_column_count:]] + + # Calculate the maximum display width of left menu items + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + + def get_display_width(s): + """Calculate the display width of a string, considering Chinese characters and emojis""" + # Remove ANSI color codes + clean_s = ansi_escape.sub('', s) + width = 0 + for c in clean_s: + # Chinese characters and some emojis occupy two character widths + if ord(c) > 127: + width += 2 + else: + width += 1 + return width + + max_left_width = 0 + for item in left_menu: + width = get_display_width(item) + max_left_width = max(max_left_width, width) + + # Set the starting position of right menu + fixed_spacing = 4 # Fixed spacing + right_start = max_left_width + fixed_spacing + + # Calculate the number of spaces needed for right menu items + spaces_list = [] + for i in range(len(left_menu)): + if i < len(left_menu): + left_item = left_menu[i] + left_width = get_display_width(left_item) + spaces = right_start - left_width + spaces_list.append(spaces) + + # Print menu items + max_rows = max(len(left_menu), len(right_menu)) + + for i in range(max_rows): + # Print left menu items + if i < len(left_menu): + left_item = left_menu[i] + print(left_item, end='') + + # Use pre-calculated spaces + spaces = spaces_list[i] + else: + # If left side has no items, print only spaces + spaces = right_start + print('', end='') + + # Print right menu items + if i < len(right_menu): + print(' ' * spaces + right_menu[i]) + else: + print() # Change line + if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw': + print(f"{Fore.YELLOW}{'─' * 70}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{'─' * 110}{Style.RESET_ALL}") def select_language(): """Language selection menu""" @@ -297,6 +394,11 @@ def check_latest_version(): timeout=10 ) + # Check if rate limit exceeded + if response.status_code == 403 and "rate limit exceeded" in response.text.lower(): + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.rate_limit_exceeded', fallback='GitHub API rate limit exceeded. Skipping update check.')}{Style.RESET_ALL}") + return + # Check if response is successful if response.status_code != 200: raise Exception(f"GitHub API returned status code {response.status_code}") @@ -448,12 +550,14 @@ def main(): print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}") return - check_latest_version() # Add version check before showing menu + if config.getboolean('Utils', 'enabled_update_check'): + check_latest_version() # Add version check before showing menu print_menu() while True: try: - choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-10')}: {Style.RESET_ALL}") + choice_num = 12 + choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}") if choice == "0": print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}") @@ -498,8 +602,16 @@ def main(): print_menu() elif choice == "10": import totally_reset_cursor - # totally_reset_cursor.main(translator) - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}") + totally_reset_cursor.run(translator) + # print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}") + print_menu() + elif choice == "11": + import logo + print(logo.CURSOR_CONTRIBUTORS) + print_menu() + elif choice == "12": + from config import print_config + print_config(get_config(), translator) print_menu() else: print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}") diff --git a/new_signup.py b/new_signup.py index d78a637..b67fefa 100644 --- a/new_signup.py +++ b/new_signup.py @@ -203,9 +203,10 @@ def setup_driver(translator=None): # Use incognito mode co.set_argument("--incognito") - # Set random port - co.set_argument("--no-sandbox") - + if sys.platform == "linux": + # Set random port + co.set_argument("--no-sandbox") + # Set random port co.auto_port() diff --git a/new_tempemail.py b/new_tempemail.py index 419ff2a..178f24f 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -106,7 +106,31 @@ class NewTempEmail: # 创建浏览器选项 co = ChromiumOptions() - co.set_argument("--headless=new") + + # Only use headless for non-OAuth operations + if not hasattr(self, 'auth_type') or self.auth_type != 'oauth': + co.set_argument("--headless=new") + + if sys.platform == "linux": + # Check if DISPLAY is set when not in headless mode + if not co.arguments.get("--headless=new") and not os.environ.get('DISPLAY'): + print(f"{Fore.RED}❌ {self.translator.get('email.no_display_found') if self.translator else 'No display found. Make sure X server is running.'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}ℹ️ {self.translator.get('email.try_export_display') if self.translator else 'Try: export DISPLAY=:0'}{Style.RESET_ALL}") + return False + + co.set_argument("--no-sandbox") + co.set_argument("--disable-dev-shm-usage") + co.set_argument("--disable-gpu") + + # If running as root, try to use actual user's Chrome profile + if os.geteuid() == 0: + sudo_user = os.environ.get('SUDO_USER') + if sudo_user: + actual_home = f"/home/{sudo_user}" + user_data_dir = os.path.join(actual_home, ".config", "google-chrome") + if os.path.exists(user_data_dir): + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.using_chrome_profile', user_data_dir=user_data_dir) if self.translator else f'Using Chrome profile from: {user_data_dir}'}{Style.RESET_ALL}") + co.set_argument(f"--user-data-dir={user_data_dir}") co.auto_port() # 自动设置端口 @@ -128,6 +152,10 @@ class NewTempEmail: print(f"{Fore.RED}❌ {self.translator.get('email.browser_start_error')}: {str(e)}{Style.RESET_ALL}") else: print(f"{Fore.RED}❌ 启动浏览器失败: {str(e)}{Style.RESET_ALL}") + + if sys.platform == "linux": + print(f"{Fore.YELLOW}ℹ️ {self.translator.get('email.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}ℹ️ {self.translator.get('email.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}") return False def create_email(self): diff --git a/oauth_auth.py b/oauth_auth.py index f166a35..5da5b16 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -21,13 +21,15 @@ EMOJI = { 'SUCCESS': '✅', 'ERROR': '❌', 'WAIT': '⏳', - 'INFO': 'ℹ️' + 'INFO': 'ℹ️', + 'WARNING': '⚠️' } class OAuthHandler: - def __init__(self, translator=None): + def __init__(self, translator=None, auth_type=None): self.translator = translator self.config = get_config(translator) + self.auth_type = auth_type # make sure the auth_type is not None os.environ['BROWSER_HEADLESS'] = 'False' self.browser = None @@ -41,12 +43,12 @@ class OAuthHandler: profiles.append(item) if not profiles: - print(f"{Fore.YELLOW}{EMOJI['INFO']} No Chrome profiles found, using Default{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.no_chrome_profiles_found') if self.translator else 'No Chrome profiles found, using Default'}{Style.RESET_ALL}") return 'Default' # First check if Default profile exists if 'Default' in profiles: - print(f"{Fore.CYAN}{EMOJI['INFO']} Found Default Chrome profile{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_default_chrome_profile') if self.translator else 'Found Default Chrome profile'}{Style.RESET_ALL}") return 'Default' # If no Default profile, check Local State for last used profile @@ -68,21 +70,35 @@ class OAuthHandler: return profile # If no profile found in Local State, use the first available profile - print(f"{Fore.CYAN}{EMOJI['INFO']} Using first available Chrome profile: {profiles[0]}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_first_available_chrome_profile', profile=profiles[0]) if self.translator else f'Using first available Chrome profile: {profiles[0]}'}{Style.RESET_ALL}") return profiles[0] except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.error_finding_chrome_profile', error=str(e)) if self.translator else f'Error finding Chrome profile, using Default: {str(e)}'}{Style.RESET_ALL}") return 'Default' def setup_browser(self): """Setup browser for OAuth flow using active profile""" try: - print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.initializing_browser_setup') if self.translator else 'Initializing browser setup...'}{Style.RESET_ALL}") # Platform-specific initialization platform_name = platform.system().lower() - print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_platform', platform=platform_name) if self.translator else f'Detected platform: {platform_name}'}{Style.RESET_ALL}") + + # Linux-specific checks + if platform_name == 'linux': + # Check if DISPLAY is set + display = os.environ.get('DISPLAY') + if not display: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.no_display_found') if self.translator else 'No display found. Make sure X server is running.'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_export_display') if self.translator else 'Try: export DISPLAY=:0'}{Style.RESET_ALL}") + return False + + # Check if running as root + if os.geteuid() == 0: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('oauth.running_as_root_warning') if self.translator else 'Running as root is not recommended for browser automation'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.consider_running_without_sudo') if self.translator else 'Consider running the script without sudo'}{Style.RESET_ALL}") # Kill existing browser processes self._kill_browser_processes() @@ -92,34 +108,64 @@ class OAuthHandler: chrome_path = self._get_browser_path() if not chrome_path: - raise Exception(f"No compatible browser found. Please install Google Chrome or Chromium.\nSupported browsers for {platform_name}:\n" + + raise Exception(f"{self.translator.get('oauth.no_compatible_browser_found') if self.translator else 'No compatible browser found. Please install Google Chrome or Chromium.'}\n{self.translator.get('oauth.supported_browsers', platform=platform_name)}\n" + "- Windows: Google Chrome, Chromium\n" + "- macOS: Google Chrome, Chromium\n" + "- Linux: Google Chrome, Chromium, chromium-browser") # Get active profile active_profile = self._get_active_profile(user_data_dir) - print(f"{Fore.CYAN}{EMOJI['INFO']} Using browser profile: {active_profile}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_browser_profile', profile=active_profile) if self.translator else f'Using browser profile: {active_profile}'}{Style.RESET_ALL}") # Configure browser options - co = self._configure_browser_options(chrome_path, user_data_dir, active_profile) + co = ChromiumOptions() - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}") + # Never use headless mode for OAuth flows + co.headless(False) + + # Platform-specific options + if os.name == 'linux': + co.set_argument('--no-sandbox') + co.set_argument('--disable-dev-shm-usage') + co.set_argument('--disable-gpu') + + # If running as root, try to use actual user's Chrome profile + if os.geteuid() == 0: + sudo_user = os.environ.get('SUDO_USER') + if sudo_user: + actual_home = f"/home/{sudo_user}" + user_data_dir = os.path.join(actual_home, ".config", "google-chrome") + if os.path.exists(user_data_dir): + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.using_chrome_profile_from', user_data_dir=user_data_dir) if self.translator else f'Using Chrome profile from: {user_data_dir}'}{Style.RESET_ALL}") + co.set_argument(f"--user-data-dir={user_data_dir}") + + # Set paths and profile + co.set_paths(browser_path=chrome_path, user_data_path=user_data_dir) + co.set_argument(f'--profile-directory={active_profile}') + + # Basic options + co.set_argument('--no-first-run') + co.set_argument('--no-default-browser-check') + # co.auto_port() + + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_browser', path=chrome_path) if self.translator else f'Starting browser at: {chrome_path}'}{Style.RESET_ALL}") self.browser = ChromiumPage(co) # Verify browser launched successfully if not self.browser: raise Exception("Failed to initialize browser instance") - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.browser_setup_completed') if self.translator else 'Browser setup completed successfully'}{Style.RESET_ALL}") return True except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Browser setup failed: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_setup_failed', error=str(e)) if self.translator else f'Browser setup failed: {str(e)}'}{Style.RESET_ALL}") if "DevToolsActivePort file doesn't exist" in str(e): - print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running with administrator/root privileges{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_running_without_sudo_admin') if self.translator else 'Try running without sudo/administrator privileges'}{Style.RESET_ALL}") elif "Chrome failed to start" in str(e): - print(f"{Fore.YELLOW}{EMOJI['INFO']} Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}") + if platform_name == 'linux': + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}") return False def _kill_browser_processes(self): @@ -136,7 +182,7 @@ class OAuthHandler: time.sleep(1) # Wait for processes to close except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Warning: Could not kill existing browser processes: {e}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.warning_could_not_kill_existing_browser_processes', error=str(e)) if self.translator else f'Warning: Could not kill existing browser processes: {e}'}{Style.RESET_ALL}") def _get_user_data_directory(self): """Get the appropriate user data directory based on platform""" @@ -162,17 +208,17 @@ class OAuthHandler: # Try each possible path for path in possible_paths: if os.path.exists(path): - print(f"{Fore.CYAN}{EMOJI['INFO']} Found browser data directory: {path}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_browser_data_directory', path=path) if self.translator else f'Found browser data directory: {path}'}{Style.RESET_ALL}") return path # Create temporary profile if no existing profile found temp_profile = os.path.join(os.path.expanduser('~'), '.cursor_temp_profile') - print(f"{Fore.YELLOW}{EMOJI['INFO']} Creating temporary profile at: {temp_profile}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.creating_temporary_profile', path=temp_profile) if self.translator else f'Creating temporary profile at: {temp_profile}'}{Style.RESET_ALL}") os.makedirs(temp_profile, exist_ok=True) return temp_profile except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error getting user data directory: {e}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_getting_user_data_directory', error=str(e)) if self.translator else f'Error getting user data directory: {e}'}{Style.RESET_ALL}") raise def _get_browser_path(self): @@ -183,7 +229,7 @@ class OAuthHandler: if chrome_path and os.path.exists(chrome_path): return chrome_path - print(f"{Fore.YELLOW}{EMOJI['INFO']} Searching for alternative browser installations...{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.searching_for_alternative_browser_installations') if self.translator else 'Searching for alternative browser installations...'}{Style.RESET_ALL}") # Platform-specific paths if os.name == 'nt': # Windows @@ -215,13 +261,13 @@ class OAuthHandler: for path in alt_paths: expanded_path = os.path.expanduser(path) if os.path.exists(expanded_path): - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found browser at: {expanded_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.found_browser_at', path=expanded_path) if self.translator else f'Found browser at: {expanded_path}'}{Style.RESET_ALL}") return expanded_path return None except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error finding browser path: {e}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_finding_browser_path', error=str(e)) if self.translator else f'Error finding browser path: {e}'}{Style.RESET_ALL}") return None def _configure_browser_options(self, chrome_path, user_data_dir, active_profile): @@ -250,22 +296,22 @@ class OAuthHandler: return co except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_configuring_browser_options', error=str(e)) if self.translator else f'Error configuring browser options: {e}'}{Style.RESET_ALL}") raise def handle_google_auth(self): """Handle Google OAuth authentication""" try: - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start') if self.translator else 'Starting Google OAuth authentication...'}{Style.RESET_ALL}") # Setup browser if not self.setup_browser(): - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed') if self.translator else 'Browser failed to initialize'}{Style.RESET_ALL}") return False, None # Navigate to auth URL try: - print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else 'Navigating to authentication page...'}{Style.RESET_ALL}") self.browser.get("https://authenticator.cursor.sh/sign-up") time.sleep(get_random_wait_time(self.config, 'page_load_wait')) @@ -289,16 +335,17 @@ class OAuthHandler: raise Exception("Could not find Google authentication button") # Click the button and wait for page load - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting Google authentication...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_google_authentication') if self.translator else 'Starting Google authentication...'}{Style.RESET_ALL}") auth_btn.click() time.sleep(get_random_wait_time(self.config, 'page_load_wait')) # Check if we're on account selection page if "accounts.google.com" in self.browser.url: - print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account to continue...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue...'}{Style.RESET_ALL}") + alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication' try: - self.browser.run_js(""" - alert('Please select your Google account to continue with Cursor authentication'); + self.browser.run_js(f""" + alert('{alert_message}'); """) except: pass # Alert is optional @@ -306,14 +353,14 @@ class OAuthHandler: # Wait for authentication to complete auth_info = self._wait_for_auth() if not auth_info: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else 'Timeout'}{Style.RESET_ALL}") return False, None - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success') if self.translator else 'Success'}{Style.RESET_ALL}") return True, auth_info except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}") return False, None finally: try: @@ -333,7 +380,7 @@ class OAuthHandler: start_time = time.time() check_interval = 2 # Check every 2 seconds - print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for authentication (timeout: 5 minutes)...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.waiting_for_authentication', timeout='5 minutes') if self.translator else 'Waiting for authentication (timeout: 5 minutes)'}{Style.RESET_ALL}") while time.time() - start_time < max_wait: try: @@ -352,7 +399,7 @@ class OAuthHandler: if token: # Get email from settings page - print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.authentication_successful_getting_account_info') if self.translator else 'Authentication successful, getting account info...'}{Style.RESET_ALL}") self.browser.get("https://www.cursor.com/settings") time.sleep(3) @@ -361,7 +408,7 @@ class OAuthHandler: email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)") if email_element: email = email_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {email}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=email) if self.translator else f'Found email: {email}'}{Style.RESET_ALL}") except: email = "user@cursor.sh" # Fallback email @@ -370,42 +417,42 @@ class OAuthHandler: usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)") if usage_element: usage_text = usage_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}") - # Check if account is expired - if usage_text.strip() == "150 / 150": - print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, creating new account...{Style.RESET_ALL}") + # Check if account is expired (both 150/150 and 50/50 cases) + if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50": + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', creating_new_account='creating new account') if self.translator else 'Account has reached maximum usage, creating new account...'}{Style.RESET_ALL}") # Delete current account if self._delete_current_account(): # Start new authentication based on auth type - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}") - if auth_type == "google": + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_authentication_process') if self.translator else 'Starting new authentication process...'}{Style.RESET_ALL}") + if self.auth_type == "google": return self.handle_google_auth() else: # github return self.handle_github_auth() else: - print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_expired_account') if self.translator else 'Failed to delete expired account'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_check_usage_count', error=str(e)) if self.translator else f'Could not check usage count: {str(e)}'}{Style.RESET_ALL}") return {"email": email, "token": token} # Also check URL as backup if "cursor.com/settings" in self.browser.url: - print(f"{Fore.CYAN}{EMOJI['INFO']} Detected successful login{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_successful_login') if self.translator else 'Detected successful login'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication', error=str(e)) if self.translator else f'Waiting for authentication... ({str(e)})'}{Style.RESET_ALL}") time.sleep(check_interval) - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else 'Authentication timeout'}{Style.RESET_ALL}") return None except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error while waiting for authentication: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_waiting_for_authentication', error=str(e)) if self.translator else f'Error while waiting for authentication: {str(e)}'}{Style.RESET_ALL}") return None def handle_github_auth(self): @@ -420,7 +467,7 @@ class OAuthHandler: # Navigate to auth URL try: - print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else 'Navigating to authentication page...'}{Style.RESET_ALL}") self.browser.get("https://authenticator.cursor.sh/sign-up") time.sleep(get_random_wait_time(self.config, 'page_load_wait')) @@ -444,21 +491,21 @@ class OAuthHandler: raise Exception("Could not find GitHub authentication button") # Click the button and wait for page load - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting GitHub authentication...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_github_authentication') if self.translator else 'Starting GitHub authentication...'}{Style.RESET_ALL}") auth_btn.click() time.sleep(get_random_wait_time(self.config, 'page_load_wait')) # Wait for authentication to complete auth_info = self._wait_for_auth() if not auth_info: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else 'Timeout'}{Style.RESET_ALL}") return False, None print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}") return True, auth_info except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}") return False, None finally: try: @@ -520,23 +567,23 @@ class OAuthHandler: # Check if we're on account selection page if auth_type == "google" and "accounts.google.com" in self.browser.url: - alert_js = """ - alert('Please select your Google account manually to continue with Cursor authentication'); - """ + alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication' try: - self.browser.run_js(alert_js) + self.browser.run_js(f""" + alert('{alert_message}'); + """) except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Alert display failed: {str(e)}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account manually to continue with Cursor authentication...{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.alert_display_failed', error=str(e)) if self.translator else f'Alert display failed: {str(e)}'}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_manually_to_continue_with_cursor_authentication') if self.translator else 'Please select your Google account manually to continue with Cursor authentication...'}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} Waiting for authentication to complete...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication_to_complete') if self.translator else 'Waiting for authentication to complete...'}{Style.RESET_ALL}") # Wait for authentication to complete max_wait = 300 # 5 minutes start_time = time.time() last_url = self.browser.url - print(f"{Fore.CYAN}{EMOJI['WAIT']} Checking authentication status...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.checking_authentication_status') if self.translator else 'Checking authentication status...'}{Style.RESET_ALL}") while time.time() - start_time < max_wait: try: @@ -552,9 +599,9 @@ class OAuthHandler: token = value.split("%3A%3A")[-1] if token: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful') if self.translator else 'Authentication successful!'}{Style.RESET_ALL}") # Navigate to settings page - print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_settings_page') if self.translator else 'Navigating to settings page...'}{Style.RESET_ALL}") self.browser.get("https://www.cursor.com/settings") time.sleep(3) # Wait for settings page to load @@ -563,9 +610,9 @@ class OAuthHandler: email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)") if email_element: actual_email = email_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'Found email: {actual_email}'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}") actual_email = "user@cursor.sh" # Check usage count @@ -573,11 +620,11 @@ class OAuthHandler: usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)") if usage_element: usage_text = usage_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}") - # Check if account is expired - if usage_text.strip() == "150 / 150": - print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}") + # Check if account is expired (both 150/150 and 50/50 cases) + if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50": + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}") delete_js = """ function deleteAccount() { @@ -609,23 +656,23 @@ class OAuthHandler: print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}") # Navigate back to auth page and repeat authentication - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_re_authentication_process') if self.translator else 'Starting re-authentication process...'}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}") # Explicitly navigate to the authentication page self.browser.get("https://authenticator.cursor.sh/sign-up") time.sleep(get_random_wait_time(self.config, 'page_load_wait')) # Call handle_google_auth again to repeat the entire process - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_google_authentication') if self.translator else 'Starting new Google authentication...'}{Style.RESET_ALL}") return self.handle_google_auth() except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account_or_re_authenticate', error=str(e)) if self.translator else f'Failed to delete account or re-authenticate: {str(e)}'}{Style.RESET_ALL}") else: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'Account is still valid (Usage: {usage_text})'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_usage_count', error=str(e)) if self.translator else f'Could not find usage count: {str(e)}'}{Style.RESET_ALL}") # Remove the browser stay open prompt and input wait return True, {"email": actual_email, "token": token} @@ -633,7 +680,7 @@ class OAuthHandler: # Also check URL as backup current_url = self.browser.url if "cursor.com/settings" in current_url: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Already on settings page!{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.already_on_settings_page') if self.translator else 'Already on settings page!'}{Style.RESET_ALL}") time.sleep(1) cookies = self.browser.cookies() for cookie in cookies: @@ -649,9 +696,9 @@ class OAuthHandler: email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)") if email_element: actual_email = email_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'Found email: {actual_email}'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}") actual_email = "user@cursor.sh" # Check usage count @@ -659,11 +706,11 @@ class OAuthHandler: usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)") if usage_element: usage_text = usage_element.text - print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'Usage count: {usage_text}'}{Style.RESET_ALL}") - # Check if account is expired - if usage_text.strip() == "150 / 150": - print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}") + # Check if account is expired (both 150/150 and 50/50 cases) + if usage_text.strip() == "150 / 150" or usage_text.strip() == "50 / 50": + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}") delete_js = """ function deleteAccount() { @@ -695,44 +742,44 @@ class OAuthHandler: print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}") # Navigate back to auth page and repeat authentication - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_re_authentication_process') if self.translator else 'Starting re-authentication process...'}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}") # Explicitly navigate to the authentication page self.browser.get("https://authenticator.cursor.sh/sign-up") time.sleep(get_random_wait_time(self.config, 'page_load_wait')) # Call handle_google_auth again to repeat the entire process - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_google_authentication') if self.translator else 'Starting new Google authentication...'}{Style.RESET_ALL}") return self.handle_google_auth() except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account_or_re_authenticate', error=str(e)) if self.translator else f'Failed to delete account or re-authenticate: {str(e)}'}{Style.RESET_ALL}") else: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'Account is still valid (Usage: {usage_text})'}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_usage_count', error=str(e)) if self.translator else f'Could not find usage count: {str(e)}'}{Style.RESET_ALL}") # Remove the browser stay open prompt and input wait return True, {"email": actual_email, "token": token} elif current_url != last_url: - print(f"{Fore.CYAN}{EMOJI['INFO']} Page changed, checking auth...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.page_changed_checking_auth') if self.translator else 'Page changed, checking auth...'}{Style.RESET_ALL}") last_url = current_url time.sleep(get_random_wait_time(self.config, 'page_load_wait')) except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Status check error: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.status_check_error', error=str(e)) if self.translator else f'Status check error: {str(e)}'}{Style.RESET_ALL}") time.sleep(1) continue time.sleep(1) - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else 'Authentication timeout'}{Style.RESET_ALL}") return False, None - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_button_not_found') if self.translator else 'Authentication button not found'}{Style.RESET_ALL}") return False, None except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Authentication failed: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_failed', error=str(e)) if self.translator else f'Authentication failed: {str(e)}'}{Style.RESET_ALL}") return False, None finally: if self.browser: @@ -755,7 +802,7 @@ class OAuthHandler: time.sleep(1) # Debug cookie information - print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_cookies', count=len(cookies)) if self.translator else f'Found {len(cookies)} cookies'}{Style.RESET_ALL}") email = None token = None @@ -770,12 +817,12 @@ class OAuthHandler: elif "%3A%3A" in value: token = value.split("%3A%3A")[-1] except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['INFO']} Token extraction error: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.token_extraction_error', error=str(e)) if self.translator else f'Token extraction error: {str(e)}'}{Style.RESET_ALL}") elif name == "cursor_email": email = cookie.get("value") if email and token: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful - Email: {email}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful', email=email) if self.translator else f'Authentication successful - Email: {email}'}{Style.RESET_ALL}") return True, {"email": email, "token": token} else: missing = [] @@ -783,11 +830,11 @@ class OAuthHandler: missing.append("email") if not token: missing.append("token") - print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.missing_authentication_data', data=', '.join(missing)) if self.translator else f'Missing authentication data: {", ".join(missing)}'}{Style.RESET_ALL}") return False, None except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Failed to extract auth info: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_extract_auth_info', error=str(e)) if self.translator else f'Failed to extract auth info: {str(e)}'}{Style.RESET_ALL}") return False, None def _delete_current_account(self): @@ -822,14 +869,14 @@ class OAuthHandler: print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}") # Navigate back to auth page - print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}") self.browser.get("https://authenticator.cursor.sh/sign-up") time.sleep(get_random_wait_time(self.config, 'page_load_wait')) return True except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account', error=str(e)) if self.translator else f'Failed to delete account: {str(e)}'}{Style.RESET_ALL}") return False def main(auth_type, translator=None): @@ -839,16 +886,16 @@ def main(auth_type, translator=None): auth_type (str): Type of authentication ('google' or 'github') translator: Translator instance for internationalization """ - handler = OAuthHandler(translator) + handler = OAuthHandler(translator, auth_type) if auth_type.lower() == 'google': - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start') if translator else 'Google start'}{Style.RESET_ALL}") success, auth_info = handler.handle_google_auth() elif auth_type.lower() == 'github': - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start') if translator else 'Github start'}{Style.RESET_ALL}") success, auth_info = handler.handle_github_auth() else: - print(f"{Fore.RED}{EMOJI['ERROR']} Invalid authentication type{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.invalid_authentication_type') if translator else 'Invalid authentication type'}{Style.RESET_ALL}") return False if success and auth_info: @@ -859,13 +906,13 @@ def main(auth_type, translator=None): access_token=auth_info["token"], refresh_token=auth_info["token"] ): - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success')}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success') if translator else 'Auth update success'}{Style.RESET_ALL}") # Close the browser after successful authentication if handler.browser: handler.browser.quit() - print(f"{Fore.CYAN}{EMOJI['INFO']} Browser closed{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.browser_closed') if translator else 'Browser closed'}{Style.RESET_ALL}") return True else: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed') if translator else 'Auth update failed'}{Style.RESET_ALL}") return False \ No newline at end of file diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 975c6bd..a10f862 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -26,6 +26,7 @@ EMOJI = { "ERROR": "❌", "INFO": "ℹ️", "RESET": "🔄", + "WARNING": "⚠️", } def get_cursor_paths(translator=None) -> Tuple[str, str]: @@ -37,10 +38,41 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]: config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") config_file = os.path.join(config_dir, "config.ini") + # 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"), "/usr/lib/cursor/app/"] + } + + # If config doesn't exist, create it with default paths if not os.path.exists(config_file): - raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件") + for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']: + if not config.has_section(section): + config.add_section(section) - config.read(config_file, encoding='utf-8') # Specify encoding + 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]) + + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + else: + config.read(config_file, encoding='utf-8') # Get path based on system if system == "Darwin": @@ -51,15 +83,26 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]: 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") @@ -127,7 +170,7 @@ def get_workbench_cursor_path(translator=None) -> str: "main": "out/vs/workbench/workbench.desktop.main.js" }, "Linux": { - "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], + "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", "/usr/lib/cursor/app/"], "main": "out/vs/workbench/workbench.desktop.main.js" } } @@ -256,11 +299,11 @@ def modify_workbench_js(file_path: str, translator=None) -> bool: 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)' + 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)' 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)' + 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)' 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)' @@ -534,6 +577,7 @@ class MachineIDResetter: if sys.platform.startswith("win"): self._update_windows_machine_guid() + self._update_windows_machine_id() elif sys.platform == "darwin": self._update_macos_platform_uuid(new_ids) @@ -563,6 +607,45 @@ class MachineIDResetter: except Exception as e: print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}") raise + + def _update_windows_machine_id(self): + """Update Windows MachineId in SQMClient registry""" + try: + import winreg + # 1. Generate new GUID + new_guid = "{" + str(uuid.uuid4()).upper() + "}" + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}") + + # 2. Open the registry key + try: + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient", + 0, + winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY + ) + except FileNotFoundError: + # If the key does not exist, create it + key = winreg.CreateKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient" + ) + + # 3. Set MachineId value + winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid) + winreg.CloseKey(key) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}") + return True + + except PermissionError: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}") + return False + def _update_macos_platform_uuid(self, new_ids): """Update macOS Platform UUID""" @@ -621,13 +704,10 @@ class MachineIDResetter: self.update_system_ids(new_ids) - ### Remove In v1.7.02 # Modify workbench.desktop.main.js - - # workbench_path = get_workbench_cursor_path(self.translator) - # modify_workbench_js(workbench_path, self.translator) + workbench_path = get_workbench_cursor_path(self.translator) + modify_workbench_js(workbench_path, self.translator) - ### Remove In v1.7.02 # Check Cursor version and perform corresponding actions greater_than_0_45 = check_cursor_version(self.translator) @@ -706,4 +786,4 @@ def run(translator=None): if __name__ == "__main__": from main import translator as main_translator - run(main_translator) \ No newline at end of file + run(main_translator) diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index 1f62a75..78dcad3 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -1,196 +1,789 @@ import os -import shutil -import platform -import time +import sys +import json import uuid -import subprocess +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 emoji constants EMOJI = { + "FILE": "📄", + "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "🔄", - "MENU": "📋", - "WARNING": "⚠️" + "RESET": "��", + "WARNING": "⚠️", } -def delete_directory(path, translator=None): - """Deletes a directory and all its contents.""" - if os.path.exists(path): - try: - shutil.rmtree(path) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.removed', path=path)}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}") +def get_cursor_paths(translator=None) -> Tuple[str, str]: + """ Get Cursor related paths""" + system = platform.system() + + # 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") + + # 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) + + 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]) + + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) else: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") - -def delete_file(path, translator=None): - """Deletes a file if it exists.""" - if os.path.isfile(path): - try: - os.remove(path) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.removed', path=path)}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_remove', path=path, error=e)}{Style.RESET_ALL}") + 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: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") - -def reset_machine_id(translator=None): - """Resets the machine ID to a new UUID.""" - new_id = str(uuid.uuid4()) - if platform.system() == "Windows": - try: - subprocess.run( - ["reg", "add", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid", "/d", new_id, "/f"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_guid_reset', new_id=new_id)}{Style.RESET_ALL}") - except subprocess.CalledProcessError as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_guid', error=e)}{Style.RESET_ALL}") - elif platform.system() == "Linux": - machine_id_paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"] - for path in machine_id_paths: + 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): - try: - with open(path, 'w') as f: - f.write(new_id) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.machine_id_reset', path=path)}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.failed_to_reset_machine_id', path=path, error=e)}{Style.RESET_ALL}") - elif platform.system() == "Darwin": # macOS - print("ℹ️ macOS does not use a machine-id file. Skipping machine ID reset.") + 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) + +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: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')}{Style.RESET_ALL}") + raise OSError(f"Unsupported operating system: {sys.platform}") -def display_features_and_warnings(translator=None): - """Displays features and warnings before proceeding.""" - print(f"\n{Fore.GREEN}{EMOJI['MENU']} {translator.get('totally_reset.title')}") - print("=====================================") - print(f"{translator.get('totally_reset.feature_title')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_1')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_2')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_3')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_4')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_5')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_6')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_7')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_8')}") - print(f"{Fore.GREEN}{translator.get('totally_reset.feature_9')}") - print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('totally_reset.warning_title')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_1')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_2')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_3')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_4')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_5')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_6')}") - print(f"{Fore.YELLOW}{translator.get('totally_reset.warning_7')}") - print("=====================================\n") + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) -def get_user_confirmation(translator=None): - """Prompts the user for confirmation to proceed.""" - while True: - response = input(f"{Fore.YELLOW} {translator.get('totally_reset.confirm_title')} {translator.get('totally_reset.invalid_choice')}: ").lower().strip() - if response in ['yes', 'y']: - return True - elif response in ['no', 'n']: +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" + } + } + + 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'
Pro Trial' + CBadge_new_pattern = r'
Pro' + + CToast_old_pattern = r'notifications-toasts' + CToast_new_pattern = r'notifications-toasts hidden' + + # Replace content + content = content.replace(CButton_old_pattern, CButton_new_pattern) + content = content.replace(CBadge_old_pattern, CBadge_new_pattern) + content = content.replace(CToast_old_pattern, CToast_new_pattern) + + # Write to temporary file + tmp_file.write(content) + tmp_path = tmp_file.name + + # Backup original file + backup_path = file_path + ".backup" + if os.path.exists(backup_path): + os.remove(backup_path) + shutil.copy2(file_path, backup_path) + + # Move temporary file to original position + if os.path.exists(file_path): + os.remove(file_path) + shutil.move(tmp_path, file_path) + + # Restore original permissions + os.chmod(file_path, original_mode) + if os.name != "nt": # Not Windows + os.chown(file_path, original_uid, original_gid) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") + if "tmp_path" in locals(): + try: + os.unlink(tmp_path) + except: + pass + return False + +def modify_main_js(main_path: str, translator) -> bool: + """Modify main.js file""" + try: + original_stat = os.stat(main_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: + with open(main_path, "r", encoding="utf-8") as main_file: + content = main_file.read() + + patterns = { + r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}", + r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}", + } + + for pattern, replacement in patterns.items(): + content = re.sub(pattern, replacement, content) + + tmp_file.write(content) + tmp_path = tmp_file.name + + shutil.copy2(main_path, main_path + ".old") + shutil.move(tmp_path, main_path) + + os.chmod(main_path, original_mode) + if os.name != "nt": + os.chown(main_path, original_uid, original_gid) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") + if "tmp_path" in locals(): + os.unlink(tmp_path) + return False + +def patch_cursor_get_machine_id(translator) -> bool: + """Patch Cursor getMachineId function""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.start_patching')}...{Style.RESET_ALL}") + + # Get paths + pkg_path, main_path = get_cursor_paths(translator) + + # Check file permissions + for file_path in [pkg_path, main_path]: + if not os.path.isfile(file_path): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.file_not_found', path=file_path)}{Style.RESET_ALL}") + return False + if not os.access(file_path, os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_write_permission', path=file_path)}{Style.RESET_ALL}") + return False + + # Get version number + try: + with open(pkg_path, "r", encoding="utf-8") as f: + version = json.load(f)["version"] + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.current_version', version=version)}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.read_version_failed', error=str(e))}{Style.RESET_ALL}") + return False + + # Check version + if not version_check(version, min_version="0.45.0", translator=translator): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_not_supported')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.version_check_passed')}{Style.RESET_ALL}") + + # Backup file + backup_path = main_path + ".bak" + if not os.path.exists(backup_path): + shutil.copy2(main_path, backup_path) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}") + + # Modify file + if not modify_main_js(main_path, translator): + return False + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.patch_completed')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.patch_failed', error=str(e))}{Style.RESET_ALL}") + return False + +class MachineIDResetter: + def __init__(self, translator=None): + self.translator = translator + + # 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 not os.path.exists(config_file): + raise FileNotFoundError(f"Config file not found: {config_file}") + + config.read(config_file, encoding='utf-8') + + # Check operating system + if sys.platform == "win32": # Windows + appdata = os.getenv("APPDATA") + if appdata is None: + raise EnvironmentError("APPDATA Environment Variable Not Set") + + if not config.has_section('WindowsPaths'): + config.add_section('WindowsPaths') + config.set('WindowsPaths', 'storage_path', os.path.join( + appdata, "Cursor", "User", "globalStorage", "storage.json" + )) + config.set('WindowsPaths', 'sqlite_path', os.path.join( + appdata, "Cursor", "User", "globalStorage", "state.vscdb" + )) + + self.db_path = config.get('WindowsPaths', 'storage_path') + self.sqlite_path = config.get('WindowsPaths', 'sqlite_path') + + elif sys.platform == "darwin": # macOS + if not config.has_section('MacPaths'): + config.add_section('MacPaths') + config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/storage.json" + ))) + config.set('MacPaths', 'sqlite_path', os.path.abspath(os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" + ))) + + self.db_path = config.get('MacPaths', 'storage_path') + self.sqlite_path = config.get('MacPaths', 'sqlite_path') + + elif sys.platform == "linux": # Linux + if not config.has_section('LinuxPaths'): + config.add_section('LinuxPaths') + # Get actual user's home directory + sudo_user = os.environ.get('SUDO_USER') + actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") + + config.set('LinuxPaths', 'storage_path', os.path.abspath(os.path.join( + actual_home, + ".config/cursor/User/globalStorage/storage.json" + ))) + config.set('LinuxPaths', 'sqlite_path', os.path.abspath(os.path.join( + actual_home, + ".config/cursor/User/globalStorage/state.vscdb" + ))) + + self.db_path = config.get('LinuxPaths', 'storage_path') + self.sqlite_path = config.get('LinuxPaths', 'sqlite_path') + else: - print(f"{Fore.RED}{translator.get('totally_reset.invalid_choice')}{Style.RESET_ALL}") + raise NotImplementedError(f"Not Supported OS: {sys.platform}") -def reset_cursor(translator=None): - print(f"\n{Fore.GREEN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor')}\n") + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) - # Platform-specific paths - paths = [] - if platform.system() == "Linux": - paths = [ - os.path.expanduser("~/.cursor"), - os.path.expanduser("~/.local/share/cursor"), - os.path.expanduser("~/.config/cursor"), - os.path.expanduser("~/.cache/cursor"), - "/usr/local/bin/cursor", - "/opt/cursor", - "/usr/bin/cursor", - os.path.expanduser("~/.cursor/machine-id.db"), - os.path.expanduser("~/.local/share/Cursor"), - os.path.expanduser("~/.config/Cursor"), - os.path.expanduser("~/.cache/Cursor") - ] - elif platform.system() == "Darwin": # macOS - paths = [ - os.path.expanduser("~/Library/Application Support/Cursor"), - os.path.expanduser("~/Library/Caches/Cursor"), - "/Applications/Cursor.app", - os.path.expanduser("~/Library/Preferences/com.cursor.app.plist"), - ] - elif platform.system() == "Windows": - paths = [ - os.path.expanduser("~\\AppData\\Local\\Cursor"), - os.path.expanduser("~\\AppData\\Roaming\\Cursor"), - os.path.expanduser("~\\.cursor"), - os.path.expanduser("~\\.config\\Cursor"), - os.path.expanduser("~\\.cache\\Cursor"), - "C:\\Program Files\\Cursor", - "C:\\Program Files (x86)\\Cursor", - "C:\\Users\\%USERNAME%\\AppData\\Local\\Cursor", - "C:\\Users\\%USERNAME%\\AppData\\Roaming\\Cursor", - ] + def generate_new_ids(self): + """Generate new machine ID""" + # Generate new UUID + dev_device_id = str(uuid.uuid4()) - # Remove directories - for path in paths: - delete_directory(path, translator) + # Generate new machineId (64 characters of hexadecimal) + machine_id = hashlib.sha256(os.urandom(32)).hexdigest() - # Remove common files related to Cursor - files = [ - os.path.expanduser("~/.cursor/machine-id.db"), - os.path.expanduser("~/.local/share/cursor.db"), - os.path.expanduser("~/.config/cursor/preferences.json"), - os.path.expanduser("~/.cache/cursor.log"), - ] + # Generate new macMachineId (128 characters of hexadecimal) + mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() - for file in files: - delete_file(file, translator) + # Generate new sqmId + sqm_id = "{" + str(uuid.uuid4()).upper() + "}" - # Extra cleanup (wildcard search) - print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.deep_scanning')}") - base_dirs = ["/tmp", "/var/tmp", os.path.expanduser("~")] # Linux and macOS - if platform.system() == "Windows": - base_dirs = ["C:\\Temp", "C:\\Windows\\Temp", os.path.expanduser("~")] # Windows + self.update_machine_id_file(dev_device_id) - for base in base_dirs: - for root, dirs, files in os.walk(base): - for dir in dirs: - if "cursor" in dir.lower(): - delete_directory(os.path.join(root, dir), translator) - for file in files: - if "cursor" in file.lower(): - delete_file(os.path.join(root, file), translator) + return { + "telemetry.devDeviceId": dev_device_id, + "telemetry.macMachineId": mac_machine_id, + "telemetry.machineId": machine_id, + "telemetry.sqmId": sqm_id, + "storage.serviceMachineId": dev_device_id, # Add storage.serviceMachineId + } - # Reset machine ID - reset_machine_id(translator) + def update_sqlite_db(self, new_ids): + """Update machine ID in SQLite database""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_sqlite')}...{Style.RESET_ALL}") + + conn = sqlite3.connect(self.sqlite_path) + cursor = conn.cursor() - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.cursor_reset_completed')}") + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ItemTable ( + key TEXT PRIMARY KEY, + value TEXT + ) + """) -def main(translator=None): - start_time = time.time() + updates = [ + (key, value) for key, value in new_ids.items() + ] + + for key, value in updates: + cursor.execute(""" + INSERT OR REPLACE INTO ItemTable (key, value) + VALUES (?, ?) + """, (key, value)) + print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}") + + conn.commit() + conn.close() + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.sqlite_success')}{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.sqlite_error', error=str(e))}{Style.RESET_ALL}") + return False + + def update_system_ids(self, new_ids): + """Update system-level IDs""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_system_ids')}...{Style.RESET_ALL}") + + if sys.platform.startswith("win"): + self._update_windows_machine_guid() + self._update_windows_machine_id() + elif sys.platform == "darwin": + self._update_macos_platform_uuid(new_ids) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.system_ids_updated')}{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}") + return False + + def _update_windows_machine_guid(self): + """Update Windows MachineGuid""" + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Cryptography", + 0, + winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY + ) + new_guid = str(uuid.uuid4()) + winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid) + winreg.CloseKey(key) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}") + except PermissionError: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}") + raise + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}") + raise - # Display features and warnings - display_features_and_warnings(translator) - - # Get user confirmation - if get_user_confirmation(translator): - reset_cursor(translator) - end_time = time.time() - print(f"\n{Fore.GREEN}⏱️ {translator.get('totally_reset.completed_in', time=f'{end_time - start_time:.2f} seconds')}{Style.RESET_ALL}") - else: - print(f"\n{Fore.RED}❌ {translator.get('totally_reset.operation_cancelled')}{Style.RESET_ALL}") + def _update_windows_machine_id(self): + """Update Windows MachineId in SQMClient registry""" + try: + import winreg + # 1. Generate new GUID + new_guid = "{" + str(uuid.uuid4()).upper() + "}" + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}") + + # 2. Open the registry key + try: + key = winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient", + 0, + winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY + ) + except FileNotFoundError: + # If the key does not exist, create it + key = winreg.CreateKey( + winreg.HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\SQMClient" + ) + + # 3. Set MachineId value + winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid) + winreg.CloseKey(key) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}") + return True + + except PermissionError: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}") + return False + -if __name__ == '__main__': - from main import translator - main(translator) + def _update_macos_platform_uuid(self, new_ids): + """Update macOS Platform UUID""" + try: + uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist" + if os.path.exists(uuid_file): + # Use sudo to execute plutil command + cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"' + result = os.system(cmd) + if result == 0: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}") + else: + raise Exception(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.failed_to_execute_plutil_command')}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_macos_platform_uuid_failed', error=str(e))}{Style.RESET_ALL}") + raise + + def reset_machine_ids(self): + """Reset machine ID and backup original file""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.checking')}...{Style.RESET_ALL}") + + if not os.path.exists(self.db_path): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.not_found')}: {self.db_path}{Style.RESET_ALL}") + return False + + if not os.access(self.db_path, os.R_OK | os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.no_permission')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.reading')}...{Style.RESET_ALL}") + with open(self.db_path, "r", encoding="utf-8") as f: + config = json.load(f) + + backup_path = self.db_path + ".bak" + if not os.path.exists(backup_path): + print(f"{Fore.YELLOW}{EMOJI['BACKUP']} {self.translator.get('reset.creating_backup')}: {backup_path}{Style.RESET_ALL}") + shutil.copy2(self.db_path, backup_path) + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_exists')}{Style.RESET_ALL}") + + print(f"{Fore.CYAN}{EMOJI['RESET']} {self.translator.get('reset.generating')}...{Style.RESET_ALL}") + new_ids = self.generate_new_ids() + + # Update configuration file + config.update(new_ids) + + print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.saving_json')}...{Style.RESET_ALL}") + with open(self.db_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=4) + + # Update SQLite database + self.update_sqlite_db(new_ids) + + # Update system IDs + self.update_system_ids(new_ids) + + + # Modify workbench.desktop.main.js + workbench_path = get_workbench_cursor_path(self.translator) + modify_workbench_js(workbench_path, self.translator) + + # Check Cursor version and perform corresponding actions + + greater_than_0_45 = check_cursor_version(self.translator) + if greater_than_0_45: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0,{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}") + patch_cursor_get_machine_id(self.translator) + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}") + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.success')}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{self.translator.get('reset.new_id')}:{Style.RESET_ALL}") + for key, value in new_ids.items(): + print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") + + return True + + except PermissionError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_error', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}") + return False + + def update_machine_id_file(self, machine_id: str) -> bool: + """ + Update machineId file with new machine_id + Args: + machine_id (str): New machine ID to write + Returns: + bool: True if successful, False otherwise + """ + try: + # Get the machineId file path + machine_id_path = get_cursor_machine_id_path() + + # Create directory if it doesn't exist + os.makedirs(os.path.dirname(machine_id_path), exist_ok=True) + + # Create backup if file exists + if os.path.exists(machine_id_path): + backup_path = machine_id_path + ".backup" + try: + shutil.copy2(machine_id_path, backup_path) + print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('reset.backup_created', path=backup_path) if self.translator else f'Backup created at: {backup_path}'}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_creation_failed', error=str(e)) if self.translator else f'Could not create backup: {str(e)}'}{Style.RESET_ALL}") + + # Write new machine ID to file + with open(machine_id_path, "w", encoding="utf-8") as f: + f.write(machine_id) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.update_success') if self.translator else 'Successfully updated machineId file'}{Style.RESET_ALL}") + return True + + except Exception as e: + error_msg = f"Failed to update machineId file: {str(e)}" + if self.translator: + error_msg = self.translator.get('reset.update_failed', error=str(e)) + print(f"{Fore.RED}{EMOJI['ERROR']} {error_msg}{Style.RESET_ALL}") + return False + +def run(translator=None): + config = get_config(translator) + if not config: + return False + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + + resetter = MachineIDResetter(translator) # Correctly pass translator + resetter.reset_machine_ids() + + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + input(f"{EMOJI['INFO']} {translator.get('reset.press_enter')}...") + +if __name__ == "__main__": + from main import translator as main_translator + run(main_translator)