From 8943266f6e21d34aafc7d1b4dc9ec1fbbcff039f Mon Sep 17 00:00:00 2001 From: Nigel1992 Date: Mon, 24 Mar 2025 23:01:24 +0100 Subject: [PATCH 01/47] fix: Improve Linux path detection and config handling --- reset_machine_manual.py | 52 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 975c6bd..c9f383f 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -37,10 +37,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")] + } + + # 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 +82,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") From a8966de771a1ef3c32c709fcd846eca046bd55f1 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Tue, 25 Mar 2025 06:37:20 +0800 Subject: [PATCH 02/47] Add new translation key for 'path_not_found' in English and Chinese (Simplified and Traditional) locales, ensuring consistent error messaging across languages. --- locales/en.json | 3 ++- locales/zh_cn.json | 3 ++- locales/zh_tw.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6054903..82bbc21 100644 --- a/locales/en.json +++ b/locales/en.json @@ -104,7 +104,8 @@ "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}" }, "register": { "title": "Cursor Registration Tool", diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 992e854..96f4b25 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -104,7 +104,8 @@ "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}" }, "register": { "title": "Cursor 注册工具", diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 75b178e..6ab8447 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -102,7 +102,8 @@ "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}" }, "register": { From eb6ef6bb3b898d0d4d52140a4aab5e4471f435d8 Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Tue, 25 Mar 2025 06:51:28 +0800 Subject: [PATCH 03/47] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b9d6f..1b481d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## v1.7.18 (Pre-Release) 1. Fix: No Write Permission | 修復沒有寫入權限 -2. Fix: Some Issues | 修復一些問題 +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 選項 From 4ef293da81b6ec940413b56488952cf9161879dd Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Tue, 25 Mar 2025 06:52:29 +0800 Subject: [PATCH 04/47] Update .env --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 95a7d79..2a2c066 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.7.17 -VERSION=1.7.17 +version=1.7.18 +VERSION=1.7.18 From 80e99462947cf30c9621415bf2f5fcd1b94bed2e Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Tue, 25 Mar 2025 16:12:04 +0800 Subject: [PATCH 05/47] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b481d7..d5e30ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## v1.7.18 (Pre-Release) +## 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 語言遺失 From 7405c61cc9ec3af2bdc5fd7b12ca957e18624e70 Mon Sep 17 00:00:00 2001 From: Ali Asghar Ranjbar Date: Wed, 26 Mar 2025 08:26:56 +0330 Subject: [PATCH 06/47] fix: modified the change language option number --- logo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logo.py b/logo.py index 7a0faa6..83f4c76 100644 --- a/logo.py +++ b/logo.py @@ -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) @@ -98,4 +98,4 @@ def print_logo(): print(CURSOR_OTHER_INFO) if __name__ == "__main__": - print_logo() \ No newline at end of file + print_logo() From 18fc0532fdfd4b0f9e5e2bba7640d4f05541f533 Mon Sep 17 00:00:00 2001 From: imbajin Date: Wed, 26 Mar 2025 18:24:10 +0800 Subject: [PATCH 07/47] fix: inconsistent message --- logo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logo.py b/logo.py index 7a0faa6..83f4c76 100644 --- a/logo.py +++ b/logo.py @@ -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) @@ -98,4 +98,4 @@ def print_logo(): print(CURSOR_OTHER_INFO) if __name__ == "__main__": - print_logo() \ No newline at end of file + print_logo() From 5cc7630ce68161315b5997052baa1d282792e7e2 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Date: Fri, 28 Mar 2025 00:39:21 -0300 Subject: [PATCH 08/47] Update README.md --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 825144c..6a4cf27 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,15 @@

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

-This is a tool to automatically register, support Windows and macOS systems, complete Auth verification, and reset -Cursor's configuration. +This tool register accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes,reset and wipe 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 cache and cookies. If possible, user a VPN to create new accounts. + + 這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。 From 4587fd9373663cd0ad8775fbc99d5f6bdeef8773 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 19:24:55 +0800 Subject: [PATCH 09/47] Update version to 1.7.19, add Cursor Account Info feature, and enhance configuration handling for updater paths. Update CHANGELOG.md with new features and fixes. --- .env | 4 +- CHANGELOG.md | 6 + config.py | 9 +- cursor_acc_info.py | 469 +++++++++++++++++++++++++++++++++++++++++ disable_auto_update.py | 68 +++++- locales/en.json | 40 +++- locales/zh_cn.json | 39 +++- locales/zh_tw.json | 38 +++- main.py | 6 + 9 files changed, 664 insertions(+), 15 deletions(-) create mode 100644 cursor_acc_info.py diff --git a/.env b/.env index 2a2c066..0aa1b48 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.7.18 -VERSION=1.7.18 +version=1.7.19 +VERSION=1.7.19 diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e30ec..7e63c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## v1.7.19 +1. Add: Cursor Account Info | 增加 Cursor 賬號信息 +2. Fix: Disable Auto Update | 修復禁用自動更新 +3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持 +4. Fix: Some Issues | 修復一些問題 + ## v1.7.18 1. Fix: No Write Permission | 修復沒有寫入權限 2. Fix: Improve Linux path detection and config handling | 修正 linux 路徑和config寫入讀取 diff --git a/config.py b/config.py index 8720412..314e4b4 100644 --- a/config.py +++ b/config.py @@ -49,7 +49,8 @@ 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") } elif sys.platform == "darwin": default_config['MacPaths'] = { @@ -57,7 +58,8 @@ def setup_config(translator=None): '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') @@ -68,7 +70,8 @@ def setup_config(translator=None): '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"), 'cursor_path': get_linux_cursor_path(), - 'updater_path': os.path.expanduser("~/.config/cursor-updater") + 'updater_path': os.path.expanduser("~/.config/cursor-updater"), + 'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml" } # Read existing configuration and merge diff --git a/cursor_acc_info.py b/cursor_acc_info.py new file mode 100644 index 0000000..5b34700 --- /dev/null +++ b/cursor_acc_info.py @@ -0,0 +1,469 @@ +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() + + # 获取 GPT-4 使用量和限制 + gpt4_data = data.get("gpt-4", {}) + premium_usage = gpt4_data.get("numRequestsTotal", 0) + max_premium_usage = gpt4_data.get("maxRequestUsage", 999) + + # 获取 GPT-3.5 使用量,但将限制设为 "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" # 将 GPT-3.5 的限制设为 "No Limit" + } + except requests.RequestException as e: + logger.error(f"获取使用量失败: {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"获取订阅信息失败: {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"获取配置路径失败: {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 == "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 "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}{'─' * 40}{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}{'─' * 40}{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 + subscription_info = UsageManager.get_stripe_profile(token) + + # 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 + usage_info = UsageManager.get_usage(token) + + # display account info + if email: + print(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}") + + # display subscription type + if subscription_info: + subscription_type = format_subscription_type(subscription_info) + print(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}") + + # display remaining trial days + days_remaining = subscription_info.get("daysRemainingOnTrial") + if days_remaining is not None and days_remaining > 0: + print(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: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}") + + # display usage info + if usage_info: + print(f"\n{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}") + + # GPT-4 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}%)" + + print(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}%)" + + print(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.usage_not_found') if translator else 'Usage information not found'}{Style.RESET_ALL}") + + print(f"{Fore.CYAN}{'─' * 40}{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..2424dc8 100644 --- a/disable_auto_update.py +++ b/disable_auto_update.py @@ -31,10 +31,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 +46,13 @@ 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 _kill_cursor_processes(self): """End all Cursor processes""" @@ -82,26 +92,68 @@ 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}") return False + + 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: @@ -121,7 +173,11 @@ class AutoUpdateDisabler: if not self._remove_updater_directory(): return False - # 3. Create blocking file + # 3. Clear update.yml file + if not self._clear_update_yml_file(): + return False + + # 4. Create blocking file if not self._create_blocking_file(): return False diff --git a/locales/en.json b/locales/en.json index 82bbc21..2003730 100644 --- a/locales/en.json +++ b/locales/en.json @@ -287,7 +287,12 @@ "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}" }, "updater": { "checking": "Checking for updates...", @@ -440,5 +445,36 @@ "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." - } + }, + "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" + } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 96f4b25..cac61bd 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -282,7 +282,12 @@ "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}" }, "updater": { "checking": "检查更新...", @@ -435,5 +440,37 @@ "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": "基础使用量" } + } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 6ab8447..2949002 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -261,7 +261,12 @@ "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}" }, "updater": { "checking": "檢查更新...", @@ -414,5 +419,36 @@ "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": "基礎使用量" } } \ No newline at end of file diff --git a/main.py b/main.py index 9cee1f3..34e524d 100644 --- a/main.py +++ b/main.py @@ -242,6 +242,12 @@ translator = Translator() def print_menu(): """Print menu options""" + try: + 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')}") From 1c1174fa6ca7c8b77954a0867c5e144f3b6c62ae Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 19:26:58 +0800 Subject: [PATCH 10/47] Add new translation key for 'remove_directory_failed' in English, Simplified Chinese, and Traditional Chinese locales to enhance error messaging consistency. --- disable_auto_update.py | 8 ++++---- locales/en.json | 3 ++- locales/zh_cn.json | 3 ++- locales/zh_tw.json | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/disable_auto_update.py b/disable_auto_update.py index 2424dc8..ba96fce 100644 --- a/disable_auto_update.py +++ b/disable_auto_update.py @@ -91,7 +91,8 @@ 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}") - return False + # 即使删除失败,也返回 True,继续执行下一步 + return True def _clear_update_yml_file(self): """Clear update.yml file""" @@ -169,9 +170,8 @@ class AutoUpdateDisabler: if not self._kill_cursor_processes(): return False - # 2. Delete directory - if not self._remove_updater_directory(): - return False + # 2. Delete directory - 即使失败也继续执行 + self._remove_updater_directory() # 3. Clear update.yml file if not self._clear_update_yml_file(): diff --git a/locales/en.json b/locales/en.json index 2003730..05b7da6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -292,7 +292,8 @@ "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}" + "unsupported_os": "Unsupported OS: {system}", + "remove_directory_failed": "Failed to remove directory: {error}" }, "updater": { "checking": "Checking for updates...", diff --git a/locales/zh_cn.json b/locales/zh_cn.json index cac61bd..ecade24 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -287,7 +287,8 @@ "update_yml_cleared": "update.yml 文件已清空", "update_yml_not_found": "update.yml 文件未找到", "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", - "unsupported_os": "不支持的操作系统: {system}" + "unsupported_os": "不支持的操作系统: {system}", + "remove_directory_failed": "删除目录失败: {error}" }, "updater": { "checking": "检查更新...", diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 2949002..8ddb6cd 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -266,7 +266,8 @@ "update_yml_cleared": "update.yml 文件已清空", "update_yml_not_found": "update.yml 文件未找到", "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", - "unsupported_os": "不支持的操作系统: {system}" + "unsupported_os": "不支持的操作系统: {system}", + "remove_directory_failed": "刪除目錄失败: {error}" }, "updater": { "checking": "檢查更新...", From b6bf62f841a6572a0b7b2113306b9a53ad26b58c Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 19:28:46 +0800 Subject: [PATCH 11/47] Update configuration paths for update.yml and add new translation key for 'create_block_file_failed' in English, Simplified Chinese, and Traditional Chinese locales. --- config.py | 2 +- locales/en.json | 3 ++- locales/zh_cn.json | 3 ++- locales/zh_tw.json | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 314e4b4..4ef5a2c 100644 --- a/config.py +++ b/config.py @@ -50,7 +50,7 @@ def setup_config(translator=None): '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"), - 'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "update.yml") + 'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "update.yml") } elif sys.platform == "darwin": default_config['MacPaths'] = { diff --git a/locales/en.json b/locales/en.json index 05b7da6..d3c1c97 100644 --- a/locales/en.json +++ b/locales/en.json @@ -293,7 +293,8 @@ "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}" + "remove_directory_failed": "Failed to remove directory: {error}", + "create_block_file_failed": "Failed to create block file: {error}" }, "updater": { "checking": "Checking for updates...", diff --git a/locales/zh_cn.json b/locales/zh_cn.json index ecade24..aaf3db4 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -288,7 +288,8 @@ "update_yml_not_found": "update.yml 文件未找到", "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", "unsupported_os": "不支持的操作系统: {system}", - "remove_directory_failed": "删除目录失败: {error}" + "remove_directory_failed": "删除目录失败: {error}", + "create_block_file_failed": "创建阻止文件失败: {error}" }, "updater": { "checking": "检查更新...", diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 8ddb6cd..af3d414 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -267,7 +267,8 @@ "update_yml_not_found": "update.yml 文件未找到", "clear_update_yml_failed": "清空 update.yml 文件失败: {error}", "unsupported_os": "不支持的操作系统: {system}", - "remove_directory_failed": "刪除目錄失败: {error}" + "remove_directory_failed": "刪除目錄失败: {error}", + "create_block_file_failed": "創建阻止文件失败: {error}" }, "updater": { "checking": "檢查更新...", From f6ffb18427fa2b06ff626056e64fbe95f286326e Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 19:29:57 +0800 Subject: [PATCH 12/47] Update configuration path for update.yml to app-update.yml in setup_config function. --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 4ef5a2c..11feb02 100644 --- a/config.py +++ b/config.py @@ -50,7 +50,7 @@ def setup_config(translator=None): '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"), - 'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "update.yml") + 'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml") } elif sys.platform == "darwin": default_config['MacPaths'] = { From 8f35f163c5599c69507af972baf86f16748169a9 Mon Sep 17 00:00:00 2001 From: Luiz Henrique Date: Fri, 28 Mar 2025 10:24:49 -0300 Subject: [PATCH 13/47] Update README.md From 28cd662e83921ec0cb704d8c5f437ab2765ed2d0 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:27:00 +0800 Subject: [PATCH 14/47] Update CHANGELOG.md, fix logo centering, and enhance totally reset cursor functionality. Revert cursor reset to beta, add new confirmation and display functions, and improve file handling for cursor-related paths. --- CHANGELOG.md | 5 +- logo.py | 4 +- main.py | 4 +- totally_reset_cursor.py | 613 ++++++++++++++++++++++++++++++---------- 4 files changed, 471 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e63c55..09af72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ 1. Add: Cursor Account Info | 增加 Cursor 賬號信息 2. Fix: Disable Auto Update | 修復禁用自動更新 3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持 -4. Fix: Some Issues | 修復一些問題 +4. Revert: Totally Reser Cursor to Beta | 恢復完全重置 Cursor 到 Beta +5. Reopen: Totally Reset Cursor | 重新開啟完全重置 Cursor +6. Fix: Logo.py Center | 修復 Logo.py 居中 +7. Fix: Some Issues | 修復一些問題 ## v1.7.18 1. Fix: No Write Permission | 修復沒有寫入權限 diff --git a/logo.py b/logo.py index 83f4c76..7e8f1cd 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 diff --git a/main.py b/main.py index 34e524d..4a0af37 100644 --- a/main.py +++ b/main.py @@ -504,8 +504,8 @@ 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() else: print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}") diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index 1f62a75..378b776 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -2,195 +2,508 @@ import os import shutil import platform import time +import sys +import glob +import json import uuid +import random +import string +import re +from datetime import datetime import subprocess from colorama import Fore, Style, init +from main import translator +from main import EMOJI +# Initialize colorama init() +# Define emoji and color constants EMOJI = { + "FILE": "📄", + "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", "RESET": "🔄", "MENU": "📋", + "ARROW": "➜", + "LANG": "🌐", + "UPDATE": "🔄", + "ADMIN": "🔐", + "STOP": "🛑", + "DISCLAIMER": "⚠️", "WARNING": "⚠️" } -def delete_directory(path, translator=None): - """Deletes a directory and all its contents.""" +def display_banner(): + """Displays a stylized banner for the tool.""" + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.title')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + +def display_features(): + """Displays the features of the Cursor AI Reset Tool.""" + print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('totally_reset.feature_title')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_1')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_2')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_3')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_4')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_5')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_6')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_7')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_8')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_9')} {Style.RESET_ALL}\n") + +def display_disclaimer(): + """Displays a disclaimer for the user.""" + print(f"\n{Fore.RED}{EMOJI['DISCLAIMER']} {translator.get('totally_reset.disclaimer_title')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_1')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_2')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_3')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_4')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_5')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_6')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_7')} {Style.RESET_ALL} \n") + +def get_confirmation(): + """Gets confirmation from the user to proceed.""" + while True: + choice = input(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.confirm_title')} (Y/n): ").strip().lower() + if choice == "y" or choice == "": + return True + elif choice == "n": + return False + else: + print(f"{EMOJI['ERROR']} {translator.get('totally_reset.invalid_choice')}") + +def remove_dir(path): + """Removes a directory if it exists and logs the action.""" + # Safety check to ensure we're only deleting Cursor-related directories + if not is_cursor_related(path): + print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") + return + 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}") + shutil.rmtree(path, ignore_errors=True) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', 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}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") + print(f"{Fore.RED}{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.""" +def remove_file(path): + """Removes a file if it exists and logs the action.""" + # Safety check to ensure we're only deleting Cursor-related files + if not is_cursor_related(path): + print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") + return + 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}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', 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}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)}{Style.RESET_ALL}") + print(f"{Fore.RED}{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: - 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.") - else: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')}{Style.RESET_ALL}") - -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") - -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']: +def is_cursor_related(path): + """ + Safety function to verify a path is related to Cursor before deletion. + Returns True if the path appears to be related to Cursor AI. + """ + # Skip .vscode check as it's shared with VS Code + if path.endswith(".vscode"): + return False + + # Check if path contains cursor-related terms + cursor_terms = ["cursor", "cursorai", "cursor-electron"] + + # Convert path to lowercase for case-insensitive matching + lower_path = path.lower() + + # Return True if any cursor term is present in the path + for term in cursor_terms: + if term in lower_path: return True - elif response in ['no', 'n']: - return False - else: - print(f"{Fore.RED}{translator.get('totally_reset.invalid_choice')}{Style.RESET_ALL}") + + # Check specific known Cursor file patterns + cursor_patterns = [ + r"\.cursor_.*$", + r"cursor-.*\.json$", + r"cursor_.*\.json$", + r"cursor-machine-id$", + r"trial_info\.json$", + r"license\.json$" + ] + + for pattern in cursor_patterns: + if re.search(pattern, lower_path): + return True + + # If it's a specific file that we know is only for Cursor + if os.path.basename(lower_path) in [ + "cursor_trial_data", + "cursor-state.json", + "cursor-machine-id", + "ai-settings.json", + "cursor.desktop" + ]: + return True + + return False -def reset_cursor(translator=None): - print(f"\n{Fore.GREEN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor')}\n") +def find_cursor_license_files(base_path, pattern): + """Finds files matching a pattern that might contain license information.""" + try: + matches = [] + for root, dirnames, filenames in os.walk(base_path): + for filename in filenames: + # Check if filename matches any pattern before adding to matches + if any(p.lower() in filename.lower() for p in pattern): + full_path = os.path.join(root, filename) + # Extra safety check to ensure it's cursor-related + if is_cursor_related(full_path): + matches.append(full_path) + return matches + except Exception as e: + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.error_searching', path=base_path, error=str(e))} {Style.RESET_ALL}") + return [] - # 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") +def generate_new_machine_id(): + """Generates a new random machine ID.""" + return str(uuid.uuid4()) + +def create_fake_machine_id(path): + """Creates a new machine ID file with random ID.""" + if not is_cursor_related(path): + return + + try: + new_id = generate_new_machine_id() + directory = os.path.dirname(path) + + # Ensure directory exists + if not os.path.exists(directory): + os.makedirs(directory) + + with open(path, 'w') as f: + f.write(new_id) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_machine_id', path=path)} {Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_machine_id', path=path, error=str(e))} {Style.RESET_ALL}") + +def reset_machine_id(system, home): + """Resets machine ID in all possible locations.""" + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_machine_id')} {Style.RESET_ALL}") + + # Common machine ID locations based on OS + if system == "Windows": + machine_id_paths = [ + os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), + os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), + os.path.join(home, "AppData", "Roaming", "cursor-electron", "cursor-machine-id"), + os.path.join(home, "AppData", "Local", "cursor-electron", "cursor-machine-id"), + os.path.join(home, ".cursor-machine-id"), ] - 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 system == "Darwin": # macOS + machine_id_paths = [ + os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), + os.path.join(home, "Library", "Application Support", "cursor-electron", "cursor-machine-id"), + os.path.join(home, ".cursor-machine-id"), ] - 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", + elif system == "Linux": + machine_id_paths = [ + os.path.join(home, ".config", "Cursor", "cursor-machine-id"), + os.path.join(home, ".config", "cursor-electron", "cursor-machine-id"), + os.path.join(home, ".cursor-machine-id"), + ] + + # First remove existing machine IDs + for path in machine_id_paths: + remove_file(path) + + # Then create new randomized IDs + for path in machine_id_paths: + create_fake_machine_id(path) + + # Try to reset system machine ID if possible (with appropriate permissions) + if system == "Windows": + try: + # Windows: Create a temporary VBS script to reset machine GUID + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_machine_id_reset_may_require_running_as_administrator')} {Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.windows_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") + + elif system == "Linux": + try: + # Linux: Create a random machine-id in /etc/ (needs sudo) + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_system_machine_id_reset_may_require_sudo_privileges')} {Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.linux_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") + +def create_fake_trial_info(path, system, home): + """Creates fake trial information to extend trial period.""" + if not is_cursor_related(path): + return + + try: + # Generate future expiry date (90 days from now) + future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds + + # Create fake trial info + fake_trial = { + "trialStartTimestamp": datetime.now().timestamp() * 1000, + "trialEndTimestamp": future_date, + "hasUsedTrial": False, + "machineId": generate_new_machine_id() + } + + directory = os.path.dirname(path) + + # Ensure directory exists + if not os.path.exists(directory): + os.makedirs(directory) + + with open(path, 'w') as f: + json.dump(fake_trial, f) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_extended_trial_info', path=path)} {Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_trial_info', path=path, error=str(e))} {Style.RESET_ALL}") + +def reset_cursor(): + """Completely resets Cursor AI by removing all settings, caches, and extensions.""" + system = platform.system() + home = os.path.expanduser("~") + + display_banner() + display_features() + display_disclaimer() + + if not get_confirmation(): + print(f"\n{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.reset_cancelled')} {Style.RESET_ALL}") + return + + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor_ai_editor')} {Style.RESET_ALL}") + + # Define paths based on OS + if system == "Windows": + cursor_paths = [ + os.path.join(home, "AppData", "Roaming", "Cursor"), + os.path.join(home, "AppData", "Local", "Cursor"), + os.path.join(home, "AppData", "Roaming", "cursor-electron"), + os.path.join(home, "AppData", "Local", "cursor-electron"), + os.path.join(home, "AppData", "Local", "CursorAI"), + os.path.join(home, "AppData", "Roaming", "CursorAI"), + # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code + os.path.join(home, "AppData", "Local", "Temp", "Cursor"), # Temporary data + os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"), + os.path.join(home, "AppData", "Local", "Programs", "cursor"), + ] + + # Additional locations for license/trial files on Windows + license_search_paths = [ + os.path.join(home, "AppData", "Roaming"), + os.path.join(home, "AppData", "Local"), + os.path.join(home, "AppData", "LocalLow"), + ] + + # Registry instructions for Windows + print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions')} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions_2')} {Style.RESET_ALL}") + + elif system == "Darwin": # macOS + cursor_paths = [ + os.path.join(home, "Library", "Application Support", "Cursor"), + os.path.join(home, "Library", "Application Support", "cursor-electron"), + os.path.join(home, "Library", "Caches", "Cursor"), + os.path.join(home, "Library", "Caches", "cursor-electron"), + os.path.join(home, "Library", "Preferences", "Cursor"), + os.path.join(home, "Library", "Preferences", "cursor-electron"), + os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"), + os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"), + os.path.join(home, "Library", "WebKit", "com.cursor.Cursor"), + # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code + "/Applications/Cursor.app", # Main application location + ] + + # Additional locations for license/trial files on macOS + license_search_paths = [ + os.path.join(home, "Library", "Application Support"), + os.path.join(home, "Library", "Preferences"), + os.path.join(home, "Library", "Caches"), ] - # Remove directories - for path in paths: - delete_directory(path, translator) + elif system == "Linux": + cursor_paths = [ + os.path.join(home, ".config", "Cursor"), + os.path.join(home, ".config", "cursor-electron"), + os.path.join(home, ".cache", "Cursor"), + os.path.join(home, ".cache", "cursor-electron"), + os.path.join(home, ".local", "share", "Cursor"), + os.path.join(home, ".local", "share", "cursor-electron"), + # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code + os.path.join(home, ".local", "share", "applications", "cursor.desktop"), + os.path.join("/usr", "share", "applications", "cursor.desktop"), + os.path.join("/opt", "Cursor"), + ] + + # Additional locations for license/trial files on Linux + license_search_paths = [ + os.path.join(home, ".config"), + os.path.join(home, ".local", "share"), + os.path.join(home, ".cache"), + ] - # 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"), + else: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')} {Style.RESET_ALL}") + return + + # Remove main Cursor directories + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_main_cursor_directories_and_files')} {Style.RESET_ALL}") + for path in cursor_paths: + remove_dir(path) + + # Reset machine identifiers (this creates new ones) + reset_machine_id(system, home) + + # Known trial/license file patterns + file_patterns = [ + ".cursor_trial_data", + "trial_info.json", + "license.json", + "cursor-license", + "cursor_license", + "cursor-auth", + "cursor_auth", + "cursor_subscription", + "cursor-subscription", + "cursor-state", + "cursorstate", + "cursorsettings", + "cursor-settings", + "ai-settings.json", + "cursor-machine-id", + "cursor_machine_id", + "cursor-storage" ] - for file in files: - delete_file(file, translator) + # Direct known trial file paths + cursor_trial_files = [ + os.path.join(home, ".cursor_trial_data"), + os.path.join(home, ".cursor_license"), + os.path.join(home, ".cursor-machine-id"), + os.path.join(home, ".cursor-state.json"), + ] - # 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 + # OS-specific known trial/license files + if system == "Windows": + cursor_trial_files.extend([ + os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), + os.path.join(home, "AppData", "Local", "Cursor", "license.json"), + os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), + os.path.join(home, "AppData", "Roaming", "Cursor", "license.json"), + os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), + os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), + os.path.join(home, "AppData", "Local", "Cursor", "ai-settings.json"), + os.path.join(home, "AppData", "Roaming", "Cursor", "ai-settings.json"), + ]) + elif system == "Darwin": # macOS + cursor_trial_files.extend([ + os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), + os.path.join(home, "Library", "Application Support", "Cursor", "license.json"), + os.path.join(home, "Library", "Preferences", "Cursor", "trial_info.json"), + os.path.join(home, "Library", "Preferences", "Cursor", "license.json"), + os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), + os.path.join(home, "Library", "Application Support", "Cursor", "ai-settings.json"), + ]) + elif system == "Linux": + cursor_trial_files.extend([ + os.path.join(home, ".config", "Cursor", "trial_info.json"), + os.path.join(home, ".config", "Cursor", "license.json"), + os.path.join(home, ".local", "share", "Cursor", "trial_info.json"), + os.path.join(home, ".local", "share", "Cursor", "license.json"), + os.path.join(home, ".config", "Cursor", "cursor-machine-id"), + os.path.join(home, ".config", "Cursor", "ai-settings.json"), + ]) - 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) + # Remove known trial/license files + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_known')} {Style.RESET_ALL}") + for path in cursor_trial_files: + remove_file(path) - # Reset machine ID - reset_machine_id(translator) - - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.cursor_reset_completed')}") - -def main(translator=None): - start_time = time.time() + # Deep search for additional trial/license files + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.performing_deep_scan')} {Style.RESET_ALL}") + all_found_files = [] + for base_path in license_search_paths: + if os.path.exists(base_path): + found_files = find_cursor_license_files(base_path, file_patterns) + all_found_files.extend(found_files) - # 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}") + if all_found_files: + print(f"\n🔎 {translator.get('totally_reset.found_additional_potential_license_trial_files', count=len(all_found_files))}\n") + for file_path in all_found_files: + remove_file(file_path) else: - print(f"\n{Fore.RED}❌ {translator.get('totally_reset.operation_cancelled')}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.no_additional_license_trial_files_found_in_deep_scan')} {Style.RESET_ALL}") -if __name__ == '__main__': - from main import translator - main(translator) + # Check for and remove localStorage files that might contain settings + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.checking_for_electron_localstorage_files')} {Style.RESET_ALL}") + if system == "Windows": + local_storage_paths = glob.glob(os.path.join(home, "AppData", "Roaming", "*cursor*", "Local Storage", "leveldb", "*")) + local_storage_paths += glob.glob(os.path.join(home, "AppData", "Local", "*cursor*", "Local Storage", "leveldb", "*")) + elif system == "Darwin": + local_storage_paths = glob.glob(os.path.join(home, "Library", "Application Support", "*cursor*", "Local Storage", "leveldb", "*")) + elif system == "Linux": + local_storage_paths = glob.glob(os.path.join(home, ".config", "*cursor*", "Local Storage", "leveldb", "*")) + + for path in local_storage_paths: + if is_cursor_related(path): + remove_file(path) + + # Create new trial files with extended expiration + print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.creating_new_trial_information_with_extended_period')} {Style.RESET_ALL}") + if system == "Windows": + create_fake_trial_info(os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), system, home) + create_fake_trial_info(os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), system, home) + elif system == "Darwin": + create_fake_trial_info(os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), system, home) + elif system == "Linux": + create_fake_trial_info(os.path.join(home, ".config", "Cursor", "trial_info.json"), system, home) + + print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.reset_log_1')}") + print(f" {translator.get('totally_reset.reset_log_2')}") + print(f" {translator.get('totally_reset.reset_log_3')}") + + print(f"\n{Fore.GREEN}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_4')} {Style.RESET_ALL}") + print(f" {translator.get('totally_reset.reset_log_5')} {Style.RESET_ALL}") + print(f" {translator.get('totally_reset.reset_log_6')} {Style.RESET_ALL}") + print(f" {translator.get('totally_reset.reset_log_7')} {Style.RESET_ALL}") + print(f" {translator.get('totally_reset.reset_log_8')} {Style.RESET_ALL}") + + print(f"\n{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_9')} {Style.RESET_ALL}") + +if __name__ == "__main__": + try: + reset_cursor() + except KeyboardInterrupt: + print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.keyboard_interrupt')} {Style.RESET_ALL}") + sys.exit(1) + except Exception as e: + print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") + print(f" {translator.get('totally_reset.report_issue')}") + sys.exit(1) + +def run(translator=None): + """Entry point for the totally reset cursor functionality when called from the main menu.""" + try: + reset_cursor() + input(f"\n\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.return_to_main_menu')} {Style.RESET_ALL}") + except KeyboardInterrupt: + print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.process_interrupted')} {Style.RESET_ALL}") + except Exception as e: + print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") + print(f" {translator.get('totally_reset.report_issue')}") + input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}") \ No newline at end of file From c97bfd1475400f2eede6b24b19b5120b2f0d6b26 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:33:48 +0800 Subject: [PATCH 15/47] Update CHANGELOG.md with specific Linux Chrome fix, add no-sandbox option in new_tempemail.py, and uncomment workbench path modification in reset_machine_manual.py. --- CHANGELOG.md | 3 ++- new_tempemail.py | 2 ++ reset_machine_manual.py | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09af72a..f087524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ 4. Revert: Totally Reser Cursor to Beta | 恢復完全重置 Cursor 到 Beta 5. Reopen: Totally Reset Cursor | 重新開啟完全重置 Cursor 6. Fix: Logo.py Center | 修復 Logo.py 居中 -7. Fix: Some Issues | 修復一些問題 +7. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開 +8. Fix: Some Issues | 修復一些問題 ## v1.7.18 1. Fix: No Write Permission | 修復沒有寫入權限 diff --git a/new_tempemail.py b/new_tempemail.py index 419ff2a..b65573a 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -107,6 +107,8 @@ class NewTempEmail: # 创建浏览器选项 co = ChromiumOptions() co.set_argument("--headless=new") + co.set_argument("--no-sandbox") + co.auto_port() # 自动设置端口 diff --git a/reset_machine_manual.py b/reset_machine_manual.py index c9f383f..4d9b710 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -666,8 +666,8 @@ class MachineIDResetter: ### 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 From 5f380ebe5eac4ffc238ac09b312d0651c4f4569e Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:41:19 +0800 Subject: [PATCH 16/47] Refactor reset_machine_manual.py to remove deprecated comments and add new error message translations for file modification failures in English, Simplified Chinese, and Traditional Chinese locales. --- locales/en.json | 3 ++- locales/zh_cn.json | 3 ++- locales/zh_tw.json | 3 ++- reset_machine_manual.py | 3 --- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index d3c1c97..a23c1a4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -105,7 +105,8 @@ "stack_trace": "Stack Trace", "version_too_low": "Cursor Version Too Low: {version} < 0.45.0", "no_write_permission": "No Write Permission: {path}", - "path_not_found": "Path Not Found: {path}" + "path_not_found": "Path Not Found: {path}", + "modify_file_failed": "Modify File Failed: {error}" }, "register": { "title": "Cursor Registration Tool", diff --git a/locales/zh_cn.json b/locales/zh_cn.json index aaf3db4..6a78544 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -105,7 +105,8 @@ "stack_trace": "堆栈跟踪", "version_too_low": "Cursor版本太低: {version} < 0.45.0", "no_write_permission": "没有写入权限: {path}", - "path_not_found": "路径未找到: {path}" + "path_not_found": "路径未找到: {path}", + "modify_file_failed": "修改文件失败: {error}" }, "register": { "title": "Cursor 注册工具", diff --git a/locales/zh_tw.json b/locales/zh_tw.json index af3d414..ba91c70 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -103,7 +103,8 @@ "stack_trace": "堆疊跟踪", "version_too_low": "Cursor版本太低: {version} < 0.45.0", "no_write_permission": "沒有寫入權限: {path}", - "path_not_found": "路徑未找到: {path}" + "path_not_found": "路徑未找到: {path}", + "modify_file_failed": "修改文件失敗: {error}" }, "register": { diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 4d9b710..722800b 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -663,13 +663,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) - ### Remove In v1.7.02 # Check Cursor version and perform corresponding actions greater_than_0_45 = check_cursor_version(self.translator) From df58b2e4abbba8f6759bca07f340e60ffaae4484 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:42:28 +0800 Subject: [PATCH 17/47] Update version to 1.8.01 in .env and CHANGELOG.md, reflecting new features and fixes. --- .env | 4 ++-- CHANGELOG.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 0aa1b48..015bf6b 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.7.19 -VERSION=1.7.19 +version=1.8.01 +VERSION=1.8.01 diff --git a/CHANGELOG.md b/CHANGELOG.md index f087524..05e572c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## v1.7.19 +## v1.8.01 1. Add: Cursor Account Info | 增加 Cursor 賬號信息 2. Fix: Disable Auto Update | 修復禁用自動更新 3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持 From 4485cc55719c484a6a5fae2f4c94158272815a9d Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:47:24 +0800 Subject: [PATCH 18/47] Add support for 'Free Trial' membership type in format_subscription_type function --- cursor_acc_info.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cursor_acc_info.py b/cursor_acc_info.py index 5b34700..adf3bc4 100644 --- a/cursor_acc_info.py +++ b/cursor_acc_info.py @@ -238,6 +238,8 @@ def format_subscription_type(subscription_data: Dict) -> str: 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": @@ -262,6 +264,8 @@ def format_subscription_type(subscription_data: Dict) -> str: 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(): From 350d781ce891b8b41097e426dcd6c1c0d44a47b4 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 23:49:32 +0800 Subject: [PATCH 19/47] Update default version in build.yml to 1.8.02 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd90d39..3b01720 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.02' permissions: contents: write From c15ea25cb3acd5ebe327058d1057bbd81b3db29e Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 21:49:57 +0800 Subject: [PATCH 20/47] Update version to 1.8.02 in .env and CHANGELOG.md, adding new features and fixes including disabling auto-update, configuration options, and contributors options. --- .env | 4 +- CHANGELOG.md | 9 +++ config.py | 43 ++++++++++++ cursor_acc_info.py | 131 ++++++++++++++++++++++++++++------- disable_auto_update.py | 45 ++++++++++++ locales/en.json | 23 ++++-- locales/zh_cn.json | 24 +++++-- locales/zh_tw.json | 23 ++++-- logo.py | 2 +- main.py | 150 ++++++++++++++++++++++++++++++++++------ oauth_auth.py | 7 +- reset_machine_manual.py | 43 +++++++++++- 12 files changed, 436 insertions(+), 68 deletions(-) diff --git a/.env b/.env index 015bf6b..68c5d20 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.8.01 -VERSION=1.8.01 +version=1.8.02 +VERSION=1.8.02 diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e572c..c15f33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## v1.8.02 +1. Fix: Disable Auto Update | 修復禁用自動更新 +2. Add: Config Options | 增加配置選項 +3. Add: Contributors Options | 增加貢獻者選項 +4. Optimize Row & Colume Options | 優化行與列選項 +5. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID +6. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用 +7. Fix: Some Issues | 修復一些問題 + ## v1.8.01 1. Add: Cursor Account Info | 增加 Cursor 賬號信息 2. Fix: Disable Auto Update | 修復禁用自動更新 diff --git a/config.py b/config.py index 11feb02..808f103 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' } } @@ -114,6 +130,33 @@ def setup_config(translator=None): else: print(f"{Fore.RED}❌ Error setting up config: {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')}{Style.RESET_ALL}") + return + + print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.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')}{Style.RESET_ALL}" + elif value.lower() in ('false', 'no', 'off', '0'): + value_display = f"{Fore.RED}{translator.get('config.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')}: {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 index adf3bc4..5ce679c 100644 --- a/cursor_acc_info.py +++ b/cursor_acc_info.py @@ -64,12 +64,12 @@ class UsageManager: response.raise_for_status() data = response.json() - # 获取 GPT-4 使用量和限制 + # 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) - # 获取 GPT-3.5 使用量,但将限制设为 "No Limit" + # get Basic usage, but set limit to "No Limit" gpt35_data = data.get("gpt-3.5-turbo", {}) basic_usage = gpt35_data.get("numRequestsTotal", 0) @@ -77,10 +77,15 @@ class UsageManager: 'premium_usage': premium_usage, 'max_premium_usage': max_premium_usage, 'basic_usage': basic_usage, - 'max_basic_usage': "No Limit" # 将 GPT-3.5 的限制设为 "No Limit" + 'max_basic_usage': "No Limit" # set Basic limit to "No Limit" } except requests.RequestException as e: - logger.error(f"获取使用量失败: {str(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 @@ -95,7 +100,7 @@ class UsageManager: response.raise_for_status() return response.json() except requests.RequestException as e: - logger.error(f"获取订阅信息失败: {str(e)}") + logger.error(f"Get subscription info failed: {str(e)}") return None def get_token_from_config(): @@ -126,7 +131,7 @@ def get_token_from_config(): 'session_path': os.path.expanduser("~/.config/Cursor/Session Storage") } except Exception as e: - logger.error(f"获取配置路径失败: {str(e)}") + logger.error(f"Get config path failed: {str(e)}") return None @@ -339,9 +344,9 @@ def get_email_from_sqlite(sqlite_path): def display_account_info(translator=None): """display account info""" - print(f"\n{Fore.CYAN}{'─' * 40}{Style.RESET_ALL}") + 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}{'─' * 40}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}") # get token token = get_token() @@ -363,7 +368,11 @@ def display_account_info(translator=None): email = get_email_from_sqlite(paths['sqlite_path']) # get subscription info - subscription_info = UsageManager.get_stripe_profile(token) + 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: @@ -371,32 +380,43 @@ def display_account_info(translator=None): if 'customer' in subscription_info and 'email' in subscription_info['customer']: email = subscription_info['customer']['email'] - # get usage info - usage_info = UsageManager.get_usage(token) + # 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 - # display account info + # Prepare left and right info + left_info = [] + right_info = [] + + # Left side shows account info if email: - print(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}") + left_info.append(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}") + left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}") - # display subscription type + # Add an empty line + # left_info.append("") + + # Show subscription type if subscription_info: subscription_type = format_subscription_type(subscription_info) - print(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}") + left_info.append(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}") - # display remaining trial days + # Show remaining trial days days_remaining = subscription_info.get("daysRemainingOnTrial") if days_remaining is not None and days_remaining > 0: - print(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}") + 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: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}") + 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}") - # display usage info + # Right side shows usage info - only if available if usage_info: - print(f"\n{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}") + right_info.append(f"{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}") - # GPT-4 usage + # Premium usage premium_usage = usage_info.get('premium_usage', 0) max_premium_usage = usage_info.get('max_premium_usage', "No Limit") @@ -425,7 +445,7 @@ def display_account_info(translator=None): premium_display = f"{premium_usage}/{max_premium_usage} ({premium_percentage:.1f}%)" - print(f"{Fore.YELLOW}{EMOJI['PREMIUM']} {translator.get('account_info.premium_usage') if translator else 'Fast Response'}: {premium_color}{premium_display}{Style.RESET_ALL}") + 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) @@ -456,11 +476,70 @@ def display_account_info(translator=None): basic_display = f"{basic_usage}/{max_basic_usage} ({basic_percentage:.1f}%)" - print(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}") + 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: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.usage_not_found') if translator else 'Usage information not found'}{Style.RESET_ALL}") + # 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 - print(f"{Fore.CYAN}{'─' * 40}{Style.RESET_ALL}") + # 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""" diff --git a/disable_auto_update.py b/disable_auto_update.py index ba96fce..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() @@ -54,6 +56,45 @@ class AutoUpdateDisabler: } 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""" try: @@ -181,6 +222,10 @@ class AutoUpdateDisabler: 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 a23c1a4..5fddde1 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", @@ -106,7 +108,10 @@ "version_too_low": "Cursor Version Too Low: {version} < 0.45.0", "no_write_permission": "No Write Permission: {path}", "path_not_found": "Path Not Found: {path}", - "modify_file_failed": "Modify File Failed: {error}" + "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", @@ -308,7 +313,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", @@ -478,6 +484,15 @@ "active": "Active", "inactive": "Inactive", "premium_usage": "Premium Usage", - "basic_usage": "Basic 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" } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 6a78544..910941a 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": "英语", @@ -106,7 +108,10 @@ "version_too_low": "Cursor版本太低: {version} < 0.45.0", "no_write_permission": "没有写入权限: {path}", "path_not_found": "路径未找到: {path}", - "modify_file_failed": "修改文件失败: {error}" + "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 注册工具", @@ -303,7 +308,8 @@ "update_skipped": "跳过更新。", "invalid_choice": "选择无效。请输入 'Y' 或 'n'.", "development_version": "开发版本 {current} > {latest}", - "changelog_title": "更新日志" + "changelog_title": "更新日志", + "rate_limit_exceeded": "GitHub API 速率限制超过。跳过更新检查。" }, "totally_reset": { "title": "完全重置 Cursor", @@ -473,7 +479,15 @@ "active": "活跃", "inactive": "非活跃", "premium_usage": "高级使用量", - "basic_usage": "基础使用量" + "basic_usage": "基础使用量", + "usage_not_found": "使用量未找到", + "lifetime_access_enabled": "永久访问已启用" + }, + "config": { + "config_not_available": "配置未找到。", + "configuration": "配置", + "enabled": "已启用", + "disabled": "已禁用", + "config_directory": "配置目录" } - } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index ba91c70..8cfb910 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": "英文", @@ -104,7 +106,10 @@ "version_too_low": "Cursor版本太低: {version} < 0.45.0", "no_write_permission": "沒有寫入權限: {path}", "path_not_found": "路徑未找到: {path}", - "modify_file_failed": "修改文件失敗: {error}" + "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": { @@ -282,7 +287,8 @@ "update_skipped": "跳過更新。", "invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.", "development_version": "開發版本 {current} > {latest}", - "changelog_title": "更新日誌" + "changelog_title": "更新日誌", + "rate_limit_exceeded": "GitHub API 速率限制超過。跳過更新檢查。" }, "totally_reset": { "title": "完全重置 Cursor", @@ -452,6 +458,15 @@ "active": "活躍", "inactive": "非活躍", "premium_usage": "高級使用量", - "basic_usage": "基礎使用量" + "basic_usage": "基礎使用量", + "usage_not_found": "使用量未找到", + "lifetime_access_enabled": "永久訪問已啟用" + }, + "config": { + "config_not_available": "配置未找到。", + "configuration": "配置", + "enabled": "已啟用", + "disabled": "已禁用", + "config_directory": "配置目錄" } } \ No newline at end of file diff --git a/logo.py b/logo.py index 7e8f1cd..f054571 100644 --- a/logo.py +++ b/logo.py @@ -94,7 +94,7 @@ 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__": diff --git a/main.py b/main.py index 4a0af37..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 @@ -243,27 +251,110 @@ translator = Translator() def print_menu(): """Print menu options""" try: - import cursor_acc_info - cursor_acc_info.display_account_info(translator) + 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""" @@ -303,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}") @@ -454,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}") @@ -507,6 +605,14 @@ def main(): 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}") print_menu() diff --git a/oauth_auth.py b/oauth_auth.py index f166a35..c27bcea 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -25,9 +25,10 @@ EMOJI = { } 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 @@ -380,7 +381,7 @@ class OAuthHandler: 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": + if self.auth_type == "google": return self.handle_google_auth() else: # github return self.handle_github_auth() @@ -839,7 +840,7 @@ 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}") diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 722800b..dacd537 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -25,7 +25,8 @@ EMOJI = { "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "🔄", + "RESET": "��", + "WARNING": "⚠️", } def get_cursor_paths(translator=None) -> Tuple[str, str]: @@ -576,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) @@ -605,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""" From 17799e020961c0a42de58dec8bc98c7507f4acc5 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 21:52:51 +0800 Subject: [PATCH 21/47] Add dugmail.com to block_domain.txt --- block_domain.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/block_domain.txt b/block_domain.txt index ea56852..72f02af 100644 --- a/block_domain.txt +++ b/block_domain.txt @@ -16,3 +16,4 @@ ikomail.com mailpull.com drewzen.com begemail.com +dugmail.com From bd9610791187d7209e33f91604fba324bad96b6f Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 21:55:21 +0800 Subject: [PATCH 22/47] Format new GUID in reset_machine_manual.py to include braces for consistency. --- reset_machine_manual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reset_machine_manual.py b/reset_machine_manual.py index dacd537..3be1e4d 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -613,7 +613,7 @@ class MachineIDResetter: try: import winreg # 1. Generate new GUID - new_guid = str(uuid.uuid4()).upper() + 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 From 087e3ebf78b236c2e4257f6006e3dbe46fd594b4 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 21:59:06 +0800 Subject: [PATCH 23/47] Add solerbe.net to block_domain.txt --- block_domain.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/block_domain.txt b/block_domain.txt index 72f02af..7811c24 100644 --- a/block_domain.txt +++ b/block_domain.txt @@ -17,3 +17,4 @@ mailpull.com drewzen.com begemail.com dugmail.com +solerbe.net \ No newline at end of file From e5b7e5727cc8f07a247c3afddba519ebf0dc1198 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:15:33 +0800 Subject: [PATCH 24/47] Enhance NewTempEmail functionality by integrating random service selection, improving email creation process with retries, and implementing API calls for domain and message handling. Add new_tempemail_smail.py to .gitignore. --- .gitignore | 1 + new_tempemail.py | 411 ++++++++++++++++++++++++++++++----------------- 2 files changed, 261 insertions(+), 151 deletions(-) diff --git a/.gitignore b/.gitignore index 654a570..e35942e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ build.py build.sh ENV/ test.py +new_tempemail_smail.py install.bat run.bat diff --git a/new_tempemail.py b/new_tempemail.py index b65573a..6eeed00 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -14,8 +14,16 @@ init() class NewTempEmail: def __init__(self, translator=None): self.translator = translator - self.page = None - self.setup_browser() + # Randomly choose between mail.tm and mail.gw + self.services = [ + {"name": "mail.tm", "api_url": "https://api.mail.tm"} + ] + self.selected_service = random.choice(self.services) + self.api_url = self.selected_service["api_url"] + self.token = None + self.email = None + self.password = None + self.blocked_domains = self.get_blocked_domains() def get_blocked_domains(self): """Get blocked domains list""" @@ -82,116 +90,166 @@ class NewTempEmail: return filtered_domains + def _generate_credentials(self): + """generate random username and password""" + username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10)) + password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12)) + return username, password - def get_extension_block(self): - """获取插件路径""" - root_dir = os.getcwd() - extension_path = os.path.join(root_dir, "PBlock") - - if hasattr(sys, "_MEIPASS"): - extension_path = os.path.join(sys._MEIPASS, "PBlock") - - if not os.path.exists(extension_path): - raise FileNotFoundError(f"插件不存在: {extension_path}") - - return extension_path - - def setup_browser(self): - """设置浏览器""" - try: - if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.starting_browser')}{Style.RESET_ALL}") - else: - print(f"{Fore.CYAN}ℹ️ 正在启动浏览器...{Style.RESET_ALL}") - - # 创建浏览器选项 - co = ChromiumOptions() - co.set_argument("--headless=new") - co.set_argument("--no-sandbox") - - - co.auto_port() # 自动设置端口 - - # 加载 uBlock 插件 - try: - extension_path = self.get_extension_block() - co.set_argument("--allow-extensions-in-incognito") - co.add_extension(extension_path) - except Exception as e: - if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.extension_load_error')}: {str(e)}{Style.RESET_ALL}") - else: - print(f"{Fore.YELLOW}⚠️ 加载插件失败: {str(e)}{Style.RESET_ALL}") - - self.page = ChromiumPage(co) - return True - except Exception as e: - if self.translator: - 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}") - return False - def create_email(self): """create temporary email""" - try: - if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site')}{Style.RESET_ALL}") - else: - print(f"{Fore.CYAN}ℹ️ 正在访问 smailpro.com...{Style.RESET_ALL}") - - # load blocked domains list - self.blocked_domains = self.get_blocked_domains() - - # visit website - self.page.get("https://smailpro.com/") - time.sleep(2) - - # click create email button - create_button = self.page.ele('xpath://button[@title="Create temporary email"]') - if create_button: - create_button.click() - time.sleep(1) - - # click Create button in popup - modal_create_button = self.page.ele('xpath://button[contains(text(), "Create")]') - if modal_create_button: - modal_create_button.click() - time.sleep(2) - - # get email address - modify selector - email_div = self.page.ele('xpath://div[@class="text-base sm:text-lg md:text-xl text-gray-700"]') - if email_div: - email = email_div.text.strip() - if '@' in email: # check if it's a valid email address - # check if domain is blocked - domain = email.split('@')[1] - if self.blocked_domains and domain in self.blocked_domains: + max_retries = 3 # Maximum number of retries + attempt = 0 # Current attempt count + + while attempt < max_retries: + attempt += 1 + try: + if self.translator: + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}") + else: + print(f"{Fore.CYAN}ℹ️ 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}") + + # Get available domain list + try: + domains_response = requests.get(f"{self.api_url}/domains", timeout=10) + if domains_response.status_code != 200: + print(f"{Fore.RED}❌ {self.translator.get('email.domains_list_error', error=domains_response.status_code)}{Style.RESET_ALL}") + print(f"{Fore.RED}❌ {self.translator.get('email.domains_list_error', error=domains_response.text)}{Style.RESET_ALL}") + raise Exception(f"{self.translator.get('email.failed_to_get_available_domains') if self.translator else 'Failed to get available domains'}") + + domains = domains_response.json()["hydra:member"] + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.available_domains_loaded', count=len(domains))}{Style.RESET_ALL}") + + if not domains: + raise Exception(f"{self.translator.get('email.no_available_domains') if self.translator else '没有可用域名'}") + except Exception as e: + print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}") + raise + + # Exclude blocked domains + try: + filtered_domains = self.exclude_blocked_domains(domains) + if self.translator: + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}") + else: + print(f"{Fore.CYAN}ℹ️ 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}") + + if not filtered_domains: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}") + + # Switch to another service + for service in self.services: + if service["api_url"] != self.api_url: + self.selected_service = service + self.api_url = service["api_url"] if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.domain_blocked')}: {domain}{Style.RESET_ALL}") + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}⚠️ 域名已被屏蔽: {domain},尝试重新创建邮箱{Style.RESET_ALL}") - # create email again - return self.create_email() - - if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}") - else: - print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}") - return email - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.create_failed')}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 创建邮箱失败{Style.RESET_ALL}") - return None - - except Exception as e: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}") - return None - + print(f"{Fore.CYAN}ℹ️ 切换到 {service['name']} 服务{Style.RESET_ALL}") + return self.create_email() # Recursively call + + raise Exception(f"{self.translator.get('email.no_available_domains_after_filtering') if self.translator else '过滤后没有可用域名'}") + except Exception as e: + print(f"{Fore.RED}❌ 过滤域名时出错: {str(e)}{Style.RESET_ALL}") + raise + + # Generate random username and password + try: + username, password = self._generate_credentials() + self.password = password + + # Create email account + selected_domain = filtered_domains[0]['domain'] + email = f"{username}@{selected_domain}" + + if self.translator: + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.trying_to_create_email', email=email)}{Style.RESET_ALL}") + else: + print(f"{Fore.CYAN}ℹ️ 尝试创建邮箱: {email}{Style.RESET_ALL}") + + account_data = { + "address": email, + "password": password + } + except Exception as e: + print(f"{Fore.RED}❌ 生成凭据时出错: {str(e)}{Style.RESET_ALL}") + raise + + # Create account + try: + create_response = requests.post(f"{self.api_url}/accounts", json=account_data, timeout=15) + + if create_response.status_code != 201: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=create_response.status_code)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 创建账户失败: 状态码 {create_response.status_code}{Style.RESET_ALL}") + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=create_response.text)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 响应内容: {create_response.text}{Style.RESET_ALL}") + + # If it's a domain problem, try the next available domain + if len(filtered_domains) > 1 and ("domain" in create_response.text.lower() or "address" in create_response.text.lower()): + print(f"{Fore.YELLOW}⚠️ 尝试使用下一个可用域名...{Style.RESET_ALL}") + # Add current domain to blocked list + if selected_domain not in self.blocked_domains: + self.blocked_domains.append(selected_domain) + # Recursively call yourself + return self.create_email() + + raise Exception(f"{self.translator.get('email.failed_to_create_account') if self.translator else '创建账户失败'}") + except Exception as e: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=str(e))}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}") + raise + + # Get access token + try: + token_data = { + "address": email, + "password": password + } + + token_response = requests.post(f"{self.api_url}/token", json=token_data, timeout=10) + if token_response.status_code != 200: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_get_access_token', error=token_response.status_code)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 获取令牌失败: 状态码 {token_response.status_code}{Style.RESET_ALL}") + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_get_access_token', error=token_response.text)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 响应内容: {token_response.text}{Style.RESET_ALL}") + raise Exception(f"{self.translator.get('email.failed_to_get_access_token') if self.translator else '获取访问令牌失败'}") + + self.token = token_response.json()["token"] + self.email = email + except Exception as e: + print(f"{Fore.RED}❌ 获取令牌时出错: {str(e)}{Style.RESET_ALL}") + raise + + if self.translator: + print(f"{Fore.GREEN}✅ {self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}") + return email + + except Exception as e: + if attempt < max_retries: + print(f"{Fore.YELLOW}⚠️ 尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") + else: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}") + return None + def close(self): """close browser""" if self.page: @@ -205,11 +263,11 @@ class NewTempEmail: else: print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}") - # click refresh button - refresh_button = self.page.ele('xpath://button[@id="refresh"]') - if refresh_button: - refresh_button.click() - time.sleep(2) # wait for refresh to complete + # Use API to get latest email + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.api_url}/messages", headers=headers) + + if response.status_code == 200: if self.translator: print(f"{Fore.GREEN}✅ {self.translator.get('email.refresh_success')}{Style.RESET_ALL}") else: @@ -217,9 +275,9 @@ class NewTempEmail: return True if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.refresh_button_not_found')}{Style.RESET_ALL}") + print(f"{Fore.RED}❌ {self.translator.get('email.refresh_failed')}{Style.RESET_ALL}") else: - print(f"{Fore.RED}❌ 未找到刷新按钮{Style.RESET_ALL}") + print(f"{Fore.RED}❌ 刷新邮箱失败{Style.RESET_ALL}") return False except Exception as e: @@ -230,19 +288,26 @@ class NewTempEmail: return False def check_for_cursor_email(self): - """检查是否有 Cursor 的验证邮件""" + """Check if there is a Cursor verification email""" try: - # find verification email - use more accurate selector - email_div = self.page.ele('xpath://div[contains(@class, "p-2") and contains(@class, "cursor-pointer") and contains(@class, "bg-white") and contains(@class, "shadow") and .//b[text()="no-reply@cursor.sh"] and .//span[text()="Verify your email address"]]') - if email_div: - if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_found')}{Style.RESET_ALL}") - else: - print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}") - # use JavaScript to click element - self.page.run_js('arguments[0].click()', email_div) - time.sleep(2) # wait for email content to load - return True + # Use API to get email list + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.api_url}/messages", headers=headers) + + if response.status_code == 200: + messages = response.json()["hydra:member"] + for message in messages: + if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: + # Get email content + message_id = message["id"] + message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) + if message_response.status_code == 200: + if self.translator: + print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_found')}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}") + return True + if self.translator: print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_not_found')}{Style.RESET_ALL}") else: @@ -256,31 +321,75 @@ class NewTempEmail: print(f"{Fore.RED}❌ 检查验证邮件出错: {str(e)}{Style.RESET_ALL}") return False - def get_verification_code(self): - """获取验证码""" - try: - # find verification code element - code_element = self.page.ele('xpath://td//div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px")]') - if code_element: - code = code_element.text.strip() - if code.isdigit() and len(code) == 6: + def get_verification_code(self, max_retries=3): + """get verification code with retry mechanism""" + for attempt in range(1, max_retries + 1): + try: + # Check if token is valid + if not self.token: if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.no_token_retry')}{Style.RESET_ALL}") else: - print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}") - return code - if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}") - else: - print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码{Style.RESET_ALL}") - return None - - except Exception as e: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}") - return None + print(f"{Fore.YELLOW}⚠️ 未获取到有效令牌,尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") + + # Try to recreate email + self.create_email() + if not self.token: + continue # Skip to next attempt if still no token + + # Use API to get email list + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.api_url}/messages", headers=headers) + + if response.status_code == 200: + messages = response.json()["hydra:member"] + for message in messages: + if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: + # Get email content + message_id = message["id"] + message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) + + if message_response.status_code == 200: + # Extract verification code from email content + email_content = message_response.json()["text"] + # Find 6-digit verification code + import re + code_match = re.search(r'\b\d{6}\b', email_content) + + if code_match: + code = code_match.group(0) + if self.translator: + print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}") + return code + + if attempt < max_retries: + wait_time = 10 * attempt # Increase wait time with each attempt + if self.translator: + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_retry', attempt=attempt, max=max_retries, wait=wait_time)}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码,将在 {wait_time} 秒后重试... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") + time.sleep(wait_time) + else: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 未找到有效的验证码{Style.RESET_ALL}") + except Exception as e: + if attempt < max_retries: + if self.translator: + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_error_retry', error=str(e), attempt=attempt, max=max_retries)}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}⚠️ 获取验证码出错: {str(e)},将重试... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") + time.sleep(get_random_wait_time(self.config, 'page_load_wait')) + else: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}") + + return None def main(translator=None): temp_email = NewTempEmail(translator) @@ -293,7 +402,7 @@ def main(translator=None): else: print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}") - # test refresh function + # Test refresh function while True: if translator: choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower() @@ -308,4 +417,4 @@ def main(translator=None): temp_email.close() if __name__ == "__main__": - main() \ No newline at end of file + main() \ No newline at end of file From 816a09d4de39ba2ba69b1280059b9e0c279fe66f Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:18:11 +0800 Subject: [PATCH 25/47] Refactor NewTempEmail class to enhance email creation and verification processes, including improved browser setup and error handling. Add new_tempemail_api.py to .gitignore. --- .gitignore | 1 + new_tempemail.py | 411 +++++++++++++++++------------------------------ 2 files changed, 152 insertions(+), 260 deletions(-) diff --git a/.gitignore b/.gitignore index e35942e..2e4e1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build.sh ENV/ test.py new_tempemail_smail.py +new_tempemail_api.py install.bat run.bat diff --git a/new_tempemail.py b/new_tempemail.py index 6eeed00..b65573a 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -14,16 +14,8 @@ init() class NewTempEmail: def __init__(self, translator=None): self.translator = translator - # Randomly choose between mail.tm and mail.gw - self.services = [ - {"name": "mail.tm", "api_url": "https://api.mail.tm"} - ] - self.selected_service = random.choice(self.services) - self.api_url = self.selected_service["api_url"] - self.token = None - self.email = None - self.password = None - self.blocked_domains = self.get_blocked_domains() + self.page = None + self.setup_browser() def get_blocked_domains(self): """Get blocked domains list""" @@ -90,166 +82,116 @@ class NewTempEmail: return filtered_domains - def _generate_credentials(self): - """generate random username and password""" - username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10)) - password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12)) - return username, password + def get_extension_block(self): + """获取插件路径""" + root_dir = os.getcwd() + extension_path = os.path.join(root_dir, "PBlock") + + if hasattr(sys, "_MEIPASS"): + extension_path = os.path.join(sys._MEIPASS, "PBlock") + + if not os.path.exists(extension_path): + raise FileNotFoundError(f"插件不存在: {extension_path}") + + return extension_path + + def setup_browser(self): + """设置浏览器""" + try: + if self.translator: + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.starting_browser')}{Style.RESET_ALL}") + else: + print(f"{Fore.CYAN}ℹ️ 正在启动浏览器...{Style.RESET_ALL}") + + # 创建浏览器选项 + co = ChromiumOptions() + co.set_argument("--headless=new") + co.set_argument("--no-sandbox") + + + co.auto_port() # 自动设置端口 + + # 加载 uBlock 插件 + try: + extension_path = self.get_extension_block() + co.set_argument("--allow-extensions-in-incognito") + co.add_extension(extension_path) + except Exception as e: + if self.translator: + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.extension_load_error')}: {str(e)}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}⚠️ 加载插件失败: {str(e)}{Style.RESET_ALL}") + + self.page = ChromiumPage(co) + return True + except Exception as e: + if self.translator: + 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}") + return False + def create_email(self): """create temporary email""" - max_retries = 3 # Maximum number of retries - attempt = 0 # Current attempt count - - while attempt < max_retries: - attempt += 1 - try: - if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}") - else: - print(f"{Fore.CYAN}ℹ️ 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}") - - # Get available domain list - try: - domains_response = requests.get(f"{self.api_url}/domains", timeout=10) - if domains_response.status_code != 200: - print(f"{Fore.RED}❌ {self.translator.get('email.domains_list_error', error=domains_response.status_code)}{Style.RESET_ALL}") - print(f"{Fore.RED}❌ {self.translator.get('email.domains_list_error', error=domains_response.text)}{Style.RESET_ALL}") - raise Exception(f"{self.translator.get('email.failed_to_get_available_domains') if self.translator else 'Failed to get available domains'}") - - domains = domains_response.json()["hydra:member"] - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.available_domains_loaded', count=len(domains))}{Style.RESET_ALL}") - - if not domains: - raise Exception(f"{self.translator.get('email.no_available_domains') if self.translator else '没有可用域名'}") - except Exception as e: - print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}") - raise - - # Exclude blocked domains - try: - filtered_domains = self.exclude_blocked_domains(domains) - if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}") - else: - print(f"{Fore.CYAN}ℹ️ 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}") - - if not filtered_domains: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}") - - # Switch to another service - for service in self.services: - if service["api_url"] != self.api_url: - self.selected_service = service - self.api_url = service["api_url"] + try: + if self.translator: + print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site')}{Style.RESET_ALL}") + else: + print(f"{Fore.CYAN}ℹ️ 正在访问 smailpro.com...{Style.RESET_ALL}") + + # load blocked domains list + self.blocked_domains = self.get_blocked_domains() + + # visit website + self.page.get("https://smailpro.com/") + time.sleep(2) + + # click create email button + create_button = self.page.ele('xpath://button[@title="Create temporary email"]') + if create_button: + create_button.click() + time.sleep(1) + + # click Create button in popup + modal_create_button = self.page.ele('xpath://button[contains(text(), "Create")]') + if modal_create_button: + modal_create_button.click() + time.sleep(2) + + # get email address - modify selector + email_div = self.page.ele('xpath://div[@class="text-base sm:text-lg md:text-xl text-gray-700"]') + if email_div: + email = email_div.text.strip() + if '@' in email: # check if it's a valid email address + # check if domain is blocked + domain = email.split('@')[1] + if self.blocked_domains and domain in self.blocked_domains: if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.domain_blocked')}: {domain}{Style.RESET_ALL}") else: - print(f"{Fore.CYAN}ℹ️ 切换到 {service['name']} 服务{Style.RESET_ALL}") - return self.create_email() # Recursively call - - raise Exception(f"{self.translator.get('email.no_available_domains_after_filtering') if self.translator else '过滤后没有可用域名'}") - except Exception as e: - print(f"{Fore.RED}❌ 过滤域名时出错: {str(e)}{Style.RESET_ALL}") - raise - - # Generate random username and password - try: - username, password = self._generate_credentials() - self.password = password - - # Create email account - selected_domain = filtered_domains[0]['domain'] - email = f"{username}@{selected_domain}" - - if self.translator: - print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.trying_to_create_email', email=email)}{Style.RESET_ALL}") - else: - print(f"{Fore.CYAN}ℹ️ 尝试创建邮箱: {email}{Style.RESET_ALL}") - - account_data = { - "address": email, - "password": password - } - except Exception as e: - print(f"{Fore.RED}❌ 生成凭据时出错: {str(e)}{Style.RESET_ALL}") - raise - - # Create account - try: - create_response = requests.post(f"{self.api_url}/accounts", json=account_data, timeout=15) - - if create_response.status_code != 201: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=create_response.status_code)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 创建账户失败: 状态码 {create_response.status_code}{Style.RESET_ALL}") - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=create_response.text)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 响应内容: {create_response.text}{Style.RESET_ALL}") - - # If it's a domain problem, try the next available domain - if len(filtered_domains) > 1 and ("domain" in create_response.text.lower() or "address" in create_response.text.lower()): - print(f"{Fore.YELLOW}⚠️ 尝试使用下一个可用域名...{Style.RESET_ALL}") - # Add current domain to blocked list - if selected_domain not in self.blocked_domains: - self.blocked_domains.append(selected_domain) - # Recursively call yourself - return self.create_email() - - raise Exception(f"{self.translator.get('email.failed_to_create_account') if self.translator else '创建账户失败'}") - except Exception as e: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_create_account', error=str(e))}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}") - raise - - # Get access token - try: - token_data = { - "address": email, - "password": password - } - - token_response = requests.post(f"{self.api_url}/token", json=token_data, timeout=10) - if token_response.status_code != 200: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_get_access_token', error=token_response.status_code)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 获取令牌失败: 状态码 {token_response.status_code}{Style.RESET_ALL}") - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.failed_to_get_access_token', error=token_response.text)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 响应内容: {token_response.text}{Style.RESET_ALL}") - raise Exception(f"{self.translator.get('email.failed_to_get_access_token') if self.translator else '获取访问令牌失败'}") - - self.token = token_response.json()["token"] - self.email = email - except Exception as e: - print(f"{Fore.RED}❌ 获取令牌时出错: {str(e)}{Style.RESET_ALL}") - raise - - if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}") - else: - print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}") - return email - - except Exception as e: - if attempt < max_retries: - print(f"{Fore.YELLOW}⚠️ 尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") - else: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}") - return None - + print(f"{Fore.YELLOW}⚠️ 域名已被屏蔽: {domain},尝试重新创建邮箱{Style.RESET_ALL}") + # create email again + return self.create_email() + + if self.translator: + print(f"{Fore.GREEN}✅ {self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}") + return email + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.create_failed')}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 创建邮箱失败{Style.RESET_ALL}") + return None + + except Exception as e: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}") + return None + def close(self): """close browser""" if self.page: @@ -263,11 +205,11 @@ class NewTempEmail: else: print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}") - # Use API to get latest email - headers = {"Authorization": f"Bearer {self.token}"} - response = requests.get(f"{self.api_url}/messages", headers=headers) - - if response.status_code == 200: + # click refresh button + refresh_button = self.page.ele('xpath://button[@id="refresh"]') + if refresh_button: + refresh_button.click() + time.sleep(2) # wait for refresh to complete if self.translator: print(f"{Fore.GREEN}✅ {self.translator.get('email.refresh_success')}{Style.RESET_ALL}") else: @@ -275,9 +217,9 @@ class NewTempEmail: return True if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.refresh_failed')}{Style.RESET_ALL}") + print(f"{Fore.RED}❌ {self.translator.get('email.refresh_button_not_found')}{Style.RESET_ALL}") else: - print(f"{Fore.RED}❌ 刷新邮箱失败{Style.RESET_ALL}") + print(f"{Fore.RED}❌ 未找到刷新按钮{Style.RESET_ALL}") return False except Exception as e: @@ -288,26 +230,19 @@ class NewTempEmail: return False def check_for_cursor_email(self): - """Check if there is a Cursor verification email""" + """检查是否有 Cursor 的验证邮件""" try: - # Use API to get email list - headers = {"Authorization": f"Bearer {self.token}"} - response = requests.get(f"{self.api_url}/messages", headers=headers) - - if response.status_code == 200: - messages = response.json()["hydra:member"] - for message in messages: - if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: - # Get email content - message_id = message["id"] - message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) - if message_response.status_code == 200: - if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_found')}{Style.RESET_ALL}") - else: - print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}") - return True - + # find verification email - use more accurate selector + email_div = self.page.ele('xpath://div[contains(@class, "p-2") and contains(@class, "cursor-pointer") and contains(@class, "bg-white") and contains(@class, "shadow") and .//b[text()="no-reply@cursor.sh"] and .//span[text()="Verify your email address"]]') + if email_div: + if self.translator: + print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_found')}{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}") + # use JavaScript to click element + self.page.run_js('arguments[0].click()', email_div) + time.sleep(2) # wait for email content to load + return True if self.translator: print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_not_found')}{Style.RESET_ALL}") else: @@ -321,75 +256,31 @@ class NewTempEmail: print(f"{Fore.RED}❌ 检查验证邮件出错: {str(e)}{Style.RESET_ALL}") return False - def get_verification_code(self, max_retries=3): - """get verification code with retry mechanism""" - for attempt in range(1, max_retries + 1): - try: - # Check if token is valid - if not self.token: + def get_verification_code(self): + """获取验证码""" + try: + # find verification code element + code_element = self.page.ele('xpath://td//div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px")]') + if code_element: + code = code_element.text.strip() + if code.isdigit() and len(code) == 6: if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.no_token_retry')}{Style.RESET_ALL}") + print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}") else: - print(f"{Fore.YELLOW}⚠️ 未获取到有效令牌,尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") - - # Try to recreate email - self.create_email() - if not self.token: - continue # Skip to next attempt if still no token - - # Use API to get email list - headers = {"Authorization": f"Bearer {self.token}"} - response = requests.get(f"{self.api_url}/messages", headers=headers) - - if response.status_code == 200: - messages = response.json()["hydra:member"] - for message in messages: - if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: - # Get email content - message_id = message["id"] - message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) - - if message_response.status_code == 200: - # Extract verification code from email content - email_content = message_response.json()["text"] - # Find 6-digit verification code - import re - code_match = re.search(r'\b\d{6}\b', email_content) - - if code_match: - code = code_match.group(0) - if self.translator: - print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}") - else: - print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}") - return code - - if attempt < max_retries: - wait_time = 10 * attempt # Increase wait time with each attempt - if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_retry', attempt=attempt, max=max_retries, wait=wait_time)}{Style.RESET_ALL}") - else: - print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码,将在 {wait_time} 秒后重试... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") - time.sleep(wait_time) - else: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 未找到有效的验证码{Style.RESET_ALL}") - except Exception as e: - if attempt < max_retries: - if self.translator: - print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_error_retry', error=str(e), attempt=attempt, max=max_retries)}{Style.RESET_ALL}") - else: - print(f"{Fore.YELLOW}⚠️ 获取验证码出错: {str(e)},将重试... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}") - time.sleep(get_random_wait_time(self.config, 'page_load_wait')) - else: - if self.translator: - print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}") - - return None + print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}") + return code + if self.translator: + print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码{Style.RESET_ALL}") + return None + + except Exception as e: + if self.translator: + print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}") + else: + print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}") + return None def main(translator=None): temp_email = NewTempEmail(translator) @@ -402,7 +293,7 @@ def main(translator=None): else: print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}") - # Test refresh function + # test refresh function while True: if translator: choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower() @@ -417,4 +308,4 @@ def main(translator=None): temp_email.close() if __name__ == "__main__": - main() \ No newline at end of file + main() \ No newline at end of file From ffff3bdeb19b7e73af615a4bb61b5cd6ae949e0b Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:22:43 +0800 Subject: [PATCH 26/47] Update block_domain.txt to include corhash.net and mailshou.com; modify new_signup.py and new_tempemail.py to conditionally set the --no-sandbox argument for Linux platforms. --- block_domain.txt | 4 +++- new_signup.py | 7 ++++--- new_tempemail.py | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/block_domain.txt b/block_domain.txt index 7811c24..e3412a6 100644 --- a/block_domain.txt +++ b/block_domain.txt @@ -17,4 +17,6 @@ mailpull.com drewzen.com begemail.com dugmail.com -solerbe.net \ No newline at end of file +solerbe.net +corhash.net +mailshou.com \ No newline at end of file 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 b65573a..307b301 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -107,7 +107,9 @@ class NewTempEmail: # 创建浏览器选项 co = ChromiumOptions() co.set_argument("--headless=new") - co.set_argument("--no-sandbox") + + if sys.platform == "linux": + co.set_argument("--no-sandbox") co.auto_port() # 自动设置端口 From 50d09e51723bedb09daa0af9e316c93359dc12ff Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:23:16 +0800 Subject: [PATCH 27/47] Update CHANGELOG.md to reflect the addition of a new temporary email feature, and fixes for Linux Chrome issues and other minor problems. --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15f33e..6574361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ 4. Optimize Row & Colume Options | 優化行與列選項 5. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID 6. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用 -7. Fix: Some Issues | 修復一些問題 +7. Add: New Temp Email | 增加新臨時郵箱 +8. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開 +9. Fix: Some Issues | 修復一些問題 ## v1.8.01 1. Add: Cursor Account Info | 增加 Cursor 賬號信息 From 4448a62458d2a5d2cd6709bea069f06e3c69ff37 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:29:27 +0800 Subject: [PATCH 28/47] Update CHANGELOG.md to reflect the reorganization of features and fixes for version 1.8.02, including the addition of new configuration options and enhancements to the temporary email functionality. --- CHANGELOG.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6574361..1beaab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,17 @@ # Change Log ## v1.8.02 -1. Fix: Disable Auto Update | 修復禁用自動更新 +1. Add: New Temp Email | 增加新臨時郵箱 2. Add: Config Options | 增加配置選項 -3. Add: Contributors Options | 增加貢獻者選項 -4. Optimize Row & Colume Options | 優化行與列選項 -5. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID -6. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用 -7. Add: New Temp Email | 增加新臨時郵箱 -8. Fix: Linux Chrome Not Open Correct | 修復 Linux Chrome 未正確打開 -9. Fix: Some Issues | 修復一些問題 +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 賬號信息 From d7ab362c4c7e2128dc3f7a918bb6377d7f664218 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 22:30:59 +0800 Subject: [PATCH 29/47] Update README.md to reflect support for the latest 0.48.x version and add new configuration options for checking updates and displaying account information. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a4cf27..fc954d5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![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 register accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes,reset and wipe Cursor data and hardware info. @@ -159,6 +159,12 @@ 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 ``` From dbc220053d28414835977ea00395efedeee857eb Mon Sep 17 00:00:00 2001 From: Nigel1992 Date: Sat, 29 Mar 2025 23:53:45 +0100 Subject: [PATCH 30/47] fix: improve Linux path handling for config file detection --- config.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index 808f103..1f75a86 100644 --- a/config.py +++ b/config.py @@ -68,6 +68,9 @@ def setup_config(translator=None): '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")), @@ -77,17 +80,81 @@ def setup_config(translator=None): 'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"), 'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml" } + # Create storage directory + os.makedirs(os.path.dirname(default_config['MacPaths']['storage_path']), exist_ok=True) + elif sys.platform == "linux": - sudo_user = os.environ.get('SUDO_USER') - actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") + # Get the actual user's home directory, handling both sudo and normal cases + current_user = os.getenv('USER') or os.getenv('USERNAME') or os.getenv('SUDO_USER') + if not current_user: + current_user = os.path.expanduser('~').split('/')[-1] + + actual_home = f"/home/{current_user}" + if not os.path.exists(actual_home): + actual_home = os.path.expanduser("~") + + # Define Linux paths + storage_path = os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")) + storage_dir = os.path.dirname(storage_path) + + # Verify paths and permissions + try: + # Check if Cursor config directory exists + cursor_config_dir = os.path.join(actual_home, ".config/cursor") + if not os.path.exists(cursor_config_dir): + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Cursor config directory not found: {cursor_config_dir}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + + # Check storage directory + if not os.path.exists(storage_dir): + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage directory not found: {storage_dir}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + + # Check storage.json with more detailed verification + if os.path.exists(storage_path): + # Get file stats + try: + stat = os.stat(storage_path) + print(f"{Fore.GREEN}{EMOJI['INFO']} Storage file found: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File size: {stat.st_size} bytes{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File permissions: {oct(stat.st_mode & 0o777)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File owner: {stat.st_uid}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File group: {stat.st_gid}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 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']} Permission denied: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running: chown {current_user}:{current_user} {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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']} Storage file is empty: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Storage file is valid and contains data{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} Error reading storage file: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} The file might be corrupted. Please reinstall Cursor{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage file not found: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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']} Error checking Linux paths: {str(e)}{Style.RESET_ALL}") default_config['LinuxPaths'] = { - 'storage_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")), + 'storage_path': storage_path, '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"), + 'machine_id_path': os.path.join(actual_home, ".config/cursor/machineid"), 'cursor_path': get_linux_cursor_path(), - 'updater_path': os.path.expanduser("~/.config/cursor-updater"), - 'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml" + 'updater_path': os.path.join(actual_home, ".config/cursor-updater"), + 'update_yml_path': os.path.join(actual_home, ".config/cursor/resources/app-update.yml") } # Read existing configuration and merge From f00678ddcbd8421f65efbd77037eecfbe70a3fac Mon Sep 17 00:00:00 2001 From: Nigel1992 Date: Sat, 29 Mar 2025 23:59:04 +0100 Subject: [PATCH 31/47] fix: add case-insensitive handling for Cursor/cursor directory --- config.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/config.py b/config.py index 1f75a86..82dfdd8 100644 --- a/config.py +++ b/config.py @@ -93,25 +93,34 @@ def setup_config(translator=None): if not os.path.exists(actual_home): actual_home = os.path.expanduser("~") - # Define Linux paths - storage_path = os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")) - storage_dir = os.path.dirname(storage_path) + # Define base config directory + config_base = os.path.join(actual_home, ".config") + + # Try both "Cursor" and "cursor" directory names + cursor_dir = None + for dir_name in ["Cursor", "cursor"]: + test_dir = os.path.join(config_base, dir_name) + if os.path.exists(test_dir): + cursor_dir = test_dir + break + + if not cursor_dir: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Neither Cursor nor cursor directory found in {config_base}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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 if Cursor config directory exists - cursor_config_dir = os.path.join(actual_home, ".config/cursor") - if not os.path.exists(cursor_config_dir): - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Cursor config directory not found: {cursor_config_dir}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") - # Check storage directory - if not os.path.exists(storage_dir): + if storage_dir and not os.path.exists(storage_dir): print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage directory not found: {storage_dir}{Style.RESET_ALL}") print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") # Check storage.json with more detailed verification - if os.path.exists(storage_path): + if storage_path and os.path.exists(storage_path): # Get file stats try: stat = os.stat(storage_path) @@ -141,20 +150,21 @@ def setup_config(translator=None): except Exception as e: print(f"{Fore.RED}{EMOJI['ERROR']} Error reading storage file: {str(e)}{Style.RESET_ALL}") print(f"{Fore.YELLOW}{EMOJI['INFO']} The file might be corrupted. Please reinstall Cursor{Style.RESET_ALL}") - else: + elif storage_path: print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage file not found: {storage_path}{Style.RESET_ALL}") print(f"{Fore.YELLOW}{EMOJI['INFO']} 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']} Error checking Linux paths: {str(e)}{Style.RESET_ALL}") + # Define all paths using the found cursor directory default_config['LinuxPaths'] = { 'storage_path': storage_path, - 'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")), - 'machine_id_path': os.path.join(actual_home, ".config/cursor/machineid"), + '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.join(actual_home, ".config/cursor-updater"), - 'update_yml_path': os.path.join(actual_home, ".config/cursor/resources/app-update.yml") + '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 From 0c0ec47432e16449d9a62f671726da34be33e5df Mon Sep 17 00:00:00 2001 From: Lostata <160412285+Lostata@users.noreply.github.com> Date: Sun, 30 Mar 2025 09:05:46 +0300 Subject: [PATCH 32/47] Fixed grammar errors in README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fc954d5..1f62b46 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,13 @@

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

