From ff358588bbdff8ae4d23a4e30f9a52bebf699855 Mon Sep 17 00:00:00 2001 From: yeongpin Date: Tue, 11 Mar 2025 11:49:17 +0800 Subject: [PATCH] feat: Refactor Project Structure and Add Configuration Management - Add `config.py` for centralized configuration management - Add `utils.py` for cross-platform utility functions - Remove `browser.py` and `control.py` to simplify project structure - Update version to 1.7.06 - Modify build specification to reflect new file structure - Update localization files with new update confirmation messages - Enhance configuration loading and path detection across different platforms --- .env | 4 +- .github/workflows/build.yml | 2 +- CHANGELOG.md | 14 +++ browser.py | 95 ------------------ build.spec | 12 ++- config.py | 117 ++++++++++++++++++++++ control.py | 181 --------------------------------- cursor_auth.py | 50 ++++++--- cursor_register.py | 3 - cursor_register_manual.py | 3 - disable_auto_update.py | 28 ++++-- locales/en.json | 9 +- locales/zh_cn.json | 9 +- locales/zh_tw.json | 9 +- main.py | 15 ++- new_signup.py | 195 ++++++------------------------------ reset_machine_manual.py | 9 +- utils.py | 32 ++++++ 18 files changed, 303 insertions(+), 484 deletions(-) delete mode 100644 browser.py create mode 100644 config.py delete mode 100644 control.py create mode 100644 utils.py diff --git a/.env b/.env index 6963d7b..d3c1634 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.7.05 -VERSION=1.7.05 +version=1.7.06 +VERSION=1.7.06 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e79cc10..a41a340 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.05' + default: '1.7.06' permissions: contents: write diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b783a..3759b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## v1.7.06 +1. Add: Update Confirm | 增加更新確認 +2. Add: Update Skipped | 增加更新跳過 +3. Add: Invalid Choice | 增加無效選擇 +4. Fix: Cursor Path | 修復Cursor路徑 +5. Fix: Path Encoding | 修復路徑編碼 +6. Fix: Getting Verification Code | 修復獲取驗證碼 +7. Fix: Setting Password | 修復設置密碼 +8. Fix: Disable Auto Update | 修復禁用自動更新 +9. Add Config.py | 增加Config.py +10. Add utils.py | 增加utils.py +11. Rebuild some logic | 重新構建一些邏輯 + + ## v1.7.05 1. Fix: Cursor Version Check | 修復Cursor版本檢查 2. Fix: Small Problem | 修復一些小問題 diff --git a/browser.py b/browser.py deleted file mode 100644 index ddafc9d..0000000 --- a/browser.py +++ /dev/null @@ -1,95 +0,0 @@ -from DrissionPage import ChromiumOptions, ChromiumPage -import sys -import os -import logging -import random - - -class BrowserManager: - def __init__(self, noheader=False): - self.browser = None - self.noheader = noheader - - def init_browser(self): - """初始化浏览器""" - co = self._get_browser_options() - - # 如果设置了 noheader,添加相应的参数 - if self.noheader: - co.set_argument('--headless=new') - - self.browser = ChromiumPage(co) - return self.browser - - def _get_browser_options(self): - """获取浏览器配置""" - co = ChromiumOptions() - try: - extension_path = self._get_extension_path() - co.add_extension(extension_path) - co.set_argument("--allow-extensions-in-incognito") - - extension_block_path = self.get_extension_block() - co.add_extension(extension_block_path) - co.set_argument("--allow-extensions-in-incognito") - except FileNotFoundError as e: - logging.warning(f"警告: {e}") - - # 设置更真实的用户代理 - co.set_user_agent( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ) - - # 基本设置 - co.set_pref("credentials_enable_service", False) - - # 随机端口 - co.auto_port() - - # 系统特定设置 - if sys.platform == "darwin": # macOS - co.set_argument("--disable-gpu") - co.set_argument("--no-sandbox") - elif sys.platform == "win32": # Windows - co.set_argument("--disable-software-rasterizer") - - # 设置窗口大小 - window_width = random.randint(1024, 1920) - window_height = random.randint(768, 1080) - co.set_argument(f"--window-size={window_width},{window_height}") - - return co - - def _get_extension_path(self): - """获取插件路径""" - root_dir = os.getcwd() - extension_path = os.path.join(root_dir, "turnstilePatch") - - if hasattr(sys, "_MEIPASS"): - extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") - - if not os.path.exists(extension_path): - raise FileNotFoundError(f"插件不存在: {extension_path}") - - return extension_path - - 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 quit(self): - """关闭浏览器""" - if self.browser: - try: - self.browser.quit() - except: - pass \ No newline at end of file diff --git a/build.spec b/build.spec index e686bca..546724d 100644 --- a/build.spec +++ b/build.spec @@ -29,15 +29,19 @@ a = Analysis( ('cursor_auth.py', '.'), ('reset_machine_manual.py', '.'), ('cursor_register.py', '.'), - ('browser.py', '.'), - ('control.py', '.'), + ('new_signup.py', '.'), + ('new_tempemail.py', '.'), + ('quit_cursor.py', '.'), + ('cursor_register_manual.py', '.'), ('.env', '.') ], hiddenimports=[ 'cursor_auth', 'reset_machine_manual', - 'browser', - 'control' + 'new_signup', + 'new_tempemail', + 'quit_cursor', + 'cursor_register_manual' ], hookspath=[], hooksconfig={}, diff --git a/config.py b/config.py new file mode 100644 index 0000000..518e7fd --- /dev/null +++ b/config.py @@ -0,0 +1,117 @@ +import os +import sys +import configparser +from colorama import Fore, Style +from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path + +def setup_config(translator=None): + """Setup configuration file and return config object""" + try: + config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") + config_file = os.path.join(config_dir, "config.ini") + os.makedirs(config_dir, exist_ok=True) + + config = configparser.ConfigParser() + + # 默認配置 + default_config = { + 'Chrome': { + 'chromepath': get_default_chrome_path() + }, + 'Turnstile': { + 'handle_turnstile_time': '2', + 'handle_turnstile_random_time': '1-3' + }, + 'Timing': { + 'min_random_time': '0.1', + 'max_random_time': '0.8', + 'page_load_wait': '0.1-0.8', + 'input_wait': '0.3-0.8', + 'submit_wait': '0.5-1.5', + 'verification_code_input': '0.1-0.3', + 'verification_success_wait': '2-3', + 'verification_retry_wait': '2-3', + 'email_check_initial_wait': '4-6', + 'email_refresh_wait': '2-4', + 'settings_page_load_wait': '1-2', + 'failed_retry_time': '0.5-1', + 'retry_interval': '8-12', + 'max_timeout': '160' + } + } + + # 添加系統特定路徑配置 + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + localappdata = os.getenv("LOCALAPPDATA", "") + default_config['WindowsPaths'] = { + 'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"), + '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") + } + elif sys.platform == "darwin": + default_config['MacPaths'] = { + 'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")), + 'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")), + 'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"), + 'cursor_path': "/Applications/Cursor.app/Contents/Resources/app", + 'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater") + } + elif sys.platform == "linux": + sudo_user = os.environ.get('SUDO_USER') + actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") + + default_config['LinuxPaths'] = { + 'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")), + 'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")), + 'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"), + 'cursor_path': get_linux_cursor_path(), + 'updater_path': os.path.expanduser("~/.config/cursor-updater") + } + + # 讀取現有配置並合併 + if os.path.exists(config_file): + config.read(config_file, encoding='utf-8') + config_modified = False + + for section, options in default_config.items(): + if not config.has_section(section): + config.add_section(section) + config_modified = True + for option, value in options.items(): + if not config.has_option(section, option): + 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}") + + 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}") + else: + for section, options in default_config.items(): + config.add_section(section) + for option, value in options.items(): + config.set(section, option, str(value)) + + 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}") + + 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}") + return None + +def get_config(translator=None): + """Get existing config or create new one""" + return setup_config(translator) \ No newline at end of file diff --git a/control.py b/control.py deleted file mode 100644 index 0ebb892..0000000 --- a/control.py +++ /dev/null @@ -1,181 +0,0 @@ -import time -import random -import os -from colorama import Fore, Style, init - -# 初始化colorama -init() - -# 定义emoji常量 -EMOJI = { - 'MAIL': '📧', - 'REFRESH': '🔄', - 'SUCCESS': '✅', - 'ERROR': '❌', - 'INFO': 'ℹ️', - 'CODE': '📱' -} - -class BrowserControl: - def __init__(self, browser, translator=None): - self.browser = browser - self.translator = translator # 保存translator - self.sign_up_url = "https://authenticator.cursor.sh/sign-up" - self.current_tab = None # 当前标签页 - self.signup_tab = None # 注册标签页 - self.email_tab = None # 邮箱标签页 - - def create_new_tab(self): - """创建新标签页""" - try: - # 保存当前标签页 - self.current_tab = self.browser - - # 创建新的浏览器实例 - from browser import BrowserManager - browser_manager = BrowserManager() - new_browser = browser_manager.init_browser() - - # 保存新标签页 - self.signup_tab = new_browser - - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.create_new_tab_success')}{Style.RESET_ALL}") - return new_browser - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.create_new_tab_failed', error=str(e))}{Style.RESET_ALL}") - return None - - - def fill_verification_code(self, code): - """填写验证码""" - try: - if not code or len(code) != 6: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.verification_code_format_error')}{Style.RESET_ALL}") - return False - - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.fill_verification_code')}...{Style.RESET_ALL}") - - # 记住当前标签页(邮箱页面) - email_tab = self.browser - - # 切换回注册页面标签 - self.switch_to_tab(self.signup_tab) - time.sleep(1) - - # 输入验证码 - for digit in code: - self.browser.actions.input(digit) - time.sleep(random.uniform(0.1, 0.3)) - - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.verification_code_filled')}{Style.RESET_ALL}") - - # 等待页面加载和登录完成 - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.wait_for_login')}...{Style.RESET_ALL}") - time.sleep(5) - - # 先访问登录页面确保登录状态 - login_url = "https://authenticator.cursor.sh" - self.browser.get(login_url) - time.sleep(3) # 增加等待时间 - - # 获取cookies(第一次尝试) - token = self.get_cursor_session_token() - if not token: - print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_token_failed')}...{Style.RESET_ALL}") - time.sleep(3) - token = self.get_cursor_session_token() - - if token: - self.save_token_to_file(token) - - # 获取到token后再访问设置页面 - settings_url = "https://www.cursor.com/settings" - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_account_info')}...{Style.RESET_ALL}") - self.browser.get(settings_url) - time.sleep(2) - - # 获取账户额度信息 - try: - usage_selector = ( - "css:div.col-span-2 > div > div > div > div > " - "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " - "span.font-mono.text-sm\\/\\[0\\.875rem\\]" - ) - usage_ele = self.browser.ele(usage_selector) - if usage_ele: - usage_info = usage_ele.text - total_usage = usage_info.split("/")[-1].strip() - print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('control.account_usage_limit')}: {total_usage}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_account_usage_failed', error=str(e))}{Style.RESET_ALL}") - - # 切换回邮箱页面 - self.switch_to_tab(email_tab) - - return True - - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.fill_verification_code_failed', error=str(e))}{Style.RESET_ALL}") - return False - - def check_and_click_turnstile(self): - """检查并点击 Turnstile 验证框""" - try: - # 等待验证框出现 - time.sleep(1) - - # 查找验证框 - verify_checkbox = self.browser.ele('xpath://label[contains(@class, "cb-lb")]//input[@type="checkbox"]') - if verify_checkbox: - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.find_turnstile_verification_box')}...{Style.RESET_ALL}") - verify_checkbox.click() - time.sleep(2) # 等待验证完成 - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.clicked_turnstile_verification_box')}{Style.RESET_ALL}") - return True - return False - except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.check_and_click_turnstile_failed', error=str(e))}{Style.RESET_ALL}") - return False - - def get_cursor_session_token(self, max_attempts=3, retry_interval=2): - """获取Cursor会话token""" - print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_cursor_session_token')}...{Style.RESET_ALL}") - attempts = 0 - - while attempts < max_attempts: - try: - # 直接从浏览器对象获取cookies - all_cookies = self.browser.get_cookies() - - # 遍历查找目标cookie - for cookie in all_cookies: - if cookie.get("name") == "WorkosCursorSessionToken": - token = cookie["value"].split("%3A%3A")[1] - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.get_cursor_session_token_success')}: {token}{Style.RESET_ALL}") - return token - - attempts += 1 - if attempts < max_attempts: - print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_cursor_session_token_failed', attempts=attempts, retry_interval=retry_interval)}...{Style.RESET_ALL}") - time.sleep(retry_interval) - else: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.reach_max_attempts', max_attempts=max_attempts)}{Style.RESET_ALL}") - - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_cookie_failed', error=str(e))}{Style.RESET_ALL}") - attempts += 1 - if attempts < max_attempts: - print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.will_retry_in', retry_interval=retry_interval)}...{Style.RESET_ALL}") - time.sleep(retry_interval) - - return None - - def save_token_to_file(self, token): - """保存token到文件""" - try: - with open('cursor_tokens.txt', 'a', encoding='utf-8') as f: - f.write(f"Token: {token}\n") - f.write("-" * 50 + "\n") - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.token_saved_to_file')}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.save_token_failed', error=str(e))}{Style.RESET_ALL}") \ No newline at end of file diff --git a/cursor_auth.py b/cursor_auth.py index c6fe429..f9c85e7 100644 --- a/cursor_auth.py +++ b/cursor_auth.py @@ -2,6 +2,7 @@ import sqlite3 import os import sys from colorama import Fore, Style, init +from config import get_config # 初始化colorama init() @@ -21,21 +22,40 @@ EMOJI = { class CursorAuth: def __init__(self, translator=None): self.translator = translator - # 判断操作系统 - if sys.platform == "win32": # Windows - self.db_path = os.path.join( - os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb" - ) - elif sys.platform == 'linux': - self.db_path = os.path.expanduser( - "~/.config/Cursor/User/globalStorage/state.vscdb" - ) - elif sys.platform == 'darwin': # macOS - self.db_path = os.path.expanduser( - "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" - ) - else: - print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform')}{Style.RESET_ALL}") + + # 获取配置 + config = get_config(translator) + if not config: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}") + sys.exit(1) + + # 根据操作系统获取路径 + try: + if sys.platform == "win32": # Windows + if not config.has_section('WindowsPaths'): + raise ValueError("Windows paths not configured") + self.db_path = config.get('WindowsPaths', 'sqlite_path') + + elif sys.platform == 'linux': # Linux + if not config.has_section('LinuxPaths'): + raise ValueError("Linux paths not configured") + self.db_path = config.get('LinuxPaths', 'sqlite_path') + + elif sys.platform == 'darwin': # macOS + if not config.has_section('MacPaths'): + raise ValueError("macOS paths not configured") + self.db_path = config.get('MacPaths', 'sqlite_path') + + else: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}") + sys.exit(1) + + # 验证路径是否存在 + if not os.path.exists(os.path.dirname(self.db_path)): + raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}") + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}") sys.exit(1) # 检查数据库文件是否存在 diff --git a/cursor_register.py b/cursor_register.py index 4333259..8f33e42 100644 --- a/cursor_register.py +++ b/cursor_register.py @@ -2,8 +2,6 @@ import os from colorama import Fore, Style, init import time import random -from browser import BrowserManager -from control import BrowserControl from cursor_auth import CursorAuth from reset_machine_manual import MachineIDResetter @@ -35,7 +33,6 @@ class CursorRegistration: self.translator = translator # Set to display mode os.environ['BROWSER_HEADLESS'] = 'False' - self.browser_manager = BrowserManager() self.browser = None self.controller = None self.mail_url = "https://yopmail.com/zh/email-generator" diff --git a/cursor_register_manual.py b/cursor_register_manual.py index 7b50b26..353958b 100644 --- a/cursor_register_manual.py +++ b/cursor_register_manual.py @@ -2,8 +2,6 @@ import os from colorama import Fore, Style, init import time import random -from browser import BrowserManager -from control import BrowserControl from cursor_auth import CursorAuth from reset_machine_manual import MachineIDResetter @@ -35,7 +33,6 @@ class CursorRegistration: self.translator = translator # Set to display mode os.environ['BROWSER_HEADLESS'] = 'False' - self.browser_manager = BrowserManager() self.browser = None self.controller = None self.sign_up_url = "https://authenticator.cursor.sh/sign-up" diff --git a/disable_auto_update.py b/disable_auto_update.py index 93cbff6..ac94fbe 100644 --- a/disable_auto_update.py +++ b/disable_auto_update.py @@ -4,6 +4,7 @@ import platform import shutil from colorama import Fore, Style, init import subprocess +from config import get_config # Initialize colorama init() @@ -24,11 +25,24 @@ class AutoUpdateDisabler: def __init__(self, translator=None): self.translator = translator self.system = platform.system() - self.updater_paths = { - "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"), - "Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"), - "Linux": os.path.expanduser("~/.config/cursor-updater") - } + + # 从配置文件获取路径 + config = get_config(translator) + if config: + if self.system == "Windows": + self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater")) + elif self.system == "Darwin": + self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater")) + elif self.system == "Linux": + self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater")) + else: + # 如果配置加载失败,使用默认路径 + self.updater_paths = { + "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"), + "Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"), + "Linux": os.path.expanduser("~/.config/cursor-updater") + } + self.updater_path = self.updater_paths.get(self.system) def _kill_cursor_processes(self): """End all Cursor processes""" @@ -50,7 +64,7 @@ class AutoUpdateDisabler: def _remove_updater_directory(self): """Delete updater directory""" try: - updater_path = self.updater_paths.get(self.system) + 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}") @@ -72,7 +86,7 @@ class AutoUpdateDisabler: def _create_blocking_file(self): """Create blocking file""" try: - updater_path = self.updater_paths.get(self.system) + 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}") diff --git a/locales/en.json b/locales/en.json index 768d9a6..15b39b1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -160,7 +160,9 @@ "config_option_added": "Config Option Added: {option}", "config_updated": "Config Updated", "password_submitted": "Password Submitted", - "total_usage": "Total Usage: {usage}" + "total_usage": "Total Usage: {usage}", + "setting_on_password": "Setting Password", + "getting_code": "Getting Verification Code, Will Try in 60s" }, "auth": { "title": "Cursor Auth Manager", @@ -270,6 +272,9 @@ "updating": "Updating to the latest version. The program will restart automatically.", "up_to_date": "You are using the latest version.", "check_failed": "Failed to check for updates: {error}", - "continue_anyway": "Continuing with current version..." + "continue_anyway": "Continuing with current version...", + "update_confirm": "Do you want to update to the latest version? (Y/n)", + "update_skipped": "Skipping update.", + "invalid_choice": "Invalid choice. Please enter 'Y' or 'n'." } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index c18277a..191a465 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -159,7 +159,9 @@ "config_option_added": "配置项已添加: {option}", "config_updated": "配置已更新", "password_submitted": "密码已提交", - "total_usage": "总使用量: {usage}" + "total_usage": "总使用量: {usage}", + "setting_on_password": "设置密码", + "getting_code": "获取验证码,将在60秒内尝试..." }, "auth": { "title": "Cursor 认证管理器", @@ -266,6 +268,9 @@ "updating": "正在更新到最新版本。程序将自动重启。", "up_to_date": "您使用的是最新版本。", "check_failed": "检查更新失败: {error}", - "continue_anyway": "继续使用当前版本..." + "continue_anyway": "继续使用当前版本...", + "update_confirm": "是否要更新到最新版本? (Y/n)", + "update_skipped": "跳过更新。", + "invalid_choice": "选择无效。请输入 'Y' 或 'n'." } } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index a9e2b5a..f0a5a39 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -141,7 +141,9 @@ "config_option_added": "配置項已添加: {option}", "config_updated": "配置已更新", "password_submitted": "密碼已提交", - "total_usage": "總使用量: {usage}" + "total_usage": "總使用量: {usage}", + "setting_on_password": "設置密碼", + "getting_code": "正在獲取驗證碼,將在60秒內嘗試..." }, "auth": { "title": "Cursor 認證管理器", @@ -248,6 +250,9 @@ "updating": "正在更新到最新版本。程序將自動重啟。", "up_to_date": "您使用的是最新版本。", "check_failed": "檢查更新失敗: {error}", - "continue_anyway": "繼續使用當前版本..." + "continue_anyway": "繼續使用當前版本...", + "update_confirm": "是否要更新到最新版本? (Y/n)", + "update_skipped": "跳過更新。", + "invalid_choice": "選擇無效。請輸入 'Y' 或 'n'." } } \ No newline at end of file diff --git a/main.py b/main.py index 5cccf0e..231251c 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ import locale import platform import requests import subprocess +from config import get_config # 只在 Windows 系统上导入 windll if platform.system() == 'Windows': @@ -238,6 +239,17 @@ def check_latest_version(): if latest_version != version: print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}") + # 詢問用戶是否要更新 + while True: + choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('updater.update_confirm', choices='Y/n')}: {Style.RESET_ALL}").lower() + if choice in ['', 'y', 'yes']: + break + elif choice in ['n', 'no']: + print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.update_skipped')}{Style.RESET_ALL}") + return + else: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}") + try: # Execute update command based on platform if platform.system() == 'Windows': @@ -287,8 +299,7 @@ def main(): print_logo() # 初始化配置 - from new_signup import setup_config - config = setup_config(translator) + config = get_config(translator) if not config: print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}") return diff --git a/new_signup.py b/new_signup.py index eacfe83..6ef854b 100644 --- a/new_signup.py +++ b/new_signup.py @@ -7,6 +7,7 @@ from colorama import Fore, Style import configparser from pathlib import Path import sys +from config import get_config # 在文件开头添加全局变量 _translator = None @@ -161,120 +162,11 @@ def get_random_wait_time(config, timing_type='page_load_wait'): except: return random.uniform(0.1, 0.8) # 出错时返回默认值 -def setup_config(translator=None): - """Setup configuration file and return config object""" - try: - # Set configuration file path - 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) - os.makedirs(config_dir, exist_ok=True) - - # Read or create configuration file - config = configparser.ConfigParser() - - # 默认配置 - default_config = { - 'Chrome': { - 'chromepath': get_default_chrome_path() - }, - 'Turnstile': { - 'handle_turnstile_time': '2', - 'handle_turnstile_random_time': '1-3' - }, - 'Timing': { - 'min_random_time': '0.1', - 'max_random_time': '0.8', - 'page_load_wait': '0.1-0.8', - 'input_wait': '0.3-0.8', - 'submit_wait': '0.5-1.5', - 'verification_code_input': '0.1-0.3', # 验证码输入间隔 - 'verification_success_wait': '2-3', # 验证成功后等待 - 'verification_retry_wait': '2-3', # 验证重试等待 - 'email_check_initial_wait': '4-6', # 首次等待邮件时间 - 'email_refresh_wait': '2-4', # 邮箱刷新等待时间 - 'settings_page_load_wait': '1-2', # 设置页面加载等待 - 'failed_retry_time': '0.5-1', # 验证失败重试等待时间 - 'retry_interval': '8-12', # 重试间隔时间 - 'max_timeout': '160' # 最大超时时间 - } - } - - # Add OS-specific path configurations - if sys.platform == "win32": - appdata = os.getenv("APPDATA") - default_config['WindowsPaths'] = { - 'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"), - 'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"), - 'machine_id_path': os.path.join(os.getenv("APPDATA"), "Cursor", "machineId"), - 'cursor_path': os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app") - } - elif sys.platform == "darwin": - default_config['MacPaths'] = { - 'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")), - 'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")), - 'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"), - 'cursor_path': "/Applications/Cursor.app/Contents/Resources/app" - } - elif sys.platform == "linux": - sudo_user = os.environ.get('SUDO_USER') - actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~") - - default_config['LinuxPaths'] = { - 'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")), - 'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")), - 'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"), - 'cursor_path': "/opt/Cursor/resources/app" # 默認路徑 - } - - if os.path.exists(config_file): - config.read(config_file) - config_modified = False - - # 检查并添加缺失的配置项 - for section, options in default_config.items(): - if not config.has_section(section): - config.add_section(section) - config_modified = True - for option, value in options.items(): - if not config.has_option(section, option): - config.set(section, option, value) - config_modified = True - if translator: - print(f"{Fore.YELLOW}ℹ️ {translator.get('register.config_option_added', option=f'{section}.{option}') if translator else 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') if translator else '配置文件已更新'}{Style.RESET_ALL}") - else: - # 创建新配置文件 - config = configparser.ConfigParser() - for section, options in default_config.items(): - config.add_section(section) - for option, value in options.items(): - config.set(section, option, value) - - with open(config_file, 'w', encoding='utf-8') as f: - config.write(f) - if translator: - print(f"{Fore.GREEN}✅ {translator.get('register.config_created') if translator else '已创建配置文件'}: {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)) if translator else f'配置设置出错: {str(e)}'}{Style.RESET_ALL}") - raise - def setup_driver(translator=None): """Setup browser driver""" try: # 获取配置 - config = setup_config(translator) + config = get_config(translator) # Get Chrome path chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path()) @@ -449,57 +341,39 @@ def generate_password(length=12): chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" return ''.join(random.choices(chars, k=length)) -def fill_password(page, password: str, config, translator=None) -> bool: +def fill_password(page, password: str, config, translator=None): """ 填写密码表单 """ try: print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}") - # 等待密码框出现并尝试多次 - max_retries = 5 - for i in range(max_retries): - # 检查是否出现错误信息 - if page.ele("This email is not available."): - print(f"{Fore.RED}❌ {translator.get('register.email_used') if translator else '注册失败:邮箱已被使用'}{Style.RESET_ALL}") - return False + # 填写密码 + password_input = page.ele("@name=password") + print(f"{Fore.CYAN}🔑 {translator.get('register.setting_on_password')}: {password}{Style.RESET_ALL}") + if password_input: + password_input.input(password) - # 查找密码输入框 - password_input = page.ele("@name=password") - if password_input: - # 清除可能存在的旧值并输入新密码 - password_input.click() - time.sleep(get_random_wait_time(config, 'input_wait')) - password_input.input(password) - time.sleep(get_random_wait_time(config, 'input_wait')) - - # 查找并点击提交按钮 - submit_button = page.ele("@type=submit") - if submit_button: - submit_button.click() - print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}") - time.sleep(get_random_wait_time(config, 'submit_wait')) - return True - else: - print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_submit') if translator else '未找到提交按钮,重试中...'}{Style.RESET_ALL}") + # 点击提交按钮 + submit_button = page.ele("@type=submit") + if submit_button: + submit_button.click() + time.sleep(get_random_wait_time(config, 'submit_wait')) - # 如果没找到密码框,等待后重试 - time.sleep(get_random_wait_time(config, 'failed_retry_time')) - if i < max_retries - 1: # 不是最后一次尝试时才打印 - print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_password', attempt=i+1) if translator else f'第 {i+1} 次尝试设置密码...'}{Style.RESET_ALL}") - - print(f"{Fore.RED}❌ {translator.get('register.password_set_failed') if translator else '密码设置失败:超过重试次数'}{Style.RESET_ALL}") - return False - + print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}") + + return True + except Exception as e: print(f"{Fore.RED}❌ {translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}") + return False -def handle_verification_code(browser_tab, email_tab, controller, email, password, config, translator=None): +def handle_verification_code(browser_tab, email_tab, controller, config, translator=None): """处理验证码""" try: if translator: - print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") # 检查是否使用手动输入验证码 if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式 @@ -520,7 +394,7 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password time.sleep(get_random_wait_time(config, 'verification_retry_wait')) # 访问设置页面 - print(f"{Fore.CYAN} {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}") + print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}") browser_tab.get("https://www.cursor.com/settings") time.sleep(get_random_wait_time(config, 'settings_page_load_wait')) return True, browser_tab @@ -529,7 +403,7 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password # 自动获取验证码逻辑 elif email_tab: - print(f"{translator.get('register.waiting_for_verification_code')}") + print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") time.sleep(get_random_wait_time(config, 'email_check_initial_wait')) # 使用已有的 email_tab 刷新邮箱 @@ -703,13 +577,11 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N # 访问注册页面 url = "https://authenticator.cursor.sh/sign-up" - if translator: - print(f"\n{Fore.CYAN}{translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}") # 访问页面 simulate_human_input(page, url, config, translator) if translator: - print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}") time.sleep(get_random_wait_time(config, 'page_load_wait')) # 如果没有提供账号信息,则生成随机信息 @@ -729,34 +601,33 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N # 填写表单 if fill_signup_form(page, first_name, last_name, email, config, translator): if translator: - print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}") + print(f"\n{Fore.GREEN}✅ {translator.get('register.form_submitted')}{Style.RESET_ALL}") # 处理第一次 Turnstile 验证 if handle_turnstile(page, config, translator): if translator: - print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}") + print(f"\n{Fore.GREEN}✅ {translator.get('register.first_verification_passed')}{Style.RESET_ALL}") # 填写密码 if fill_password(page, password, config, translator): if translator: - print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}") - time.sleep(2) - + print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}") + # 处理第二次 Turnstile 验证 if handle_turnstile(page, config, translator): if translator: - print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") - if handle_verification_code(page, email_tab, controller, email, password, config, translator): + print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") + if handle_verification_code(page, email_tab, controller, config, translator): success = True return True, page else: - print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}") + print(f"\n{Fore.RED}❌ {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}") else: - print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") + print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") else: - print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") + print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") else: - print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}") + print(f"\n{Fore.RED}❌ {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}") return False, None diff --git a/reset_machine_manual.py b/reset_machine_manual.py index 4c6c056..e0ce8c2 100644 --- a/reset_machine_manual.py +++ b/reset_machine_manual.py @@ -13,6 +13,7 @@ from typing import Tuple import configparser from new_signup import get_user_documents_path import traceback +from config import get_config # Initialize colorama init() @@ -39,7 +40,7 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]: if not os.path.exists(config_file): raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件") - config.read(config_file) + config.read(config_file, encoding='utf-8') # 指定編碼 # 根據系統獲取路徑 if system == "Darwin": @@ -408,7 +409,7 @@ class MachineIDResetter: if not os.path.exists(config_file): raise FileNotFoundError(f"Config file not found: {config_file}") - config.read(config_file) + config.read(config_file, encoding='utf-8') # Check operating system if sys.platform == "win32": # Windows @@ -690,7 +691,9 @@ class MachineIDResetter: return False def run(translator=None): - """Convenient function for directly calling the reset function""" + 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}") diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..b8c083b --- /dev/null +++ b/utils.py @@ -0,0 +1,32 @@ +import os +import sys +import platform + +def get_user_documents_path(): + """Get user documents path""" + if platform.system() == "Windows": + return os.path.expanduser("~\\Documents") + else: + return os.path.expanduser("~/Documents") + +def get_default_chrome_path(): + """Get default Chrome path""" + if sys.platform == "win32": + return r"C:\Program Files\Google\Chrome\Application\chrome.exe" + elif sys.platform == "darwin": + return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + else: + return "/usr/bin/google-chrome" + +def get_linux_cursor_path(): + """Get Linux Cursor path""" + possible_paths = [ + "/opt/Cursor/resources/app", + "/usr/share/cursor/resources/app", + "/opt/cursor-bin/resources/app", + "/usr/lib/cursor/resources/app", + os.path.expanduser("~/.local/share/cursor/resources/app") + ] + + # 返回第一个存在的路径,如果都不存在则返回第一个路径 + return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0]) \ No newline at end of file