diff --git a/.env b/.env index 6720ee8..14684b0 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -version=1.10.02 -VERSION=1.10.02 +version=1.10.03 +VERSION=1.10.03 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acc397..324852d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## v1.10.03 +1. Add: Manual Registration | 增加手動註冊 +2. Only support your own Email | 只支持自己的Email 請勿使用Temp Email 註冊 註冊假賬號. +3. Fix: macOS 'bypass_version.py' get product_json_path from config_file | 修復 macOS 'bypass_version.py' 從 config_file 獲取 product_json_path +4. Fix: use cursor_path from config_file | 修復使用 cursor_path 從 config_file +5. Fix: Some Issues | 修復一些問題 + ## v1.10.02 1. Remove: Remove All Auto generating fake Google email accounts and OAuth access | 移除所有自動生成假 Google 電子郵件帳戶和 OAuth 訪問 2. Follow GitHub Terms of Service | 遵守 GitHub Terms of Service diff --git a/cursor_auth.py b/cursor_auth.py new file mode 100644 index 0000000..1a29cdd --- /dev/null +++ b/cursor_auth.py @@ -0,0 +1,159 @@ +import sqlite3 +import os +import sys +from colorama import Fore, Style, init +from config import get_config + +# Initialize colorama +init() + +# Define emoji and color constants +EMOJI = { + 'DB': '🗄️', + 'UPDATE': '🔄', + 'SUCCESS': '✅', + 'ERROR': '❌', + 'WARN': '⚠️', + 'INFO': 'ℹ️', + 'FILE': '📄', + 'KEY': '🔐' +} + +class CursorAuth: + def __init__(self, translator=None): + self.translator = translator + + # Get configuration + 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) + + # Get path based on operating system + 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) + + # Verify if the path exists + 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) + + # Check if the database file exists + if not os.path.exists(self.db_path): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path)}{Style.RESET_ALL}") + return + + # Check file permissions + if not os.access(self.db_path, os.R_OK | os.W_OK): + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error')}{Style.RESET_ALL}") + return + + try: + self.conn = sqlite3.connect(self.db_path) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}") + except sqlite3.Error as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e))}{Style.RESET_ALL}") + return + + def update_auth(self, email=None, access_token=None, refresh_token=None): + conn = None + try: + # Ensure the directory exists and set the correct permissions + db_dir = os.path.dirname(self.db_path) + if not os.path.exists(db_dir): + os.makedirs(db_dir, mode=0o755, exist_ok=True) + + # If the database file does not exist, create a new one + if not os.path.exists(self.db_path): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS ItemTable ( + key TEXT PRIMARY KEY, + value TEXT + ) + ''') + conn.commit() + if sys.platform != "win32": + os.chmod(self.db_path, 0o644) + conn.close() + + # Reconnect to the database + conn = sqlite3.connect(self.db_path) + print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}") + cursor = conn.cursor() + + # Add timeout and other optimization settings + conn.execute("PRAGMA busy_timeout = 5000") + conn.execute("PRAGMA journal_mode = WAL") + conn.execute("PRAGMA synchronous = NORMAL") + + # Set the key-value pairs to update + updates = [] + + updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) + + if email is not None: + updates.append(("cursorAuth/cachedEmail", email)) + if access_token is not None: + updates.append(("cursorAuth/accessToken", access_token)) + if refresh_token is not None: + updates.append(("cursorAuth/refreshToken", refresh_token)) + + + # Use transactions to ensure data integrity + cursor.execute("BEGIN TRANSACTION") + try: + for key, value in updates: + # Check if the key exists + cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,)) + if cursor.fetchone()[0] == 0: + cursor.execute(""" + INSERT INTO ItemTable (key, value) + VALUES (?, ?) + """, (key, value)) + else: + cursor.execute(""" + UPDATE ItemTable SET value = ? + WHERE key = ? + """, (value, key)) + print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair')} {key.split('/')[-1]}...{Style.RESET_ALL}") + + cursor.execute("COMMIT") + print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully')}{Style.RESET_ALL}") + return True + + except Exception as e: + cursor.execute("ROLLBACK") + raise e + + except sqlite3.Error as e: + print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}") + return False + except Exception as e: + print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}") + return False + finally: + if conn: + conn.close() + print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}") \ No newline at end of file diff --git a/cursor_register_manual.py b/cursor_register_manual.py new file mode 100644 index 0000000..e0f8b3e --- /dev/null +++ b/cursor_register_manual.py @@ -0,0 +1,271 @@ +import os +from colorama import Fore, Style, init +import time +import random +from cursor_auth import CursorAuth +from reset_machine_manual import MachineIDResetter +from get_user_token import get_token_from_cookie + +os.environ["PYTHONVERBOSE"] = "0" +os.environ["PYINSTALLER_VERBOSE"] = "0" + +# Initialize colorama +init() + +# Define emoji constants +EMOJI = { + 'START': '🚀', + 'FORM': '📝', + 'VERIFY': '🔄', + 'PASSWORD': '🔑', + 'CODE': '📱', + 'DONE': '✨', + 'ERROR': '❌', + 'WAIT': '⏳', + 'SUCCESS': '✅', + 'MAIL': '📧', + 'KEY': '🔐', + 'UPDATE': '🔄', + 'INFO': 'ℹ️' +} + +class CursorRegistration: + def __init__(self, translator=None): + self.translator = translator + # Set to display mode + os.environ['BROWSER_HEADLESS'] = 'False' + self.browser = None + self.controller = None + self.sign_up_url = "https://authenticator.cursor.sh/sign-up" + self.settings_url = "https://www.cursor.com/settings" + self.email_address = None + self.signup_tab = None + self.email_tab = None + + # Generate account information + self.password = self._generate_password() + # Generate first name and last name separately + first_name = random.choice([ + "James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas", + "Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia", + "Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander" + ]) + self.last_name = random.choice([ + "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", + "Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee", + "Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young" + ]) + + # Modify first letter of first name + new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + self.first_name = new_first_letter + first_name[1:] + + print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}") + + def _generate_password(self, length=12): + """Generate Random Password""" + chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" + return ''.join(random.choices(chars, k=length)) + + def setup_email(self): + """Setup Email""" + try: + print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}") + self.email_address = input().strip() + + if '@' not in self.email_address: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}") + return False + + print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}" + "\n" + f"{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}") + return False + + def get_verification_code(self): + """Manually Get Verification Code""" + try: + print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}") + code = input().strip() + + if not code.isdigit() or len(code) != 6: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}") + return None + + return code + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}") + return None + + def register_cursor(self): + """Register Cursor""" + browser_tab = None + try: + print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}") + + # Use new_signup.py directly for registration + from new_signup import main as new_signup_main + + # Execute new registration process, passing translator + result, browser_tab = new_signup_main( + email=self.email_address, + password=self.password, + first_name=self.first_name, + last_name=self.last_name, + email_tab=None, # No email tab needed + controller=self, # Pass self instead of self.controller + translator=self.translator + ) + + if result: + # Use the returned browser instance to get account information + self.signup_tab = browser_tab # Save browser instance + success = self._get_account_info() + + # Close browser after getting information + if browser_tab: + try: + browser_tab.quit() + except: + pass + + return success + + return False + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}") + return False + finally: + # Ensure browser is closed in any case + if browser_tab: + try: + browser_tab.quit() + except: + pass + + def _get_account_info(self): + """Get Account Information and Token""" + try: + self.signup_tab.get(self.settings_url) + time.sleep(2) + + 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.signup_tab.ele(usage_selector) + total_usage = "未知" + if usage_ele: + total_usage = usage_ele.text.split("/")[-1].strip() + + print(f"Total Usage: {total_usage}\n") + print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}") + max_attempts = 30 + retry_interval = 2 + attempts = 0 + + while attempts < max_attempts: + try: + cookies = self.signup_tab.cookies() + for cookie in cookies: + if cookie.get("name") == "WorkosCursorSessionToken": + token = get_token_from_cookie(cookie["value"], self.translator) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}") + self._save_account_info(token, total_usage) + return True + + attempts += 1 + if attempts < max_attempts: + print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}") + time.sleep(retry_interval) + else: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}") + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}") + attempts += 1 + if attempts < max_attempts: + print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}") + time.sleep(retry_interval) + + return False + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}") + return False + + def _save_account_info(self, token, total_usage): + """Save Account Information to File""" + try: + # Update authentication information first + print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}") + if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token): + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}") + else: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}") + + # Reset machine ID + print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}") + resetter = MachineIDResetter(self.translator) # Create instance with translator + if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly + raise Exception("Failed to reset machine ID") + + # Save account information to file + with open('cursor_accounts.txt', 'a', encoding='utf-8') as f: + f.write(f"\n{'='*50}\n") + f.write(f"Email: {self.email_address}\n") + f.write(f"Password: {self.password}\n") + f.write(f"Token: {token}\n") + f.write(f"Usage Limit: {total_usage}\n") + f.write(f"{'='*50}\n") + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}") + return True + + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}") + return False + + def start(self): + """Start Registration Process""" + try: + if self.setup_email(): + if self.register_cursor(): + print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}") + return True + return False + finally: + # Close email tab + if hasattr(self, 'temp_email'): + try: + self.temp_email.close() + except: + pass + + def update_cursor_auth(self, email=None, access_token=None, refresh_token=None): + """Convenient function to update Cursor authentication information""" + auth_manager = CursorAuth(translator=self.translator) + return auth_manager.update_auth(email, access_token, refresh_token) + +def main(translator=None): + """Main function to be called from main.py""" + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + + registration = CursorRegistration(translator) + registration.start() + + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...") + +if __name__ == "__main__": + from main import translator as main_translator + main(main_translator) \ No newline at end of file diff --git a/main.py b/main.py index 9428b67..e42698f 100644 --- a/main.py +++ b/main.py @@ -284,15 +284,16 @@ def print_menu(): 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['ERROR']} {translator.get('menu.quit')}", - 3: f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}", - 4: f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}", - 5: f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}", - 6: f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}", - 7: f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}", - 8: f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.bypass_version_check', fallback='Bypass Cursor Version Check')}", - 9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.check_user_authorized', fallback='Check User Authorized')}", - 10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.bypass_token_limit', fallback='Bypass Token Limit')}" + 2: f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}", + 3: f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}", + 4: f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}", + 5: f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}", + 6: f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}", + 7: f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}", + 8: f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}", + 9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.bypass_version_check', fallback='Bypass Cursor Version Check')}", + 10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.check_user_authorized', fallback='Check User Authorized')}", + 11: f"{Fore.GREEN}11{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.bypass_token_limit', fallback='Bypass Token Limit')}" } # Automatically calculate the number of menu items in the left and right columns @@ -565,7 +566,7 @@ def main(): while True: try: - choice_num = 10 + choice_num = 11 choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}") match choice: @@ -576,41 +577,45 @@ def main(): case "1": import reset_machine_manual reset_machine_manual.run(translator) - print_menu() + print_menu() case "2": + import cursor_register_manual + cursor_register_manual.main(translator) + print_menu() + case "3": import quit_cursor quit_cursor.quit_cursor(translator) print_menu() - case "3": + case "4": if select_language(): print_menu() continue - case "4": + case "5": import disable_auto_update disable_auto_update.run(translator) print_menu() - case "5": + case "6": import totally_reset_cursor totally_reset_cursor.run(translator) # print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}") print_menu() - case "6": + case "7": import logo print(logo.CURSOR_CONTRIBUTORS) print_menu() - case "7": + case "8": from config import print_config print_config(get_config(), translator) print_menu() - case "8": + case "9": import bypass_version bypass_version.main(translator) print_menu() - case "9": + case "10": import check_user_authorized check_user_authorized.main(translator) print_menu() - case "10": + case "11": import bypass_token_limit bypass_token_limit.run(translator) print_menu() diff --git a/new_signup.py b/new_signup.py new file mode 100644 index 0000000..e24f487 --- /dev/null +++ b/new_signup.py @@ -0,0 +1,692 @@ +from DrissionPage import ChromiumOptions, ChromiumPage +import time +import os +import signal +import random +from colorama import Fore, Style +import configparser +from pathlib import Path +import sys +from config import get_config +from utils import get_default_browser_path as utils_get_default_browser_path + +# Add global variable at the beginning of the file +_translator = None + +# Add global variable to track our Chrome processes +_chrome_process_ids = [] + +def cleanup_chrome_processes(translator=None): + """Clean only Chrome processes launched by this script""" + global _chrome_process_ids + + if not _chrome_process_ids: + print("\nNo Chrome processes to clean...") + return + + print("\nCleaning Chrome processes launched by this script...") + try: + if os.name == 'nt': + for pid in _chrome_process_ids: + try: + os.system(f'taskkill /F /PID {pid} /T 2>nul') + except: + pass + else: + for pid in _chrome_process_ids: + try: + os.kill(pid, signal.SIGTERM) + except: + pass + _chrome_process_ids = [] # Reset the list after cleanup + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.cleanup_error', error=str(e))}{Style.RESET_ALL}") + else: + print(f"清理进程时出错: {e}") + +def signal_handler(signum, frame): + """Handle Ctrl+C signal""" + global _translator + if _translator: + print(f"{Fore.CYAN}{_translator.get('register.exit_signal')}{Style.RESET_ALL}") + else: + print("\n接收到退出信号,正在关闭...") + cleanup_chrome_processes(_translator) + os._exit(0) + +def simulate_human_input(page, url, config, translator=None): + """Visit URL""" + if translator: + print(f"{Fore.CYAN}🚀 {translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}") + + # First visit blank page + page.get('about:blank') + time.sleep(get_random_wait_time(config, 'page_load_wait')) + + # Visit target page + page.get(url) + time.sleep(get_random_wait_time(config, 'page_load_wait')) + +def fill_signup_form(page, first_name, last_name, email, config, translator=None): + """Fill signup form""" + try: + if translator: + print(f"{Fore.CYAN}📧 {translator.get('register.filling_form')}{Style.RESET_ALL}") + else: + print("\n正在填写注册表单...") + + # Fill first name + first_name_input = page.ele("@name=first_name") + if first_name_input: + first_name_input.input(first_name) + time.sleep(get_random_wait_time(config, 'input_wait')) + + # Fill last name + last_name_input = page.ele("@name=last_name") + if last_name_input: + last_name_input.input(last_name) + time.sleep(get_random_wait_time(config, 'input_wait')) + + # Fill email + email_input = page.ele("@name=email") + if email_input: + email_input.input(email) + time.sleep(get_random_wait_time(config, 'input_wait')) + + # Click submit button + submit_button = page.ele("@type=submit") + if submit_button: + submit_button.click() + time.sleep(get_random_wait_time(config, 'submit_wait')) + + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.form_success')}{Style.RESET_ALL}") + else: + print("Form filled successfully") + return True + + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.form_error', error=str(e))}{Style.RESET_ALL}") + else: + print(f"Error filling form: {e}") + return False + +def get_user_documents_path(): + """Get user Documents folder path""" + if sys.platform == "win32": + return os.path.join(os.path.expanduser("~"), "Documents") + elif sys.platform == "darwin": + return os.path.join(os.path.expanduser("~"), "Documents") + else: # Linux + # Get actual user's home directory + sudo_user = os.environ.get('SUDO_USER') + if sudo_user: + return os.path.join("/home", sudo_user, "Documents") + return os.path.join(os.path.expanduser("~"), "Documents") + +def get_random_wait_time(config, timing_type='page_load_wait'): + """ + Get random wait time from config + Args: + config: ConfigParser object + timing_type: Type of timing to get (page_load_wait, input_wait, submit_wait) + Returns: + float: Random wait time or fixed time + """ + try: + if not config.has_section('Timing'): + return random.uniform(0.1, 0.8) # Default value + + if timing_type == 'random': + min_time = float(config.get('Timing', 'min_random_time', fallback='0.1')) + max_time = float(config.get('Timing', 'max_random_time', fallback='0.8')) + return random.uniform(min_time, max_time) + + time_value = config.get('Timing', timing_type, fallback='0.1-0.8') + + # Check if it's a fixed time value + if '-' not in time_value and ',' not in time_value: + return float(time_value) # Return fixed time + + # Process range time + min_time, max_time = map(float, time_value.split('-' if '-' in time_value else ',')) + return random.uniform(min_time, max_time) + except: + return random.uniform(0.1, 0.8) # Return default value when error + +def setup_driver(translator=None): + """Setup browser driver""" + global _chrome_process_ids + + try: + # Get config + config = get_config(translator) + + # Get browser type and path + browser_type = config.get('Browser', 'default_browser', fallback='chrome') + browser_path = config.get('Browser', f'{browser_type}_path', fallback=utils_get_default_browser_path(browser_type)) + + if not browser_path or not os.path.exists(browser_path): + if translator: + print(f"{Fore.YELLOW}⚠️ {browser_type} {translator.get('register.browser_path_invalid')}{Style.RESET_ALL}") + browser_path = utils_get_default_browser_path(browser_type) + + # For backward compatibility, also check Chrome path + if browser_type == 'chrome': + chrome_path = config.get('Chrome', 'chromepath', fallback=None) + if chrome_path and os.path.exists(chrome_path): + browser_path = chrome_path + + # Set browser options + co = ChromiumOptions() + + # Set browser path + co.set_browser_path(browser_path) + + # Use incognito mode + co.set_argument("--incognito") + + if sys.platform == "linux": + # Set Linux specific options + co.set_argument("--no-sandbox") + + # Set random port + co.auto_port() + + # Use headless mode (must be set to False, simulate human operation) + co.headless(False) + + # Log browser info + if translator: + print(f"{Fore.CYAN}🌐 {translator.get('register.using_browser', browser=browser_type, path=browser_path)}{Style.RESET_ALL}") + + try: + # Load extension + extension_path = os.path.join(os.getcwd(), "turnstilePatch") + if os.path.exists(extension_path): + co.set_argument("--allow-extensions-in-incognito") + co.add_extension(extension_path) + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.extension_load_error', error=str(e))}{Style.RESET_ALL}") + else: + print(f"Error loading extension: {e}") + + if translator: + print(f"{Fore.CYAN}🚀 {translator.get('register.starting_browser')}{Style.RESET_ALL}") + else: + print("Starting browser...") + + # Record Chrome processes before launching + before_pids = [] + try: + import psutil + browser_process_names = { + 'chrome': ['chrome', 'chromium'], + 'edge': ['msedge', 'edge'], + 'firefox': ['firefox'], + 'brave': ['brave', 'brave-browser'] + } + process_names = browser_process_names.get(browser_type, ['chrome']) + before_pids = [p.pid for p in psutil.process_iter() if any(name in p.name().lower() for name in process_names)] + except: + pass + + # Launch browser + page = ChromiumPage(co) + + # Wait a moment for browser to fully launch + time.sleep(1) + + # Record browser processes after launching and find new ones + try: + import psutil + process_names = browser_process_names.get(browser_type, ['chrome']) + after_pids = [p.pid for p in psutil.process_iter() if any(name in p.name().lower() for name in process_names)] + # Find new browser processes + new_pids = [pid for pid in after_pids if pid not in before_pids] + _chrome_process_ids.extend(new_pids) + + if _chrome_process_ids: + print(f"{translator.get('register.tracking_processes', count=len(_chrome_process_ids), browser=browser_type)}") + else: + print(f"{Fore.YELLOW}Warning: {translator.get('register.no_new_processes_detected', browser=browser_type)}{Style.RESET_ALL}") + except Exception as e: + print(f"{translator.get('register.could_not_track_processes', browser=browser_type, error=str(e))}") + + return config, page + + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.browser_setup_error', error=str(e))}{Style.RESET_ALL}") + else: + print(f"Error setting up browser: {e}") + raise + +def handle_turnstile(page, config, translator=None): + """Handle Turnstile verification""" + try: + if translator: + print(f"{Fore.CYAN}🔄 {translator.get('register.handling_turnstile')}{Style.RESET_ALL}") + else: + print("\nHandling Turnstile verification...") + + # from config + turnstile_time = float(config.get('Turnstile', 'handle_turnstile_time', fallback='2')) + random_time_str = config.get('Turnstile', 'handle_turnstile_random_time', fallback='1-3') + + # Parse random time range + try: + min_time, max_time = map(float, random_time_str.split('-')) + except: + min_time, max_time = 1, 3 # Default value + + max_retries = 2 + retry_count = 0 + + while retry_count < max_retries: + retry_count += 1 + if translator: + print(f"{Fore.CYAN}🔄 {translator.get('register.retry_verification', attempt=retry_count)}{Style.RESET_ALL}") + else: + print(f"Attempt {retry_count} of verification...") + + try: + # Try to reset turnstile + page.run_js("try { turnstile.reset() } catch(e) { }") + time.sleep(turnstile_time) # from config + + # Locate verification box element + challenge_check = ( + page.ele("@id=cf-turnstile", timeout=2) + .child() + .shadow_root.ele("tag:iframe") + .ele("tag:body") + .sr("tag:input") + ) + + if challenge_check: + if translator: + print(f"{Fore.CYAN}🔄 {translator.get('register.detect_turnstile')}{Style.RESET_ALL}") + else: + print("Detected verification box...") + + # from config + time.sleep(random.uniform(min_time, max_time)) + challenge_check.click() + time.sleep(turnstile_time) # from config + + # check verification result + if check_verification_success(page, translator): + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + else: + print("Verification successful!") + return True + + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}") + else: + print(f"Verification attempt failed: {e}") + + # Check if verification has been successful + if check_verification_success(page, translator): + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + else: + print("Verification successful!") + return True + + time.sleep(random.uniform(min_time, max_time)) + + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}") + else: + print("Exceeded maximum retry attempts") + return False + + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}") + else: + print(f"Error in verification process: {e}") + return False + +def check_verification_success(page, translator=None): + """Check if verification is successful""" + try: + # Check if there is a subsequent form element, indicating verification has passed + if (page.ele("@name=password", timeout=0.5) or + page.ele("@name=email", timeout=0.5) or + page.ele("@data-index=0", timeout=0.5) or + page.ele("Account Settings", timeout=0.5)): + return True + + # Check if there is an error message + error_messages = [ + 'xpath://div[contains(text(), "Can\'t verify the user is human")]', + 'xpath://div[contains(text(), "Error: 600010")]', + 'xpath://div[contains(text(), "Please try again")]' + ] + + for error_xpath in error_messages: + if page.ele(error_xpath): + return False + + return False + except: + return False + +def generate_password(length=12): + """Generate random password""" + chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" + return ''.join(random.choices(chars, k=length)) + +def fill_password(page, password: str, config, translator=None): + """ + Fill password form + """ + try: + print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else 'Setting password'}{Style.RESET_ALL}") + + # Fill password + 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) + + # Click submit button + submit_button = page.ele("@type=submit") + if submit_button: + submit_button.click() + time.sleep(get_random_wait_time(config, 'submit_wait')) + + print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else 'Password submitted'}{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'Error setting password: {str(e)}'}{Style.RESET_ALL}") + + return False + +def handle_verification_code(browser_tab, email_tab, controller, config, translator=None): + """Handle verification code""" + try: + if translator: + print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") + + # Check if using manual input verification code + if hasattr(controller, 'get_verification_code') and email_tab is None: # Manual mode + verification_code = controller.get_verification_code() + if verification_code: + # Fill verification code in registration page + for i, digit in enumerate(verification_code): + browser_tab.ele(f"@data-index={i}").input(digit) + time.sleep(get_random_wait_time(config, 'verification_code_input')) + + print(f"{translator.get('register.verification_success')}") + time.sleep(get_random_wait_time(config, 'verification_success_wait')) + + # Handle last Turnstile verification + if handle_turnstile(browser_tab, config, translator): + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'verification_retry_wait')) + + # Visit settings page + 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 + + return False, None + + # Automatic verification code logic + elif email_tab: + 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')) + + # Use existing email_tab to refresh email + email_tab.refresh_inbox() + time.sleep(get_random_wait_time(config, 'email_refresh_wait')) + + # Check if there is a verification code email + if email_tab.check_for_cursor_email(): + verification_code = email_tab.get_verification_code() + if verification_code: + # Fill verification code in registration page + for i, digit in enumerate(verification_code): + browser_tab.ele(f"@data-index={i}").input(digit) + time.sleep(get_random_wait_time(config, 'verification_code_input')) + + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'verification_success_wait')) + + # Handle last Turnstile verification + if handle_turnstile(browser_tab, config, translator): + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'verification_retry_wait')) + + # Visit settings page + if translator: + 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 + + else: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}") + else: + print("最后一次验证失败") + return False, None + + # Get verification code, set timeout + verification_code = None + max_attempts = 20 + retry_interval = get_random_wait_time(config, 'retry_interval') # Use get_random_wait_time + start_time = time.time() + timeout = float(config.get('Timing', 'max_timeout', fallback='160')) # This can be kept unchanged because it is a fixed value + + if translator: + print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}") + + for attempt in range(max_attempts): + # Check if timeout + if time.time() - start_time > timeout: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_timeout')}{Style.RESET_ALL}") + break + + verification_code = controller.get_verification_code() + if verification_code: + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + break + + remaining_time = int(timeout - (time.time() - start_time)) + if translator: + print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}") + + # Refresh email + email_tab.refresh_inbox() + time.sleep(retry_interval) # Use get_random_wait_time + + if verification_code: + # Fill verification code in registration page + for i, digit in enumerate(verification_code): + browser_tab.ele(f"@data-index={i}").input(digit) + time.sleep(get_random_wait_time(config, 'verification_code_input')) + + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'verification_success_wait')) + + # Handle last Turnstile verification + if handle_turnstile(browser_tab, config, translator): + if translator: + print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'verification_retry_wait')) + + # Visit settings page + if translator: + 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 success directly, let cursor_register.py handle account information acquisition + return True, browser_tab + + else: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}") + return False, None + + return False, None + + except Exception as e: + if translator: + print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}") + return False, None + +def handle_sign_in(browser_tab, email, password, translator=None): + """Handle login process""" + try: + # Check if on login page + sign_in_header = browser_tab.ele('xpath://h1[contains(text(), "Sign in")]') + if not sign_in_header: + return True # If not on login page, it means login is successful + + print(f"{Fore.CYAN}检测到登录页面,开始登录...{Style.RESET_ALL}") + + # Fill email + email_input = browser_tab.ele('@name=email') + if email_input: + email_input.input(email) + time.sleep(1) + + # Click Continue + continue_button = browser_tab.ele('xpath://button[contains(@class, "BrandedButton") and text()="Continue"]') + if continue_button: + continue_button.click() + time.sleep(2) + + # Handle Turnstile verification + if handle_turnstile(browser_tab, translator): + # Fill password + password_input = browser_tab.ele('@name=password') + if password_input: + password_input.input(password) + time.sleep(1) + + # Click Sign in + sign_in_button = browser_tab.ele('xpath://button[@name="intent" and @value="password"]') + if sign_in_button: + sign_in_button.click() + time.sleep(2) + + # Handle last Turnstile verification + if handle_turnstile(browser_tab, translator): + print(f"{Fore.GREEN}Login successful!{Style.RESET_ALL}") + time.sleep(3) + return True + + print(f"{Fore.RED}Login failed{Style.RESET_ALL}") + return False + + except Exception as e: + print(f"{Fore.RED}Login process error: {str(e)}{Style.RESET_ALL}") + return False + +def main(email=None, password=None, first_name=None, last_name=None, email_tab=None, controller=None, translator=None): + """Main function, can receive account information, email tab, and translator""" + global _translator + global _chrome_process_ids + _translator = translator # Save to global variable + _chrome_process_ids = [] # Reset the process IDs list + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + page = None + success = False + try: + config, page = setup_driver(translator) + if translator: + print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}") + + # Visit registration page + url = "https://authenticator.cursor.sh/sign-up" + + # Visit page + simulate_human_input(page, url, config, translator) + if translator: + print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}") + time.sleep(get_random_wait_time(config, 'page_load_wait')) + + # If account information is not provided, generate random information + if not all([email, password, first_name, last_name]): + first_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize() + last_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize() + email = f"{first_name.lower()}{random.randint(100,999)}@example.com" + password = generate_password() + + # Save account information + with open('test_accounts.txt', 'a', encoding='utf-8') as f: + f.write(f"\n{'='*50}\n") + f.write(f"Email: {email}\n") + f.write(f"Password: {password}\n") + f.write(f"{'='*50}\n") + + # Fill form + 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}") + + # Handle first Turnstile verification + if handle_turnstile(page, config, translator): + if translator: + print(f"\n{Fore.GREEN}✅ {translator.get('register.first_verification_passed')}{Style.RESET_ALL}") + + # Fill password + if fill_password(page, password, config, translator): + if translator: + print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}") + + # Handle second Turnstile verification + 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, config, translator): + success = True + return True, page + else: + print(f"\n{Fore.RED}❌ {translator.get('register.verification_code_processing_failed') if translator else 'Verification code processing failed'}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else 'Second verification failed'}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else 'Second verification failed'}{Style.RESET_ALL}") + else: + print(f"\n{Fore.RED}❌ {translator.get('register.first_verification_failed') if translator else 'First verification failed'}{Style.RESET_ALL}") + + return False, None + + except Exception as e: + print(f"发生错误: {e}") + return False, None + finally: + if page and not success: # Only clean up when failed + try: + page.quit() + except: + pass + cleanup_chrome_processes(translator) + +if __name__ == "__main__": + main() # Run without parameters, use randomly generated information \ No newline at end of file