From c15ea25cb3acd5ebe327058d1057bbd81b3db29e Mon Sep 17 00:00:00 2001 From: yeongpin Date: Sat, 29 Mar 2025 21:49:57 +0800 Subject: [PATCH] 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"""