From b3e7c101d37e2db7fe044ca313a574567e3397fd Mon Sep 17 00:00:00 2001 From: Saad <70136884+saadtahir995@users.noreply.github.com> Date: Sat, 5 Apr 2025 12:16:02 +0500 Subject: [PATCH 1/2] feat: Added Google OAuth Acc Deletion Tool --- .env | 2 +- CHANGELOG.md | 5 + README.md | 2 + delete_cursor_google.py | 386 ++++++++++++++++++++++++++++++++++++++++ locales/en.json | 65 ++++++- locales/zh_cn.json | 46 ++++- main.py | 9 +- 7 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 delete_cursor_google.py diff --git a/.env b/.env index 1199388..48cb412 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ version=1.8.05 -VERSION=1.8.05 +VERSION=1.8.06 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f9bfcd..4bc0cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## v1.8.06 +1. Add: Google Account Deletion Feature | 添加 Google 账号删除功能 +2. Update: Menu with new account deletion option | 更新菜单添加账号删除选项 +3. Add: Multilanguage support for account deletion | 添加账号删除功能的多语言支持 + ## v1.8.05 1. Fix: Linux Path Not Found | 修復linuxpath問題 2. Add: support for detecting both 150/150 and 50/50 usage limits | 添加偵測50 或者150的使用量 diff --git a/README.md b/README.md index 1f62b46..3060c57 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ Always clean your browser's cache and cookies. If possible, use a VPN to create * Reset Cursor's configuration
重置 Cursor 的配置
+* Delete Cursor Google Account
删除 Cursor Google 账号
+ * Multi-language support (English, 简体中文, 繁體中文, Vietnamese)
多語言支持(英文、简体中文、繁體中文、越南語)
## 💻 System Support | 系統支持 diff --git a/delete_cursor_google.py b/delete_cursor_google.py new file mode 100644 index 0000000..39b3457 --- /dev/null +++ b/delete_cursor_google.py @@ -0,0 +1,386 @@ +from oauth_auth import OAuthHandler +import time +from colorama import Fore, Style, init +import sys + +# Initialize colorama +init() + +# Define emoji constants +EMOJI = { + 'START': '🚀', + 'DELETE': '🗑️', + 'SUCCESS': '✅', + 'ERROR': '❌', + 'WAIT': '⏳', + 'INFO': 'ℹ️', + 'WARNING': '⚠️' +} + +class CursorGoogleAccountDeleter(OAuthHandler): + def __init__(self, translator=None): + super().__init__(translator, auth_type='google') + + def delete_google_account(self): + """Delete Cursor account using Google OAuth""" + try: + # Setup browser and select profile + if not self.setup_browser(): + return False + + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.starting_process') if self.translator else 'Starting account deletion process...'}{Style.RESET_ALL}") + + # Navigate to Cursor auth page - using the same URL as in registration + self.browser.get("https://authenticator.cursor.sh/sign-up") + time.sleep(2) + + # Click Google auth button using same selectors as in registration + selectors = [ + "//a[contains(@href,'GoogleOAuth')]", + "//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]", + "(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback + ] + + auth_btn = None + for selector in selectors: + try: + auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2) + if auth_btn: + break + except: + continue + + if not auth_btn: + raise Exception(self.translator.get('account_delete.google_button_not_found') if self.translator else "Google login button not found") + + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.logging_in') if self.translator else 'Logging in with Google...'}{Style.RESET_ALL}") + auth_btn.click() + + # Wait for authentication to complete using a more robust method + print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('account_delete.waiting_for_auth', fallback='Waiting for Google authentication...')}{Style.RESET_ALL}") + + # Dynamic wait for authentication + max_wait_time = 120 # Increase maximum wait time to 120 seconds + start_time = time.time() + check_interval = 3 # Check every 3 seconds + google_account_alert_shown = False # Track if we've shown the alert already + + while time.time() - start_time < max_wait_time: + current_url = self.browser.url + + # If we're already on the settings or dashboard page, we're successful + if "/dashboard" in current_url or "/settings" in current_url or "cursor.com" in current_url: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.login_successful') if self.translator else 'Login successful'}{Style.RESET_ALL}") + break + + # If we're on Google accounts page or accounts.google.com, wait for user selection + if "accounts.google.com" in current_url: + # Only show the alert once to avoid spamming + if not google_account_alert_shown: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.select_google_account', fallback='Please select your Google account...')}{Style.RESET_ALL}") + # Alert to indicate user action needed + try: + self.browser.run_js(""" + alert('Please select your Google account to continue with Cursor authentication'); + """) + google_account_alert_shown = True # Mark that we've shown the alert + except: + pass # Alert is optional + + # Sleep before checking again + time.sleep(check_interval) + else: + # If the loop completed without breaking, it means we hit the timeout + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.auth_timeout', fallback='Authentication timeout, continuing anyway...')}{Style.RESET_ALL}") + + # Check current URL to determine next steps + current_url = self.browser.url + + # If we're already on the settings page, no need to navigate + if "/settings" in current_url and "cursor.com" in current_url: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.already_on_settings', fallback='Already on settings page')}{Style.RESET_ALL}") + # If we're on the dashboard or any Cursor page but not settings, navigate to settings + elif "cursor.com" in current_url or "authenticator.cursor.sh" in current_url: + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.navigating_to_settings', fallback='Navigating to settings page...')}{Style.RESET_ALL}") + self.browser.get("https://www.cursor.com/settings") + # If we're still on Google auth or somewhere else, try directly going to settings + else: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.login_redirect_failed', fallback='Login redirection failed, trying direct navigation...')}{Style.RESET_ALL}") + self.browser.get("https://www.cursor.com/settings") + + # Wait for the settings page to load + time.sleep(3) # Reduced from 5 seconds + + # First look for the email element to confirm we're logged in + try: + email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)") + if email_element: + email = email_element.text + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.found_email', email=email, fallback=f'Found email: {email}')}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.email_not_found', error=str(e), fallback=f'Email not found: {str(e)}')}{Style.RESET_ALL}") + + # Click on "Advanced" tab or dropdown - keep only the successful approach + advanced_found = False + + # Direct JavaScript querySelector approach that worked according to logs + try: + advanced_element_js = self.browser.run_js(""" + // Try to find the Advanced dropdown using querySelector with the exact classes + let advancedElement = document.querySelector('div.mb-0.flex.cursor-pointer.items-center.text-xs:not([style*="display: none"])'); + + // If not found, try a more general approach + if (!advancedElement) { + const allDivs = document.querySelectorAll('div'); + for (const div of allDivs) { + if (div.textContent.includes('Advanced') && + div.className.includes('mb-0') && + div.className.includes('flex') && + div.className.includes('cursor-pointer')) { + advancedElement = div; + break; + } + } + } + + // Click the element if found + if (advancedElement) { + advancedElement.click(); + return true; + } + + return false; + """) + + if advanced_element_js: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found and clicked Advanced using direct JavaScript selector{Style.RESET_ALL}") + advanced_found = True + time.sleep(1) # Reduced from 2 seconds + except Exception as e: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} JavaScript querySelector approach failed: {str(e)}{Style.RESET_ALL}") + + if not advanced_found: + # Fallback to direct URL navigation which is faster and more reliable + try: + self.browser.get("https://www.cursor.com/settings?tab=advanced") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('account_delete.direct_advanced_navigation', fallback='Trying direct navigation to advanced tab')}{Style.RESET_ALL}") + advanced_found = True + except: + raise Exception(self.translator.get('account_delete.advanced_tab_not_found') if self.translator else "Advanced option not found after multiple attempts") + + # Wait for dropdown/tab content to load + time.sleep(2) # Reduced from 4 seconds + + # Find and click the "Delete Account" button + delete_button_found = False + + # Simplified approach for delete button based on what worked + delete_button_selectors = [ + 'xpath://button[contains(., "Delete Account")]', + 'xpath://button[text()="Delete Account"]', + 'xpath://div[contains(text(), "Delete Account")]', + 'xpath://button[contains(text(), "Delete") and contains(text(), "Account")]' + ] + + for selector in delete_button_selectors: + try: + delete_button = self.browser.ele(selector, timeout=2) + if delete_button: + delete_button.click() + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.delete_button_clicked') if self.translator else 'Clicked on Delete Account button'}{Style.RESET_ALL}") + delete_button_found = True + break + except: + continue + + if not delete_button_found: + raise Exception(self.translator.get('account_delete.delete_button_not_found') if self.translator else "Delete Account button not found") + + # Wait for confirmation dialog to appear + time.sleep(2) + + # Check if we need to input "Delete" at all - some modals might not require it + input_required = True + try: + # Try detecting if the DELETE button is already enabled + delete_button_enabled = self.browser.run_js(""" + const buttons = Array.from(document.querySelectorAll('button')); + const deleteButtons = buttons.filter(btn => + btn.textContent.trim() === 'DELETE' || + btn.textContent.trim() === 'Delete' + ); + + if (deleteButtons.length > 0) { + return !deleteButtons.some(btn => btn.disabled); + } + return false; + """) + + if delete_button_enabled: + print(f"{Fore.CYAN}{EMOJI['INFO']} DELETE button appears to be enabled already. Input may not be required.{Style.RESET_ALL}") + input_required = False + except: + pass + + # Type "Delete" in the confirmation input - only if required + delete_input_found = False + + if input_required: + # Try common selectors for the input field + delete_input_selectors = [ + 'xpath://input[@placeholder="Delete"]', + 'xpath://div[contains(@class, "modal")]//input', + 'xpath://input', + 'css:input' + ] + + for selector in delete_input_selectors: + try: + delete_input = self.browser.ele(selector, timeout=3) + if delete_input: + delete_input.clear() + delete_input.input("Delete") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Typed \"Delete\" in confirmation box{Style.RESET_ALL}") + delete_input_found = True + time.sleep(2) + break + except: + # Try direct JavaScript input as fallback + try: + self.browser.run_js(f""" + arguments[0].value = "Delete"; + const event = new Event('input', {{ bubbles: true }}); + arguments[0].dispatchEvent(event); + const changeEvent = new Event('change', {{ bubbles: true }}); + arguments[0].dispatchEvent(changeEvent); + """, delete_input) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Typed \"Delete\" using JavaScript{Style.RESET_ALL}") + delete_input_found = True + time.sleep(2) + break + except: + continue + + if not delete_input_found: + print(f"{Fore.YELLOW}{EMOJI['WARNING']} Delete confirmation input not found, continuing anyway{Style.RESET_ALL}") + time.sleep(2) + + # Wait before clicking the final DELETE button + time.sleep(2) + + # Click on the final DELETE button + confirm_button_found = False + + # Use JavaScript approach for the DELETE button + try: + delete_button_js = self.browser.run_js(""" + // Try to find the DELETE button by exact text content + const buttons = Array.from(document.querySelectorAll('button')); + const deleteButton = buttons.find(btn => + btn.textContent.trim() === 'DELETE' || + btn.textContent.trim() === 'Delete' + ); + + if (deleteButton) { + console.log("Found DELETE button with JavaScript"); + deleteButton.click(); + return true; + } + + // If not found by text, try to find right-most button in the modal + const modalButtons = Array.from(document.querySelectorAll('.relative button, [role="dialog"] button, .modal button, [aria-modal="true"] button')); + + if (modalButtons.length > 1) { + modalButtons.sort((a, b) => { + const rectA = a.getBoundingClientRect(); + const rectB = b.getBoundingClientRect(); + return rectB.right - rectA.right; + }); + + console.log("Clicking right-most button in modal"); + modalButtons[0].click(); + return true; + } else if (modalButtons.length === 1) { + console.log("Clicking single button found in modal"); + modalButtons[0].click(); + return true; + } + + return false; + """) + + if delete_button_js: + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Clicked DELETE button{Style.RESET_ALL}") + confirm_button_found = True + except: + pass + + if not confirm_button_found: + # Fallback to simple selectors + delete_button_selectors = [ + 'xpath://button[text()="DELETE"]', + 'xpath://div[contains(@class, "modal")]//button[last()]' + ] + + for selector in delete_button_selectors: + try: + delete_button = self.browser.ele(selector, timeout=2) + if delete_button: + delete_button.click() + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account deleted successfully!{Style.RESET_ALL}") + confirm_button_found = True + break + except: + continue + + if not confirm_button_found: + raise Exception(self.translator.get('account_delete.confirm_button_not_found') if self.translator else "Confirm button not found") + + # Wait a moment to see the confirmation + time.sleep(2) + + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('account_delete.error', error=str(e)) if self.translator else f'Error during account deletion: {str(e)}'}{Style.RESET_ALL}") + return False + finally: + # Clean up browser + if self.browser: + try: + self.browser.quit() + except: + pass + +def main(translator=None): + """Main function to handle Google account deletion""" + print(f"\n{Fore.CYAN}{EMOJI['START']} {translator.get('account_delete.title') if translator else 'Cursor Google Account Deletion Tool'}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}") + + deleter = CursorGoogleAccountDeleter(translator) + + try: + # Ask for confirmation + print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}") + confirm = input(f"{Fore.RED}Are you sure you want to proceed? (y/N): {Style.RESET_ALL}").lower() + + if confirm != 'y': + print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}") + return + + success = deleter.delete_google_account() + + if success: + print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('account_delete.success') if translator else 'Your Cursor account has been successfully deleted!'}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.failed') if translator else 'Account deletion process failed or was cancelled.'}{Style.RESET_ALL}") + + except KeyboardInterrupt: + print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.interrupted') if translator else 'Account deletion process interrupted by user.'}{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}") + finally: + print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 6868432..05e29cc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -25,7 +25,8 @@ "coming_soon": "Coming Soon", "fixed_soon": "Fixed Soon", "contribute": "Contribute to the Project", - "config": "Show Config" + "config": "Show Config", + "delete_google_account": "Delete Cursor Google Account" }, "languages": { "en": "English", @@ -433,7 +434,24 @@ "cursor_reset_completed": "Cursor AI Editor has been fully reset and trial detection bypassed!", "cursor_reset_failed": "Cursor AI Editor reset failed: {error}", "cursor_reset_cancelled": "Cursor AI Editor reset cancelled. Exiting without making any changes.", - "operation_cancelled": "Operation cancelled. Exiting without making any changes." + "operation_cancelled": "Operation cancelled. Exiting without making any changes.", + "navigating_to_settings": "Navigating to settings page...", + "already_on_settings": "Already on settings page", + "login_redirect_failed": "Login redirection failed, trying direct navigation...", + "advanced_tab_not_found": "Advanced tab not found after multiple attempts", + "advanced_tab_retry": "Advanced tab not found, attempt {attempt}/{max_attempts}", + "advanced_tab_error": "Error finding Advanced tab: {error}", + "advanced_tab_clicked": "Clicked on Advanced tab", + "direct_advanced_navigation": "Trying direct navigation to advanced tab", + "delete_button_not_found": "Delete Account button not found after multiple attempts", + "delete_button_retry": "Delete button not found, attempt {attempt}/{max_attempts}", + "delete_button_error": "Error finding Delete button: {error}", + "delete_button_clicked": "Clicked on Delete Account button", + "found_danger_zone": "Found Danger Zone section", + "delete_input_not_found": "Delete confirmation input not found after multiple attempts", + "delete_input_retry": "Delete input not found, attempt {attempt}/{max_attempts}", + "delete_input_error": "Error finding Delete input: {error}", + "delete_input_not_found_continuing": "Delete confirmation input not found, trying to continue anyway" }, "github_register": { "title": "GitHub + Cursor AI Registration Automation", @@ -594,5 +612,48 @@ "profile_selected": "Selected profile: {profile}", "invalid_selection": "Invalid selection. Please try again", "warning_chrome_close": "Warning: This will close all running Chrome processes" + }, + "account_delete": { + "title": "Cursor Google Account Deletion Tool", + "warning": "WARNING: This will permanently delete your Cursor account. This action cannot be undone.", + "cancelled": "Account deletion cancelled.", + "starting_process": "Starting account deletion process...", + "google_button_not_found": "Google login button not found", + "logging_in": "Logging in with Google...", + "waiting_for_auth": "Waiting for Google authentication...", + "login_successful": "Login successful", + "unexpected_page": "Unexpected page after login: {url}", + "trying_settings": "Trying to navigate to settings page...", + "select_google_account": "Please select your Google account...", + "auth_timeout": "Authentication timeout, continuing anyway...", + "navigating_to_settings": "Navigating to settings page...", + "already_on_settings": "Already on settings page", + "login_redirect_failed": "Login redirection failed, trying direct navigation...", + "advanced_tab_not_found": "Advanced tab not found after multiple attempts", + "advanced_tab_retry": "Advanced tab not found, attempt {attempt}/{max_attempts}", + "advanced_tab_error": "Error finding Advanced tab: {error}", + "advanced_tab_clicked": "Clicked on Advanced tab", + "direct_advanced_navigation": "Trying direct navigation to advanced tab", + "delete_button_not_found": "Delete Account button not found after multiple attempts", + "delete_button_retry": "Delete button not found, attempt {attempt}/{max_attempts}", + "delete_button_error": "Error finding Delete button: {error}", + "delete_button_clicked": "Clicked on Delete Account button", + "found_danger_zone": "Found Danger Zone section", + "delete_input_not_found": "Delete confirmation input not found after multiple attempts", + "delete_input_retry": "Delete input not found, attempt {attempt}/{max_attempts}", + "delete_input_error": "Error finding Delete input: {error}", + "delete_input_not_found_continuing": "Delete confirmation input not found, trying to continue anyway", + "typed_delete": "Typed \"Delete\" in confirmation box", + "confirm_button_not_found": "Confirm button not found after multiple attempts", + "confirm_button_retry": "Confirm button not found, attempt {attempt}/{max_attempts}", + "confirm_button_error": "Error finding Confirm button: {error}", + "account_deleted": "Account deleted successfully!", + "error": "Error during account deletion: {error}", + "success": "Your Cursor account has been successfully deleted!", + "failed": "Account deletion process failed or was cancelled.", + "interrupted": "Account deletion process interrupted by user.", + "unexpected_error": "Unexpected error: {error}", + "found_email": "Found email: {email}", + "email_not_found": "Email not found: {error}" } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index f94ce50..321e807 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -25,7 +25,8 @@ "coming_soon": "即将推出", "fixed_soon": "即将修复", "contribute": "贡献项目", - "config": "显示配置" + "config": "显示配置", + "delete_google_account": "删除 Cursor Google 账号" }, "languages": { "en": "英语", @@ -589,5 +590,48 @@ "profile_selected": "已选择配置文件:{profile}", "invalid_selection": "选择无效。请重试", "warning_chrome_close": "警告:这将关闭所有正在运行的Chrome进程" + }, + "account_delete": { + "title": "Cursor Google 账号删除工具", + "warning": "警告:这将永久删除您的 Cursor 账号。此操作无法撤销。", + "cancelled": "账号删除已取消。", + "starting_process": "开始账号删除过程...", + "google_button_not_found": "未找到 Google 登录按钮", + "logging_in": "正在使用 Google 登录...", + "waiting_for_auth": "等待 Google 验证...", + "login_successful": "登录成功", + "unexpected_page": "登录后页面异常:{url}", + "trying_settings": "尝试导航到设置页面...", + "select_google_account": "请选择您的 Google 账号...", + "auth_timeout": "认证超时,继续执行...", + "navigating_to_settings": "正在导航到设置页面...", + "already_on_settings": "已在设置页面", + "login_redirect_failed": "登录重定向失败,尝试直接导航...", + "advanced_tab_not_found": "多次尝试后未找到高级选项卡", + "advanced_tab_retry": "未找到高级选项卡,尝试 {attempt}/{max_attempts}", + "advanced_tab_error": "查找高级选项卡时出错:{error}", + "advanced_tab_clicked": "已点击高级选项卡", + "direct_advanced_navigation": "尝试直接导航到高级选项卡", + "delete_button_not_found": "多次尝试后未找到删除账号按钮", + "delete_button_retry": "未找到删除按钮,尝试 {attempt}/{max_attempts}", + "delete_button_error": "查找删除按钮时出错:{error}", + "delete_button_clicked": "已点击删除账号按钮", + "delete_input_not_found": "多次尝试后未找到删除确认输入框", + "delete_input_retry": "未找到删除输入框,尝试 {attempt}/{max_attempts}", + "delete_input_error": "查找删除输入框时出错:{error}", + "delete_input_not_found_continuing": "未找到删除确认输入框,尝试继续执行...", + "typed_delete": "已在确认框中输入\"Delete\"", + "confirm_button_not_found": "多次尝试后未找到确认按钮", + "confirm_button_retry": "未找到确认按钮,尝试 {attempt}/{max_attempts}", + "confirm_button_error": "查找确认按钮时出错:{error}", + "account_deleted": "账号删除成功!", + "error": "账号删除过程中出错:{error}", + "success": "您的 Cursor 账号已成功删除!", + "failed": "账号删除过程失败或已取消。", + "interrupted": "账号删除过程被用户中断。", + "unexpected_error": "意外错误:{error}", + "found_email": "找到邮箱:{email}", + "email_not_found": "未找到邮箱: {error}", + "found_danger_zone": "已找到危险区域部分" } } \ No newline at end of file diff --git a/main.py b/main.py index 7a2ad94..89d7c07 100644 --- a/main.py +++ b/main.py @@ -285,7 +285,8 @@ def print_menu(): 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')}", - 13: f"{Fore.GREEN}13{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.select_chrome_profile')}" + 13: f"{Fore.GREEN}13{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.select_chrome_profile')}", + 14: f"{Fore.GREEN}14{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.delete_google_account', fallback='Delete Cursor Google Account')}" } # Automatically calculate the number of menu items in the left and right columns @@ -557,7 +558,7 @@ def main(): while True: try: - choice_num = 13 + choice_num = 14 choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}") if choice == "0": @@ -621,6 +622,10 @@ def main(): if user_data_dir: oauth._select_profile(user_data_dir) print_menu() + elif choice == "14": + import delete_cursor_google + delete_cursor_google.main(translator) + print_menu() else: print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}") print_menu() From 35e01edf9c1b9d95ab4397cb5346c4ef8f8dd975 Mon Sep 17 00:00:00 2001 From: Pin Studios Date: Sat, 5 Apr 2025 18:30:44 +0800 Subject: [PATCH 2/2] feat: Enhance account deletion process with improved translations and error handling - Added multilingual support for account deletion messages in delete_cursor_google.py and oauth_auth.py - Updated CHANGELOG.md to reflect new features and fixes - Improved user prompts and error messages for better clarity and user experience --- CHANGELOG.md | 12 +++++++--- delete_cursor_google.py | 16 ++++++------- locales/en.json | 10 +++++--- locales/zh_cn.json | 10 +++++--- locales/zh_tw.json | 53 +++++++++++++++++++++++++++++++++++++++-- oauth_auth.py | 12 +++++----- 6 files changed, 88 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc0cd9..6c98613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Change Log ## v1.8.06 -1. Add: Google Account Deletion Feature | 添加 Google 账号删除功能 -2. Update: Menu with new account deletion option | 更新菜单添加账号删除选项 -3. Add: Multilanguage support for account deletion | 添加账号删除功能的多语言支持 +1. Add: Google Account Deletion Feature | 添加 Google 账号删除功能 +2. Update: Menu with new account deletion option | 更新菜单添加账号删除选项 +3. Add: Multilanguage support for account deletion | 添加账号删除功能的多语言支持 +4. Fix: Improve usage limits check and tuple index error | 修复使用限制检查和元组索引错误 +5. Fix: bug in disable cursor auto update | 修复禁用 Cursor 自动更新的错误 +6. Fix: Linux-appimage | 修复 Linux-appimage 问题 +7. Add: Support for custom Cursor installation paths on Windows | 添加 Windows 系统下自定义 Cursor 安装路径支持 +8. Add: Chrome profile selection feature | 添加 Chrome 配置文件选择功能 +9. Fix: improve account usage limit detection | 修復賬號檢測 ## v1.8.05 1. Fix: Linux Path Not Found | 修復linuxpath問題 diff --git a/delete_cursor_google.py b/delete_cursor_google.py index 39b3457..baa233f 100644 --- a/delete_cursor_google.py +++ b/delete_cursor_google.py @@ -153,11 +153,11 @@ class CursorGoogleAccountDeleter(OAuthHandler): """) if advanced_element_js: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found and clicked Advanced using direct JavaScript selector{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.advanced_tab_clicked', fallback='Found and clicked Advanced using direct JavaScript selector')}{Style.RESET_ALL}") advanced_found = True time.sleep(1) # Reduced from 2 seconds except Exception as e: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} JavaScript querySelector approach failed: {str(e)}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.advanced_tab_error', error=str(e), fallback='JavaScript querySelector approach failed: {str(e)}')}{Style.RESET_ALL}") if not advanced_found: # Fallback to direct URL navigation which is faster and more reliable @@ -240,7 +240,7 @@ class CursorGoogleAccountDeleter(OAuthHandler): if delete_input: delete_input.clear() delete_input.input("Delete") - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Typed \"Delete\" in confirmation box{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete', fallback='Typed \"Delete\" in confirmation box')}{Style.RESET_ALL}") delete_input_found = True time.sleep(2) break @@ -254,7 +254,7 @@ class CursorGoogleAccountDeleter(OAuthHandler): const changeEvent = new Event('change', {{ bubbles: true }}); arguments[0].dispatchEvent(changeEvent); """, delete_input) - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Typed \"Delete\" using JavaScript{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete_js', fallback='Typed \"Delete\" using JavaScript')}{Style.RESET_ALL}") delete_input_found = True time.sleep(2) break @@ -262,7 +262,7 @@ class CursorGoogleAccountDeleter(OAuthHandler): continue if not delete_input_found: - print(f"{Fore.YELLOW}{EMOJI['WARNING']} Delete confirmation input not found, continuing anyway{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.delete_input_not_found', fallback='Delete confirmation input not found, continuing anyway')}{Style.RESET_ALL}") time.sleep(2) # Wait before clicking the final DELETE button @@ -310,7 +310,7 @@ class CursorGoogleAccountDeleter(OAuthHandler): """) if delete_button_js: - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Clicked DELETE button{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Clicked DELETE button')}{Style.RESET_ALL}") confirm_button_found = True except: pass @@ -327,7 +327,7 @@ class CursorGoogleAccountDeleter(OAuthHandler): delete_button = self.browser.ele(selector, timeout=2) if delete_button: delete_button.click() - print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account deleted successfully!{Style.RESET_ALL}") + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Account deleted successfully!')}{Style.RESET_ALL}") confirm_button_found = True break except: @@ -362,7 +362,7 @@ def main(translator=None): try: # Ask for confirmation print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}") - confirm = input(f"{Fore.RED}Are you sure you want to proceed? (y/N): {Style.RESET_ALL}").lower() + confirm = input(f"{Fore.RED} {translator.get('account_delete.confirm_prompt') if translator else 'Are you sure you want to proceed? (y/N): '}{Style.RESET_ALL}").lower() if confirm != 'y': print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}") diff --git a/locales/en.json b/locales/en.json index 05e29cc..e1af20f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -26,7 +26,9 @@ "fixed_soon": "Fixed Soon", "contribute": "Contribute to the Project", "config": "Show Config", - "delete_google_account": "Delete Cursor Google Account" + "delete_google_account": "Delete Cursor Google Account", + "continue_prompt": "Continue? (y/N): ", + "operation_cancelled_by_user": "Operation cancelled by user" }, "languages": { "en": "English", @@ -599,7 +601,8 @@ "authentication_successful_getting_account_info": "Authentication successful, getting account info...", "warning_could_not_kill_existing_browser_processes": "Warning: Could not kill existing browser processes: {error}", "browser_failed_to_start": "Browser failed to start: {error}", - "browser_failed": "Browser failed to start: {error}" + "browser_failed": "Browser failed to start: {error}", + "browser_failed_to_start_fallback": "Browser failed to start: {error}" }, "chrome_profile": { "title": "Chrome Profile Selection", @@ -654,6 +657,7 @@ "interrupted": "Account deletion process interrupted by user.", "unexpected_error": "Unexpected error: {error}", "found_email": "Found email: {email}", - "email_not_found": "Email not found: {error}" + "email_not_found": "Email not found: {error}", + "confirm_prompt": "Are you sure you want to proceed? (y/N): " } } \ No newline at end of file diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 321e807..1bffbd6 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -26,7 +26,9 @@ "fixed_soon": "即将修复", "contribute": "贡献项目", "config": "显示配置", - "delete_google_account": "删除 Cursor Google 账号" + "delete_google_account": "删除 Cursor Google 账号", + "continue_prompt": "继续?(y/N): ", + "operation_cancelled_by_user": "操作被用户取消" }, "languages": { "en": "英语", @@ -577,7 +579,8 @@ "authentication_successful_getting_account_info": "认证成功, 获取账户信息...", "warning_could_not_kill_existing_browser_processes": "警告: 无法杀死现有浏览器进程: {error}", "browser_failed_to_start": "浏览器启动失败: {error}", - "browser_failed": "浏览器启动失败: {error}" + "browser_failed": "浏览器启动失败: {error}", + "browser_failed_to_start_fallback": "浏览器启动失败: {error}" }, "chrome_profile": { "title": "Chrome配置文件选择", @@ -632,6 +635,7 @@ "unexpected_error": "意外错误:{error}", "found_email": "找到邮箱:{email}", "email_not_found": "未找到邮箱: {error}", - "found_danger_zone": "已找到危险区域部分" + "found_danger_zone": "已找到危险区域部分", + "confirm_prompt": "您确定要继续吗?(y/N): " } } \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index 1748909..dc0acd4 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -25,7 +25,10 @@ "coming_soon": "即將推出", "fixed_soon": "即將修復", "contribute": "貢獻項目", - "config": "顯示配置" + "config": "顯示配置", + "delete_google_account": "刪除 Cursor Google 帳號", + "continue_prompt": "繼續?(y/N): ", + "operation_cancelled_by_user": "操作被使用者取消" }, "languages": { "en": "英文", @@ -557,7 +560,8 @@ "authentication_successful_getting_account_info": "認證成功, 獲取帳戶信息...", "warning_could_not_kill_existing_browser_processes": "警告: 無法殺死現有瀏覽器進程: {error}", "browser_failed_to_start": "瀏覽器啟動失敗: {error}", - "browser_failed": "瀏覽器啟動失敗: {error}" + "browser_failed": "瀏覽器啟動失敗: {error}", + "browser_failed_to_start_fallback": "瀏覽器啟動失敗: {error}" }, "chrome_profile": { "title": "Chrome配置檔案選擇", @@ -570,5 +574,50 @@ "profile_selected": "已選擇配置檔案:{profile}", "invalid_selection": "選擇無效。請重試", "warning_chrome_close": "警告:這將關閉所有正在執行的Chrome程序" + }, + "account_delete": { + "title": "Cursor Google 帳號刪除工具", + "warning": "警告:這將永久刪除您的 Cursor 帳號。此操作無法撤銷。", + "cancelled": "帳號刪除已取消。", + "starting_process": "開始帳號刪除過程...", + "google_button_not_found": "未找到 Google 登錄按鈕", + "logging_in": "正在使用 Google 登錄...", + "waiting_for_auth": "等待 Google 驗證...", + "login_successful": "登錄成功", + "unexpected_page": "登錄後頁面異常:{url}", + "trying_settings": "嘗試導航到設置頁面...", + "select_google_account": "請選擇您的 Google 帳號...", + "auth_timeout": "認證超時,繼續執行...", + "navigating_to_settings": "正在導航到設置頁面...", + "already_on_settings": "已在設置頁面", + "login_redirect_failed": "登錄重定向失敗,嘗試直接導航...", + "advanced_tab_not_found": "多次嘗試後未找到高級選項卡", + "advanced_tab_retry": "未找到高級選項卡,嘗試 {attempt}/{max_attempts}", + "advanced_tab_error": "查找高級選項卡時出錯:{error}", + "advanced_tab_clicked": "已點擊高級選項卡", + "direct_advanced_navigation": "嘗試直接導航到高級選項卡", + "delete_button_not_found": "多次嘗試後未找到刪除帳號按鈕", + "delete_button_retry": "未找到刪除按鈕,嘗試 {attempt}/{max_attempts}", + "delete_button_error": "查找刪除按鈕時出錯:{error}", + "delete_button_clicked": "已點擊刪除帳號按鈕", + "delete_input_not_found": "多次嘗試後未找到刪除確認輸入框", + "delete_input_retry": "未找到刪除輸入框,嘗試 {attempt}/{max_attempts}", + "delete_input_error": "查找刪除輸入框時出錯:{error}", + "delete_input_not_found_continuing": "未找到刪除確認輸入框,嘗試繼續執行...", + "typed_delete": "已在確認框中輸入\"Delete\"", + "confirm_button_not_found": "多次嘗試後未找到確認按鈕", + "confirm_button_retry": "未找到確認按鈕,嘗試 {attempt}/{max_attempts}", + "confirm_button_error": "查找確認按鈕時出錯:{error}", + "account_deleted": "帳號刪除成功!", + "error": "帳號刪除過程中出錯:{error}", + "success": "您的 Cursor 帳號已成功刪除!", + "failed": "帳號刪除過程失敗或已取消。", + "interrupted": "帳號刪除過程被用戶中斷。", + "unexpected_error": "意外錯誤:{error}", + "found_email": "找到郵箱:{email}", + "email_not_found": "未找到郵箱: {error}", + "found_danger_zone": "已找到危險區域部分", + "confirm_prompt": "您確定要繼續嗎?(y/N): ", + "typed_delete_js": "已使用 JavaScript 輸入\"Delete\"" } } \ No newline at end of file diff --git a/oauth_auth.py b/oauth_auth.py index 94e8695..38c350d 100644 --- a/oauth_auth.py +++ b/oauth_auth.py @@ -97,11 +97,11 @@ class OAuthHandler: def setup_browser(self): """Setup browser for OAuth flow using selected profile""" try: - print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.initializing_browser_setup') if self.translator else 'Initializing browser setup...'}{Style.RESET_ALL}") # Platform-specific initialization platform_name = platform.system().lower() - print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_platform', platform=platform_name) if self.translator else f'Detected platform: {platform_name}'}{Style.RESET_ALL}") # Get browser paths and user data directory user_data_dir = self._get_user_data_directory() @@ -117,9 +117,9 @@ class OAuthHandler: # Show warning about closing Chrome first print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('chrome_profile.warning_chrome_close') if self.translator else 'Warning: This will close all running Chrome processes'}{Style.RESET_ALL}") - choice = input(f"{Fore.YELLOW}Continue? (y/N): {Style.RESET_ALL}").lower() + choice = input(f"{Fore.YELLOW} {self.translator.get('menu.continue_prompt', choices='y/N')} {Style.RESET_ALL}").lower() if choice != 'y': - print(f"{Fore.YELLOW}{EMOJI['INFO']} Operation cancelled by user{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('menu.operation_cancelled_by_user') if self.translator else 'Operation cancelled by user'}{Style.RESET_ALL}") return False # Kill existing browser processes @@ -127,7 +127,7 @@ class OAuthHandler: # Let user select a profile if not self._select_profile(): - print(f"{Fore.YELLOW}{EMOJI['INFO']} Operation cancelled by user{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('menu.operation_cancelled_by_user') if self.translator else 'Operation cancelled by user'}{Style.RESET_ALL}") return False # Configure browser options @@ -138,7 +138,7 @@ class OAuthHandler: # Verify browser launched successfully if not self.browser: - raise Exception("Failed to initialize browser instance") + raise Exception(f"{self.translator.get('oauth.browser_failed_to_start') if self.translator else 'Failed to initialize browser instance'}") print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.browser_setup_completed') if self.translator else 'Browser setup completed successfully'}{Style.RESET_ALL}") return True