-This tool register accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes,reset and wipe Cursor data and hardware info. +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 cache and cookies. If possible, user a VPN to create new accounts. - +Always clean your browser's cache and cookies. If possible, use a VPN to create new accounts. 這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。 @@ -101,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 | 注意事項 @@ -116,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] @@ -169,7 +168,7 @@ 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
From cb202e08e5d5644d32ac83e147c9f5a769c8aab8 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Mon, 31 Mar 2025 00:58:51 +0800 Subject: [PATCH 33/47] refactor: enhance configuration error messages with translation support and improve user feedback --- config.py | 52 ++++++++++++++++++++++------------------------ locales/en.json | 23 +++++++++++++++++++- locales/zh_cn.json | 23 +++++++++++++++++++- locales/zh_tw.json | 23 +++++++++++++++++++- 4 files changed, 91 insertions(+), 30 deletions(-) diff --git a/config.py b/config.py index 82dfdd8..2fb5954 100644 --- a/config.py +++ b/config.py @@ -105,8 +105,8 @@ def setup_config(translator=None): break if not cursor_dir: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Neither Cursor nor cursor directory found in {config_base}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.neither_cursor_nor_cursor_directory_found', config_base=config_base)}{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')}{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 "" @@ -116,46 +116,46 @@ def setup_config(translator=None): try: # Check storage directory if storage_dir and not os.path.exists(storage_dir): - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage directory not found: {storage_dir}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_directory_not_found', storage_dir=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')}{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']} Storage file found: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File size: {stat.st_size} bytes{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File permissions: {oct(stat.st_mode & 0o777)}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File owner: {stat.st_uid}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File group: {stat.st_gid}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.storage_file_found', storage_path=storage_path)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', stat.st_size)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', oct(stat.st_mode & 0o777))}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', stat.st_uid)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', stat.st_gid)}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error getting file stats: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_getting_file_stats', error=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']} Permission denied: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running: chown {current_user}:{current_user} {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} And: chmod 644 {storage_path}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.permission_denied', storage_path=storage_path)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', current_user=current_user, storage_path=storage_path)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and', storage_path=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']} Storage file is empty: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_is_empty', storage_path=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')}{Style.RESET_ALL}") else: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Storage file is valid and contains data{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data')}{Style.RESET_ALL}") except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error reading storage file: {str(e)}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} The file might be corrupted. Please reinstall Cursor{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_reading_storage_file', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor')}{Style.RESET_ALL}") elif storage_path: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage file not found: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_not_found', storage_path=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')}{Style.RESET_ALL}") except (OSError, IOError) as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error checking Linux paths: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_checking_linux_paths', error=str(e))}{Style.RESET_ALL}") # Define all paths using the found cursor directory default_config['LinuxPaths'] = { @@ -181,13 +181,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('register.config_option_added', option=f'{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('register.config_updated')}{Style.RESET_ALL}") else: for section, options in default_config.items(): config.add_section(section) @@ -197,15 +197,13 @@ 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('register.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('register.config_setup_error', error=str(e))}{Style.RESET_ALL}") return None def print_config(config, translator=None): diff --git a/locales/en.json b/locales/en.json index 5fddde1..c3bef05 100644 --- a/locales/en.json +++ b/locales/en.json @@ -493,6 +493,27 @@ "configuration": "Configuration", "enabled": "Enabled", "disabled": "Disabled", - "config_directory": "Config Directory" + "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}", + "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 'cursor --help' to check if it's installed", + "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}" + } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 910941a..05710fe 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -488,6 +488,27 @@ "configuration": "配置", "enabled": "已启用", "disabled": "已禁用", - "config_directory": "配置目录" + "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": "尝试运行 'cursor --help' 检查是否已安装", + "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": "配置设置错误" } + } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 8cfb910..c88ae6e 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -467,6 +467,27 @@ "configuration": "配置", "enabled": "已啟用", "disabled": "已禁用", - "config_directory": "配置目錄" + "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": "嘗試運行 'cursor --help' 檢查是否已安裝", + "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": "配置設置錯誤" + } } \ No newline at end of file From 650e2d33db23264a6586cda387bcd9af774304c5 Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Mon, 31 Mar 2025 01:01:02 +0800 Subject: [PATCH 34/47] Update .env --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 68c5d20..5b2edca 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.8.02 -VERSION=1.8.02 +version=1.8.03 +VERSION=1.8.03 From e2d70c173838b600695a1d5c1b3e31b9ce803b5e Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Mon, 31 Mar 2025 01:03:13 +0800 Subject: [PATCH 35/47] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1beaab7..0615c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 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 | 增加配置選項 From 172d9fb4bb2a851079c9d5b1fa144ea63f0daa7e Mon Sep 17 00:00:00 2001 From: Prathmesh Barot Date: Sun, 30 Mar 2025 23:23:10 +0530 Subject: [PATCH 36/47] Update totally_reset_cursor.py face 1 wait for face 3 1/3 --- totally_reset_cursor.py | 468 ++++++---------------------------------- 1 file changed, 71 insertions(+), 397 deletions(-) diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index 378b776..bd6fd74 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -1,254 +1,57 @@ -import os + input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}")import os import shutil import platform -import time import sys -import glob -import json import uuid -import random -import string -import re +import json from datetime import datetime -import subprocess from colorama import Fore, Style, init -from main import translator -from main import EMOJI # Initialize colorama init() -# Define emoji and color constants +# Define simplified emoji constants EMOJI = { - "FILE": "📄", - "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", "RESET": "🔄", - "MENU": "📋", - "ARROW": "➜", - "LANG": "🌐", - "UPDATE": "🔄", - "ADMIN": "🔐", - "STOP": "🛑", - "DISCLAIMER": "⚠️", "WARNING": "⚠️" } -def display_banner(): - """Displays a stylized banner for the tool.""" - print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.title')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") - -def display_features(): - """Displays the features of the Cursor AI Reset Tool.""" - print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('totally_reset.feature_title')}{Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_1')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_2')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_3')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_4')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_5')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_6')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_7')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_8')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_9')} {Style.RESET_ALL}\n") - -def display_disclaimer(): - """Displays a disclaimer for the user.""" - print(f"\n{Fore.RED}{EMOJI['DISCLAIMER']} {translator.get('totally_reset.disclaimer_title')}{Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_1')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_2')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_3')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_4')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_5')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_6')} {Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_7')} {Style.RESET_ALL} \n") - def get_confirmation(): """Gets confirmation from the user to proceed.""" - while True: - choice = input(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.confirm_title')} (Y/n): ").strip().lower() - if choice == "y" or choice == "": - return True - elif choice == "n": - return False - else: - print(f"{EMOJI['ERROR']} {translator.get('totally_reset.invalid_choice')}") + choice = input(f"{Fore.RED}{EMOJI['WARNING']} Confirm Cursor AI reset (y/n): ").strip().lower() + return choice == "y" or choice == "" -def remove_dir(path): - """Removes a directory if it exists and logs the action.""" - # Safety check to ensure we're only deleting Cursor-related directories - if not is_cursor_related(path): - print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") +def remove_path(path): + """Removes a directory or file if it exists.""" + if not path or not os.path.exists(path): return - if os.path.exists(path): - try: - shutil.rmtree(path, ignore_errors=True) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") - else: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") - -def remove_file(path): - """Removes a file if it exists and logs the action.""" - # Safety check to ensure we're only deleting Cursor-related files - if not is_cursor_related(path): - print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}") - return - - if os.path.isfile(path): - try: - os.remove(path) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}") - else: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}") - -def is_cursor_related(path): - """ - Safety function to verify a path is related to Cursor before deletion. - Returns True if the path appears to be related to Cursor AI. - """ - # Skip .vscode check as it's shared with VS Code - if path.endswith(".vscode"): - return False - - # Check if path contains cursor-related terms - cursor_terms = ["cursor", "cursorai", "cursor-electron"] - - # Convert path to lowercase for case-insensitive matching - lower_path = path.lower() - - # Return True if any cursor term is present in the path - for term in cursor_terms: - if term in lower_path: - return True - - # Check specific known Cursor file patterns - cursor_patterns = [ - r"\.cursor_.*$", - r"cursor-.*\.json$", - r"cursor_.*\.json$", - r"cursor-machine-id$", - r"trial_info\.json$", - r"license\.json$" - ] - - for pattern in cursor_patterns: - if re.search(pattern, lower_path): - return True - - # If it's a specific file that we know is only for Cursor - if os.path.basename(lower_path) in [ - "cursor_trial_data", - "cursor-state.json", - "cursor-machine-id", - "ai-settings.json", - "cursor.desktop" - ]: - return True - - return False - -def find_cursor_license_files(base_path, pattern): - """Finds files matching a pattern that might contain license information.""" try: - matches = [] - for root, dirnames, filenames in os.walk(base_path): - for filename in filenames: - # Check if filename matches any pattern before adding to matches - if any(p.lower() in filename.lower() for p in pattern): - full_path = os.path.join(root, filename) - # Extra safety check to ensure it's cursor-related - if is_cursor_related(full_path): - matches.append(full_path) - return matches + if os.path.isfile(path): + os.remove(path) + else: + shutil.rmtree(path, ignore_errors=True) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Deleted: {path} {Style.RESET_ALL}") except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.error_searching', path=base_path, error=str(e))} {Style.RESET_ALL}") - return [] - -def generate_new_machine_id(): - """Generates a new random machine ID.""" - return str(uuid.uuid4()) + print(f"{Fore.RED}{EMOJI['ERROR']} Error deleting {path}: {str(e)} {Style.RESET_ALL}") def create_fake_machine_id(path): """Creates a new machine ID file with random ID.""" - if not is_cursor_related(path): - return - try: - new_id = generate_new_machine_id() directory = os.path.dirname(path) - - # Ensure directory exists if not os.path.exists(directory): os.makedirs(directory) - with open(path, 'w') as f: - f.write(new_id) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_machine_id', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_machine_id', path=path, error=str(e))} {Style.RESET_ALL}") + f.write(str(uuid.uuid4())) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created new machine ID: {path} {Style.RESET_ALL}") + except Exception: + pass -def reset_machine_id(system, home): - """Resets machine ID in all possible locations.""" - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_machine_id')} {Style.RESET_ALL}") - - # Common machine ID locations based on OS - if system == "Windows": - machine_id_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Roaming", "cursor-electron", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - elif system == "Darwin": # macOS - machine_id_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - os.path.join(home, "Library", "Application Support", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - elif system == "Linux": - machine_id_paths = [ - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - os.path.join(home, ".config", "cursor-electron", "cursor-machine-id"), - os.path.join(home, ".cursor-machine-id"), - ] - - # First remove existing machine IDs - for path in machine_id_paths: - remove_file(path) - - # Then create new randomized IDs - for path in machine_id_paths: - create_fake_machine_id(path) - - # Try to reset system machine ID if possible (with appropriate permissions) - if system == "Windows": - try: - # Windows: Create a temporary VBS script to reset machine GUID - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_machine_id_reset_may_require_running_as_administrator')} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.windows_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") - - elif system == "Linux": - try: - # Linux: Create a random machine-id in /etc/ (needs sudo) - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_system_machine_id_reset_may_require_sudo_privileges')} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.linux_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}") - -def create_fake_trial_info(path, system, home): +def create_fake_trial_info(path): """Creates fake trial information to extend trial period.""" - if not is_cursor_related(path): - return - try: # Generate future expiry date (90 days from now) future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds @@ -258,37 +61,41 @@ def create_fake_trial_info(path, system, home): "trialStartTimestamp": datetime.now().timestamp() * 1000, "trialEndTimestamp": future_date, "hasUsedTrial": False, - "machineId": generate_new_machine_id() + "machineId": str(uuid.uuid4()) } directory = os.path.dirname(path) - - # Ensure directory exists if not os.path.exists(directory): os.makedirs(directory) with open(path, 'w') as f: json.dump(fake_trial, f) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_extended_trial_info', path=path)} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_trial_info', path=path, error=str(e))} {Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created extended trial: {path} {Style.RESET_ALL}") + except Exception: + pass def reset_cursor(): - """Completely resets Cursor AI by removing all settings, caches, and extensions.""" + """Reset Cursor AI by removing settings, caches, and creating new identifiers.""" system = platform.system() home = os.path.expanduser("~") - display_banner() - display_features() - display_disclaimer() + print(f"\n{Fore.CYAN}====== Cursor AI Reset Tool ======{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['WARNING']} This will reset Cursor AI completely:") + print(f"• All settings, cache, and identifiers will be removed") + print(f"• Data may be unrecoverable") + print(f"• You may need to reinstall Cursor after reset{Style.RESET_ALL}\n") if not get_confirmation(): - print(f"\n{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.reset_cancelled')} {Style.RESET_ALL}") + print(f"\n{Fore.CYAN}Reset cancelled.{Style.RESET_ALL}") return - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor_ai_editor')} {Style.RESET_ALL}") + print(f"\n{Fore.CYAN}{EMOJI['RESET']} Resetting Cursor AI...{Style.RESET_ALL}") # Define paths based on OS + cursor_paths = [] + machine_id_paths = [] + trial_info_path = "" + if system == "Windows": cursor_paths = [ os.path.join(home, "AppData", "Roaming", "Cursor"), @@ -297,23 +104,19 @@ def reset_cursor(): os.path.join(home, "AppData", "Local", "cursor-electron"), os.path.join(home, "AppData", "Local", "CursorAI"), os.path.join(home, "AppData", "Roaming", "CursorAI"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - os.path.join(home, "AppData", "Local", "Temp", "Cursor"), # Temporary data + os.path.join(home, "AppData", "Local", "Temp", "Cursor"), os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"), - os.path.join(home, "AppData", "Local", "Programs", "cursor"), + os.path.join(home, ".cursor_trial_data"), + os.path.join(home, ".cursor_license"), + os.path.join(home, ".cursor-machine-id"), + os.path.join(home, ".cursor-state.json"), ] - - # Additional locations for license/trial files on Windows - license_search_paths = [ - os.path.join(home, "AppData", "Roaming"), - os.path.join(home, "AppData", "Local"), - os.path.join(home, "AppData", "LocalLow"), + machine_id_paths = [ + os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), + os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), ] + trial_info_path = os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json") - # Registry instructions for Windows - print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions')} {Style.RESET_ALL}") - print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions_2')} {Style.RESET_ALL}") - elif system == "Darwin": # macOS cursor_paths = [ os.path.join(home, "Library", "Application Support", "Cursor"), @@ -324,18 +127,15 @@ def reset_cursor(): os.path.join(home, "Library", "Preferences", "cursor-electron"), os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"), os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"), - os.path.join(home, "Library", "WebKit", "com.cursor.Cursor"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - "/Applications/Cursor.app", # Main application location + os.path.join(home, ".cursor_trial_data"), + os.path.join(home, ".cursor_license"), + os.path.join(home, ".cursor-machine-id"), ] + machine_id_paths = [ + os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), + ] + trial_info_path = os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json") - # Additional locations for license/trial files on macOS - license_search_paths = [ - os.path.join(home, "Library", "Application Support"), - os.path.join(home, "Library", "Preferences"), - os.path.join(home, "Library", "Caches"), - ] - elif system == "Linux": cursor_paths = [ os.path.join(home, ".config", "Cursor"), @@ -344,166 +144,40 @@ def reset_cursor(): os.path.join(home, ".cache", "cursor-electron"), os.path.join(home, ".local", "share", "Cursor"), os.path.join(home, ".local", "share", "cursor-electron"), - # os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code - os.path.join(home, ".local", "share", "applications", "cursor.desktop"), - os.path.join("/usr", "share", "applications", "cursor.desktop"), - os.path.join("/opt", "Cursor"), + os.path.join(home, ".cursor_trial_data"), + os.path.join(home, ".cursor_license"), + os.path.join(home, ".cursor-machine-id"), ] - - # Additional locations for license/trial files on Linux - license_search_paths = [ - os.path.join(home, ".config"), - os.path.join(home, ".local", "share"), - os.path.join(home, ".cache"), + machine_id_paths = [ + os.path.join(home, ".config", "Cursor", "cursor-machine-id"), ] - + trial_info_path = os.path.join(home, ".config", "Cursor", "trial_info.json") else: - print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')} {Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} Unsupported OS: {system} {Style.RESET_ALL}") return - # Remove main Cursor directories - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_main_cursor_directories_and_files')} {Style.RESET_ALL}") + # Remove main Cursor directories and files for path in cursor_paths: - remove_dir(path) + remove_path(path) - # Reset machine identifiers (this creates new ones) - reset_machine_id(system, home) + # Create new machine IDs + print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new machine identifiers...{Style.RESET_ALL}") + for path in machine_id_paths: + create_fake_machine_id(path) - # Known trial/license file patterns - file_patterns = [ - ".cursor_trial_data", - "trial_info.json", - "license.json", - "cursor-license", - "cursor_license", - "cursor-auth", - "cursor_auth", - "cursor_subscription", - "cursor-subscription", - "cursor-state", - "cursorstate", - "cursorsettings", - "cursor-settings", - "ai-settings.json", - "cursor-machine-id", - "cursor_machine_id", - "cursor-storage" - ] + # Create new trial with extended expiration + print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new trial information...{Style.RESET_ALL}") + create_fake_trial_info(trial_info_path) - # Direct known trial file paths - cursor_trial_files = [ - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - os.path.join(home, ".cursor-state.json"), - ] - - # OS-specific known trial/license files - if system == "Windows": - cursor_trial_files.extend([ - os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), - os.path.join(home, "AppData", "Local", "Cursor", "license.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "license.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "ai-settings.json"), - os.path.join(home, "AppData", "Roaming", "Cursor", "ai-settings.json"), - ]) - elif system == "Darwin": # macOS - cursor_trial_files.extend([ - os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), - os.path.join(home, "Library", "Application Support", "Cursor", "license.json"), - os.path.join(home, "Library", "Preferences", "Cursor", "trial_info.json"), - os.path.join(home, "Library", "Preferences", "Cursor", "license.json"), - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - os.path.join(home, "Library", "Application Support", "Cursor", "ai-settings.json"), - ]) - elif system == "Linux": - cursor_trial_files.extend([ - os.path.join(home, ".config", "Cursor", "trial_info.json"), - os.path.join(home, ".config", "Cursor", "license.json"), - os.path.join(home, ".local", "share", "Cursor", "trial_info.json"), - os.path.join(home, ".local", "share", "Cursor", "license.json"), - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - os.path.join(home, ".config", "Cursor", "ai-settings.json"), - ]) - - # Remove known trial/license files - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_known')} {Style.RESET_ALL}") - for path in cursor_trial_files: - remove_file(path) - - # Deep search for additional trial/license files - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.performing_deep_scan')} {Style.RESET_ALL}") - all_found_files = [] - for base_path in license_search_paths: - if os.path.exists(base_path): - found_files = find_cursor_license_files(base_path, file_patterns) - all_found_files.extend(found_files) - - if all_found_files: - print(f"\n🔎 {translator.get('totally_reset.found_additional_potential_license_trial_files', count=len(all_found_files))}\n") - for file_path in all_found_files: - remove_file(file_path) - else: - print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.no_additional_license_trial_files_found_in_deep_scan')} {Style.RESET_ALL}") - - # Check for and remove localStorage files that might contain settings - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.checking_for_electron_localstorage_files')} {Style.RESET_ALL}") - if system == "Windows": - local_storage_paths = glob.glob(os.path.join(home, "AppData", "Roaming", "*cursor*", "Local Storage", "leveldb", "*")) - local_storage_paths += glob.glob(os.path.join(home, "AppData", "Local", "*cursor*", "Local Storage", "leveldb", "*")) - elif system == "Darwin": - local_storage_paths = glob.glob(os.path.join(home, "Library", "Application Support", "*cursor*", "Local Storage", "leveldb", "*")) - elif system == "Linux": - local_storage_paths = glob.glob(os.path.join(home, ".config", "*cursor*", "Local Storage", "leveldb", "*")) - - for path in local_storage_paths: - if is_cursor_related(path): - remove_file(path) - - # Create new trial files with extended expiration - print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.creating_new_trial_information_with_extended_period')} {Style.RESET_ALL}") - if system == "Windows": - create_fake_trial_info(os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), system, home) - create_fake_trial_info(os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), system, home) - elif system == "Darwin": - create_fake_trial_info(os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), system, home) - elif system == "Linux": - create_fake_trial_info(os.path.join(home, ".config", "Cursor", "trial_info.json"), system, home) - - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.reset_log_1')}") - print(f" {translator.get('totally_reset.reset_log_2')}") - print(f" {translator.get('totally_reset.reset_log_3')}") - - print(f"\n{Fore.GREEN}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_4')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_5')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_6')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_7')} {Style.RESET_ALL}") - print(f" {translator.get('totally_reset.reset_log_8')} {Style.RESET_ALL}") - - print(f"\n{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_9')} {Style.RESET_ALL}") + print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} Cursor AI has been reset successfully!") + print(f"{Fore.CYAN}{EMOJI['INFO']} Reinstall Cursor if needed and enjoy your extended trial.{Style.RESET_ALL}") if __name__ == "__main__": try: reset_cursor() except KeyboardInterrupt: - print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.keyboard_interrupt')} {Style.RESET_ALL}") + print(f"\n\n{Fore.RED}{EMOJI['WARNING']} Process interrupted.{Style.RESET_ALL}") sys.exit(1) except Exception as e: - print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") - print(f" {translator.get('totally_reset.report_issue')}") + print(f"\n{Fore.RED}{EMOJI['ERROR']} Unexpected error: {str(e)}{Style.RESET_ALL}") sys.exit(1) - -def run(translator=None): - """Entry point for the totally reset cursor functionality when called from the main menu.""" - try: - reset_cursor() - input(f"\n\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.return_to_main_menu')} {Style.RESET_ALL}") - except KeyboardInterrupt: - print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.process_interrupted')} {Style.RESET_ALL}") - except Exception as e: - print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}") - print(f" {translator.get('totally_reset.report_issue')}") - input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}") \ No newline at end of file From bd152be4e85c90282361324e6c4cf6db5d6ea472 Mon Sep 17 00:00:00 2001 From: Nigel1992 Date: Sun, 30 Mar 2025 22:14:03 +0200 Subject: [PATCH 37/47] fix: improve Linux path handling and fix permission issues --- config.py | 77 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/config.py b/config.py index 2fb5954..1d8e6bf 100644 --- a/config.py +++ b/config.py @@ -85,28 +85,45 @@ def setup_config(translator=None): elif sys.platform == "linux": # Get the actual user's home directory, handling both sudo and normal cases - current_user = os.getenv('USER') or os.getenv('USERNAME') or os.getenv('SUDO_USER') + 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] - actual_home = f"/home/{current_user}" + # 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 + # Try both "Cursor" and "cursor" directory names in both user and root locations cursor_dir = None - for dir_name in ["Cursor", "cursor"]: - test_dir = os.path.join(config_base, dir_name) - if os.path.exists(test_dir): - cursor_dir = test_dir + 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)}{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')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Neither Cursor nor cursor directory found in {config_base}{Style.RESET_ALL}") + if root_home: + print(f"{Fore.YELLOW}{EMOJI['INFO']} Also checked {root_home}/.config{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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 "" @@ -116,46 +133,50 @@ def setup_config(translator=None): 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)}{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')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage directory not found: {storage_dir}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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)}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', stat.st_size)}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', oct(stat.st_mode & 0o777))}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', stat.st_uid)}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', stat.st_gid)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} Storage file found: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File size: {stat.st_size} bytes{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File permissions: {oct(stat.st_mode & 0o777)}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} File owner: {stat.st_uid}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['INFO']} 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))}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} 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)}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', current_user=current_user, storage_path=storage_path)}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and', storage_path=storage_path)}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} Permission denied: {storage_path}{Style.RESET_ALL}") + if sudo_user: + print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running: chown {sudo_user}:{sudo_user} {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} And: chmod 644 {storage_path}{Style.RESET_ALL}") + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running: chown {current_user}:{current_user} {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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)}{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')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage file is empty: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") else: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data')}{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} 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))}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor')}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} Error reading storage file: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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)}{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')}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage file not found: {storage_path}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 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))}{Style.RESET_ALL}") + print(f"{Fore.RED}{EMOJI['ERROR']} Error checking Linux paths: {str(e)}{Style.RESET_ALL}") # Define all paths using the found cursor directory default_config['LinuxPaths'] = { From d7b056b3393c70600afe37ced29e339ef7ae3112 Mon Sep 17 00:00:00 2001 From: Nigel1992 Date: Sun, 30 Mar 2025 22:27:08 +0200 Subject: [PATCH 38/47] fix: improve Linux Chrome visibility and root user handling --- new_tempemail.py | 30 +++++++++++++++++++++++++--- oauth_auth.py | 51 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/new_tempemail.py b/new_tempemail.py index 307b301..dfb96b3 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -106,11 +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": - co.set_argument("--no-sandbox") - + # 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}❌ No display found. Make sure X server is running.{Style.RESET_ALL}") + print(f"{Fore.YELLOW}ℹ️ 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}ℹ️ Using Chrome profile from: {user_data_dir}{Style.RESET_ALL}") + co.set_argument(f"--user-data-dir={user_data_dir}") co.auto_port() # 自动设置端口 @@ -132,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}ℹ️ Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}") + print(f"{Fore.YELLOW}ℹ️ 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 c27bcea..374bec6 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -21,7 +21,8 @@ EMOJI = { 'SUCCESS': '✅', 'ERROR': '❌', 'WAIT': '⏳', - 'INFO': 'ℹ️' + 'INFO': 'ℹ️', + 'WARNING': '⚠️' } class OAuthHandler: @@ -85,6 +86,20 @@ class OAuthHandler: platform_name = platform.system().lower() print(f"{Fore.CYAN}{EMOJI['INFO']} 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']} No display found. Make sure X server is running.{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Try: export DISPLAY=:0{Style.RESET_ALL}") + return False + + # Check if running as root + if os.geteuid() == 0: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Running as root is not recommended for browser automation{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} Consider running the script without sudo{Style.RESET_ALL}") + # Kill existing browser processes self._kill_browser_processes() @@ -103,7 +118,35 @@ class OAuthHandler: print(f"{Fore.CYAN}{EMOJI['INFO']} 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() + + # Never use headless mode for OAuth flows + co.headless(False) + + # Platform-specific options + if platform_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']} 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']} Starting browser at: {chrome_path}{Style.RESET_ALL}") self.browser = ChromiumPage(co) @@ -118,9 +161,11 @@ class OAuthHandler: except Exception as e: print(f"{Fore.RED}{EMOJI['ERROR']} 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']} 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}") + if platform_name == 'linux': + print(f"{Fore.YELLOW}{EMOJI['INFO']} Try: sudo apt install chromium-browser{Style.RESET_ALL}") return False def _kill_browser_processes(self): From a4d5b0e46735f7670089de0b869af05cf73bb40c Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Mon, 31 Mar 2025 11:23:31 +0800 Subject: [PATCH 39/47] Update totally_reset_cursor.py Fix & Add Locale --- totally_reset_cursor.py | 922 +++++++++++++++++++++++++++++++++------- 1 file changed, 764 insertions(+), 158 deletions(-) diff --git a/totally_reset_cursor.py b/totally_reset_cursor.py index bd6fd74..78dcad3 100644 --- a/totally_reset_cursor.py +++ b/totally_reset_cursor.py @@ -1,183 +1,789 @@ - input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}")import os -import shutil -import platform +import os import sys -import uuid import json -from datetime import datetime +import uuid +import hashlib +import shutil +import sqlite3 +import platform +import re +import tempfile from colorama import Fore, Style, init +from typing import Tuple +import configparser +from new_signup import get_user_documents_path +import traceback +from config import get_config # Initialize colorama init() -# Define simplified emoji constants +# Define emoji constants EMOJI = { + "FILE": "📄", + "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "🔄", - "WARNING": "⚠️" + "RESET": "��", + "WARNING": "⚠️", } -def get_confirmation(): - """Gets confirmation from the user to proceed.""" - choice = input(f"{Fore.RED}{EMOJI['WARNING']} Confirm Cursor AI reset (y/n): ").strip().lower() - return choice == "y" or choice == "" - -def remove_path(path): - """Removes a directory or file if it exists.""" - if not path or not os.path.exists(path): - return - - try: - if os.path.isfile(path): - os.remove(path) - else: - shutil.rmtree(path, ignore_errors=True) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Deleted: {path} {Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} Error deleting {path}: {str(e)} {Style.RESET_ALL}") - -def create_fake_machine_id(path): - """Creates a new machine ID file with random ID.""" - try: - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - with open(path, 'w') as f: - f.write(str(uuid.uuid4())) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created new machine ID: {path} {Style.RESET_ALL}") - except Exception: - pass - -def create_fake_trial_info(path): - """Creates fake trial information to extend trial period.""" - try: - # Generate future expiry date (90 days from now) - future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds - - # Create fake trial info - fake_trial = { - "trialStartTimestamp": datetime.now().timestamp() * 1000, - "trialEndTimestamp": future_date, - "hasUsedTrial": False, - "machineId": str(uuid.uuid4()) - } - - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - - with open(path, 'w') as f: - json.dump(fake_trial, f) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Created extended trial: {path} {Style.RESET_ALL}") - except Exception: - pass - -def reset_cursor(): - """Reset Cursor AI by removing settings, caches, and creating new identifiers.""" +def get_cursor_paths(translator=None) -> Tuple[str, str]: + """ Get Cursor related paths""" system = platform.system() - home = os.path.expanduser("~") - - print(f"\n{Fore.CYAN}====== Cursor AI Reset Tool ======{Style.RESET_ALL}") - print(f"{Fore.RED}{EMOJI['WARNING']} This will reset Cursor AI completely:") - print(f"• All settings, cache, and identifiers will be removed") - print(f"• Data may be unrecoverable") - print(f"• You may need to reinstall Cursor after reset{Style.RESET_ALL}\n") - if not get_confirmation(): - print(f"\n{Fore.CYAN}Reset cancelled.{Style.RESET_ALL}") - return - - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Resetting Cursor AI...{Style.RESET_ALL}") - - # Define paths based on OS - cursor_paths = [] - machine_id_paths = [] - trial_info_path = "" + # Read config file + config = configparser.ConfigParser() + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") - if system == "Windows": - cursor_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor"), - os.path.join(home, "AppData", "Local", "Cursor"), - os.path.join(home, "AppData", "Roaming", "cursor-electron"), - os.path.join(home, "AppData", "Local", "cursor-electron"), - os.path.join(home, "AppData", "Local", "CursorAI"), - os.path.join(home, "AppData", "Roaming", "CursorAI"), - os.path.join(home, "AppData", "Local", "Temp", "Cursor"), - os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - os.path.join(home, ".cursor-state.json"), - ] - machine_id_paths = [ - os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"), - os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json") + # Create config directory if it doesn't exist + if not os.path.exists(config_dir): + os.makedirs(config_dir) + + # Default paths for different systems + default_paths = { + "Darwin": "/Applications/Cursor.app/Contents/Resources/app", + "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app")] + } + + # If config doesn't exist, create it with default paths + if not os.path.exists(config_file): + for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']: + if not config.has_section(section): + config.add_section(section) - elif system == "Darwin": # macOS - cursor_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor"), - os.path.join(home, "Library", "Application Support", "cursor-electron"), - os.path.join(home, "Library", "Caches", "Cursor"), - os.path.join(home, "Library", "Caches", "cursor-electron"), - os.path.join(home, "Library", "Preferences", "Cursor"), - os.path.join(home, "Library", "Preferences", "cursor-electron"), - os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"), - os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - ] - machine_id_paths = [ - os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json") + if system == "Darwin": + config.set('MacPaths', 'cursor_path', default_paths["Darwin"]) + elif system == "Windows": + config.set('WindowsPaths', 'cursor_path', default_paths["Windows"]) + elif system == "Linux": + # For Linux, try to find the first existing path + for path in default_paths["Linux"]: + if os.path.exists(path): + config.set('LinuxPaths', 'cursor_path', path) + break + else: + # If no path exists, use the first one as default + config.set('LinuxPaths', 'cursor_path', default_paths["Linux"][0]) - elif system == "Linux": - cursor_paths = [ - os.path.join(home, ".config", "Cursor"), - os.path.join(home, ".config", "cursor-electron"), - os.path.join(home, ".cache", "Cursor"), - os.path.join(home, ".cache", "cursor-electron"), - os.path.join(home, ".local", "share", "Cursor"), - os.path.join(home, ".local", "share", "cursor-electron"), - os.path.join(home, ".cursor_trial_data"), - os.path.join(home, ".cursor_license"), - os.path.join(home, ".cursor-machine-id"), - ] - machine_id_paths = [ - os.path.join(home, ".config", "Cursor", "cursor-machine-id"), - ] - trial_info_path = os.path.join(home, ".config", "Cursor", "trial_info.json") + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) else: - print(f"{Fore.RED}{EMOJI['ERROR']} Unsupported OS: {system} {Style.RESET_ALL}") - return + config.read(config_file, encoding='utf-8') + + # Get path based on system + if system == "Darwin": + section = 'MacPaths' + elif system == "Windows": + section = 'WindowsPaths' + elif system == "Linux": + section = 'LinuxPaths' + else: + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") + + if not config.has_section(section) or not config.has_option(section, 'cursor_path'): + raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑") + + base_path = config.get(section, 'cursor_path') + + # For Linux, try to find the first existing path if the configured one doesn't exist + if system == "Linux" and not os.path.exists(base_path): + for path in default_paths["Linux"]: + if os.path.exists(path): + base_path = path + # Update config with the found path + config.set(section, 'cursor_path', path) + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + break + + if not os.path.exists(base_path): + raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}") + + pkg_path = os.path.join(base_path, "package.json") + main_path = os.path.join(base_path, "out/main.js") + + # Check if files exist + if not os.path.exists(pkg_path): + raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}") + if not os.path.exists(main_path): + raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}") + + return (pkg_path, main_path) - # Remove main Cursor directories and files - for path in cursor_paths: - remove_path(path) +def get_cursor_machine_id_path(translator=None) -> str: + """ + Get Cursor machineId file path based on operating system + Returns: + str: Path to machineId file + """ + # Read configuration + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + config = configparser.ConfigParser() + + if os.path.exists(config_file): + config.read(config_file) + + if sys.platform == "win32": # Windows + if not config.has_section('WindowsPaths'): + config.add_section('WindowsPaths') + config.set('WindowsPaths', 'machine_id_path', + os.path.join(os.getenv("APPDATA"), "Cursor", "machineId")) + return config.get('WindowsPaths', 'machine_id_path') + + elif sys.platform == "linux": # Linux + if not config.has_section('LinuxPaths'): + config.add_section('LinuxPaths') + config.set('LinuxPaths', 'machine_id_path', + os.path.expanduser("~/.config/cursor/machineid")) + return config.get('LinuxPaths', 'machine_id_path') + + elif sys.platform == "darwin": # macOS + if not config.has_section('MacPaths'): + config.add_section('MacPaths') + config.set('MacPaths', 'machine_id_path', + os.path.expanduser("~/Library/Application Support/Cursor/machineId")) + return config.get('MacPaths', 'machine_id_path') + + else: + raise OSError(f"Unsupported operating system: {sys.platform}") - # Create new machine IDs - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new machine identifiers...{Style.RESET_ALL}") - for path in machine_id_paths: - create_fake_machine_id(path) + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) - # Create new trial with extended expiration - print(f"\n{Fore.CYAN}{EMOJI['RESET']} Creating new trial information...{Style.RESET_ALL}") - create_fake_trial_info(trial_info_path) +def get_workbench_cursor_path(translator=None) -> str: + """Get Cursor workbench.desktop.main.js path""" + system = platform.system() + + paths_map = { + "Darwin": { # macOS + "base": "/Applications/Cursor.app/Contents/Resources/app", + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Windows": { + "base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"), + "main": "out/vs/workbench/workbench.desktop.main.js" + }, + "Linux": { + "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], + "main": "out/vs/workbench/workbench.desktop.main.js" + } + } - print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} Cursor AI has been reset successfully!") - print(f"{Fore.CYAN}{EMOJI['INFO']} Reinstall Cursor if needed and enjoy your extended trial.{Style.RESET_ALL}") + if system not in paths_map: + raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") + + if system == "Linux": + for base in paths_map["Linux"]["bases"]: + main_path = os.path.join(base, paths_map["Linux"]["main"]) + if os.path.exists(main_path): + return main_path + raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径") + + base_path = paths_map[system]["base"] + main_path = os.path.join(base_path, paths_map[system]["main"]) + + if not os.path.exists(main_path): + raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}") + + return main_path + +def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool: + """Version number check""" + version_pattern = r"^\d+\.\d+\.\d+$" + try: + if not re.match(version_pattern, version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + def parse_version(ver: str) -> Tuple[int, ...]: + return tuple(map(int, ver.split("."))) + + current = parse_version(version) + + if min_version and current < parse_version(min_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}") + return False + + if max_version and current > parse_version(max_version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}") + return False + + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}") + return False + +def check_cursor_version(translator) -> bool: + """Check Cursor version""" + try: + pkg_path, _ = get_cursor_paths(translator) + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}") + + try: + with open(pkg_path, "r", encoding="utf-8") as f: + data = json.load(f) + except UnicodeDecodeError: + # If UTF-8 reading fails, try other encodings + with open(pkg_path, "r", encoding="latin-1") as f: + data = json.load(f) + + if not isinstance(data, dict): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + + if "version" not in data: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}") + return False + + version = str(data["version"]).strip() + if not version: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}") + + # Check version format + if not re.match(r"^\d+\.\d+\.\d+$", version): + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}") + return False + + # Compare versions + try: + current = tuple(map(int, version.split("."))) + min_ver = (0, 45, 0) # Use tuple directly instead of string + + if current >= min_ver: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return True + else: + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}") + return False + except ValueError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}") + return False + + except FileNotFoundError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}") + return False + except json.JSONDecodeError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}") + return False + +def modify_workbench_js(file_path: str, translator=None) -> bool: + """ + Modify file content + """ + try: + # Save original file permissions + original_stat = os.stat(file_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + # Create temporary file + with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file: + # Read original content + with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file: + content = main_file.read() + + if sys.platform == "win32": + # Define replacement patterns + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "linux": + CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + elif sys.platform == "darwin": + CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)' + CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)' + + CBadge_old_pattern = r'
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: + raise NotImplementedError(f"Not Supported OS: {sys.platform}") + + # Save any changes to config file + with open(config_file, 'w', encoding='utf-8') as f: + config.write(f) + + def generate_new_ids(self): + """Generate new machine ID""" + # Generate new UUID + dev_device_id = str(uuid.uuid4()) + + # Generate new machineId (64 characters of hexadecimal) + machine_id = hashlib.sha256(os.urandom(32)).hexdigest() + + # Generate new macMachineId (128 characters of hexadecimal) + mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() + + # Generate new sqmId + sqm_id = "{" + str(uuid.uuid4()).upper() + "}" + + self.update_machine_id_file(dev_device_id) + + 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 + } + + 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() + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS ItemTable ( + key TEXT PRIMARY KEY, + value TEXT + ) + """) + + 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 + + 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""" + 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__": - try: - reset_cursor() - except KeyboardInterrupt: - print(f"\n\n{Fore.RED}{EMOJI['WARNING']} Process interrupted.{Style.RESET_ALL}") - sys.exit(1) - except Exception as e: - print(f"\n{Fore.RED}{EMOJI['ERROR']} Unexpected error: {str(e)}{Style.RESET_ALL}") - sys.exit(1) + from main import translator as main_translator + run(main_translator) From 735dd8c1eb15c82790dc85e1615e790bc346981f Mon Sep 17 00:00:00 2001 From: yeongpin Date: Mon, 31 Mar 2025 11:58:54 +0800 Subject: [PATCH 40/47] refactor: enhance user feedback and error messages with translation support across multiple modules --- config.py | 66 +++++++------- locales/en.json | 77 +++++++++++++++-- locales/zh_cn.json | 70 ++++++++++++++- locales/zh_tw.json | 70 ++++++++++++++- new_tempemail.py | 10 +-- oauth_auth.py | 209 +++++++++++++++++++++++---------------------- 6 files changed, 347 insertions(+), 155 deletions(-) diff --git a/config.py b/config.py index 1d8e6bf..cf481c0 100644 --- a/config.py +++ b/config.py @@ -120,10 +120,10 @@ def setup_config(translator=None): break if not cursor_dir: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Neither Cursor nor cursor directory found in {config_base}{Style.RESET_ALL}") + 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']} Also checked {root_home}/.config{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + 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 "" @@ -133,50 +133,50 @@ def setup_config(translator=None): try: # Check storage directory if storage_dir and not os.path.exists(storage_dir): - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Storage directory not found: {storage_dir}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + 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']} Storage file found: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File size: {stat.st_size} bytes{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File permissions: {oct(stat.st_mode & 0o777)}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File owner: {stat.st_uid}{Style.RESET_ALL}") - print(f"{Fore.GREEN}{EMOJI['INFO']} File group: {stat.st_gid}{Style.RESET_ALL}") + 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']} Error getting file stats: {str(e)}{Style.RESET_ALL}") + 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']} Permission denied: {storage_path}{Style.RESET_ALL}") + 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']} Try running: chown {sudo_user}:{sudo_user} {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} And: chmod 644 {storage_path}{Style.RESET_ALL}") + 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']} Try running: chown {current_user}:{current_user} {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} And: chmod 644 {storage_path}{Style.RESET_ALL}") + 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']} Storage file is empty: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + 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']} Storage file is valid and contains data{Style.RESET_ALL}") + 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']} Error reading storage file: {str(e)}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} The file might be corrupted. Please reinstall Cursor{Style.RESET_ALL}") + 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']} Storage file not found: {storage_path}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Please make sure Cursor is installed and has been run at least once{Style.RESET_ALL}") + 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']} Error checking Linux paths: {str(e)}{Style.RESET_ALL}") + 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'] = { @@ -202,13 +202,13 @@ def setup_config(translator=None): config.set(section, option, str(value)) config_modified = True if translator: - print(f"{Fore.YELLOW}{EMOJI['INFO']} {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}{EMOJI['SUCCESS']} {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) @@ -218,31 +218,31 @@ def setup_config(translator=None): with open(config_file, 'w', encoding='utf-8') as f: config.write(f) if translator: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {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}{EMOJI['ERROR']} {translator.get('register.config_setup_error', error=str(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')}{Style.RESET_ALL}") + 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')}:{Style.RESET_ALL}") + 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')}{Style.RESET_ALL}" + 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')}{Style.RESET_ALL}" + value_display = f"{Fore.RED}{translator.get('config.disabled') if translator else 'Disabled'}{Style.RESET_ALL}" else: value_display = value @@ -250,7 +250,7 @@ def print_config(config, translator=None): 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')}: {config_dir}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory') if translator else 'Config Directory'}: {config_dir}{Style.RESET_ALL}") print() diff --git a/locales/en.json b/locales/en.json index c3bef05..05a4877 100644 --- a/locales/en.json +++ b/locales/en.json @@ -280,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", @@ -498,14 +504,14 @@ "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}", + "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 'cursor --help' to check if it's installed", - "and": "and", + "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}", @@ -513,7 +519,64 @@ "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}" - + "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..." } -} \ No newline at end of file +} \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 05710fe..4bf37ed 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -275,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 自动更新", @@ -499,7 +505,7 @@ "file_group": "文件组", "error_getting_file_stats": "获取文件统计信息时出错", "permission_denied": "权限拒绝", - "try_running": "尝试运行 'cursor --help' 检查是否已安装", + "try_running": "尝试运行: {command}", "and": "和", "storage_file_is_empty": "存储文件为空", "the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已损坏,请重新安装 Cursor", @@ -508,7 +514,65 @@ "config_option_added": "添加配置选项", "config_updated": "配置更新", "config_created": "配置已创建", - "config_setup_error": "配置设置错误" + "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": "认证成功, 获取账户信息..." } } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index c88ae6e..bca1a83 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -254,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 自动更新", @@ -478,7 +485,7 @@ "file_group": "文件組", "error_getting_file_stats": "獲取文件統計信息時出錯", "permission_denied": "權限拒絕", - "try_running": "嘗試運行 'cursor --help' 檢查是否已安裝", + "try_running": "嘗試運行: {command}", "and": "和", "storage_file_is_empty": "儲存文件為空", "the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已損壞,請重新安裝 Cursor", @@ -487,7 +494,64 @@ "config_option_added": "添加配置選項", "config_updated": "配置更新", "config_created": "配置已創建", - "config_setup_error": "配置設置錯誤" + "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": "認證成功, 獲取帳戶信息..." } } \ No newline at end of file diff --git a/new_tempemail.py b/new_tempemail.py index dfb96b3..178f24f 100644 --- a/new_tempemail.py +++ b/new_tempemail.py @@ -114,8 +114,8 @@ class NewTempEmail: 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}❌ No display found. Make sure X server is running.{Style.RESET_ALL}") - print(f"{Fore.YELLOW}ℹ️ Try: export DISPLAY=:0{Style.RESET_ALL}") + 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") @@ -129,7 +129,7 @@ class NewTempEmail: 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}ℹ️ Using Chrome profile from: {user_data_dir}{Style.RESET_ALL}") + 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() # 自动设置端口 @@ -154,8 +154,8 @@ class NewTempEmail: print(f"{Fore.RED}❌ 启动浏览器失败: {str(e)}{Style.RESET_ALL}") if sys.platform == "linux": - print(f"{Fore.YELLOW}ℹ️ Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}") - print(f"{Fore.YELLOW}ℹ️ Try: sudo apt install chromium-browser{Style.RESET_ALL}") + 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 374bec6..6dc4c71 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -43,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 @@ -70,35 +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']} No display found. Make sure X server is running.{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Try: export DISPLAY=:0{Style.RESET_ALL}") + 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']} Running as root is not recommended for browser automation{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{EMOJI['INFO']} Consider running the script without sudo{Style.RESET_ALL}") + 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() @@ -108,14 +108,14 @@ 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 = ChromiumOptions() @@ -124,7 +124,7 @@ class OAuthHandler: co.headless(False) # Platform-specific options - if platform_name == 'linux': + if os.name == 'linux': co.set_argument('--no-sandbox') co.set_argument('--disable-dev-shm-usage') co.set_argument('--disable-gpu') @@ -136,7 +136,7 @@ class OAuthHandler: 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']} Using Chrome profile from: {user_data_dir}{Style.RESET_ALL}") + 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 @@ -146,26 +146,26 @@ class OAuthHandler: # Basic options co.set_argument('--no-first-run') co.set_argument('--no-default-browser-check') - co.auto_port() + # co.auto_port() - print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}") + 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 without sudo/administrator 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']} Try: sudo apt install chromium-browser{Style.RESET_ALL}") + 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): @@ -182,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""" @@ -208,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): @@ -229,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 @@ -261,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): @@ -296,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')) @@ -335,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 @@ -352,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: @@ -379,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: @@ -398,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) @@ -407,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 @@ -416,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}") + 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}") + 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): @@ -466,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')) @@ -490,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: @@ -566,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: @@ -598,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 @@ -609,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 @@ -619,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}") + 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() { @@ -655,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} @@ -679,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: @@ -695,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 @@ -705,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}") + 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() { @@ -741,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: @@ -801,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 @@ -816,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 = [] @@ -829,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): @@ -868,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): @@ -888,13 +889,13 @@ def main(auth_type, translator=None): 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: @@ -905,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 From a56b97866942034df3a6ad0d5468fdf972bcdbc4 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Mon, 31 Mar 2025 12:33:25 +0800 Subject: [PATCH 41/47] feat: add new error messages for browser process handling in English, Simplified Chinese, and Traditional Chinese locales --- locales/en.json | 5 ++++- locales/zh_cn.json | 5 ++++- locales/zh_tw.json | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 05a4877..f5e84a8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -577,6 +577,9 @@ "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..." + "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 4bf37ed..1a4d304 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -572,7 +572,10 @@ "navigating_to_authentication_page": "正在导航到认证页面...", "please_select_your_google_account_to_continue": "请选择您的 Google 账户以继续...", "found_browser_data_directory": "找到浏览器数据目录: {path}", - "authentication_successful_getting_account_info": "认证成功, 获取账户信息..." + "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 bca1a83..ab97eed 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -552,6 +552,9 @@ "navigating_to_authentication_page": "正在導航到認證頁面...", "please_select_your_google_account_to_continue": "請選擇您的 Google 帳戶以繼續...", "found_browser_data_directory": "找到瀏覽器數據目錄: {path}", - "authentication_successful_getting_account_info": "認證成功, 獲取帳戶信息..." + "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 From e2d7c1c496c5834eb230882dea5afa7c3ddefee7 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Mon, 31 Mar 2025 12:35:35 +0800 Subject: [PATCH 42/47] chore: update version to 1.8.04 and enhance CHANGELOG with new features and fixes --- .env | 4 ++-- .github/workflows/build.yml | 2 +- CHANGELOG.md | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 5b2edca..e485b0b 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.8.03 -VERSION=1.8.03 +version=1.8.04 +VERSION=1.8.04 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b01720..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.8.02' + default: '1.8.04' permissions: contents: write diff --git a/CHANGELOG.md b/CHANGELOG.md index 0615c1d..62d76b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 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 | 修復一些問題 From 6e6180e3316c6bd2939baf13bbe4bbdb745b67d7 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Mon, 31 Mar 2025 17:49:35 +0800 Subject: [PATCH 43/47] fix: update reset emoji and correct button patterns for cross-platform compatibility --- reset_machine_manual.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 3be1e4d..f9359be 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -25,7 +25,7 @@ EMOJI = { "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", - "RESET": "��", + "RESET": "🔄", "WARNING": "⚠️", } @@ -299,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)' From 51cef9c2b25236622da1df625e1dc174f5a3361f Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 1 Apr 2025 15:42:05 -0300 Subject: [PATCH 44/47] fix: improve account usage limit detection - Add support for detecting both 150/150 and 50/50 usage limits - Improve usage parsing and validation - Add better error handling for usage detection - Add more descriptive log messages --- oauth_auth.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/oauth_auth.py b/oauth_auth.py index 6dc4c71..5da5b16 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -419,8 +419,8 @@ class OAuthHandler: usage_text = usage_element.text 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": + # 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 @@ -622,8 +622,8 @@ class OAuthHandler: usage_text = usage_element.text 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": + # 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 = """ @@ -708,8 +708,8 @@ class OAuthHandler: usage_text = usage_element.text 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": + # 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 = """ From d91485ec7522ec179fd92d560847d9ccf83182f6 Mon Sep 17 00:00:00 2001 From: Valentin Guerdin <70648608+blobs0@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:32:27 +0200 Subject: [PATCH 45/47] Patch Linux Path Not Found add folder /usr/lib/cursor/app/ --- reset_machine_manual.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reset_machine_manual.py b/reset_machine_manual.py index f9359be..a10f862 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -46,7 +46,7 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]: 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")] + "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 @@ -170,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" } } @@ -786,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) From fdc831738076bed4e0fac395f889984c559dc0e9 Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Thu, 3 Apr 2025 02:07:49 +0800 Subject: [PATCH 46/47] Update .env --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index e485b0b..1199388 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.8.04 -VERSION=1.8.04 +version=1.8.05 +VERSION=1.8.05 From ea44218a8a8e4cc0fc8652d79ab72c10e6c1fb02 Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Thu, 3 Apr 2025 02:09:29 +0800 Subject: [PATCH 47/47] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d76b8..3f9bfcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 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 用戶處理