From 4587fd9373663cd0ad8775fbc99d5f6bdeef8773 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Fri, 28 Mar 2025 19:24:55 +0800 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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'] = {