├── turnstilePatch ├── readme.txt ├── script.js └── manifest.json ├── requirements.txt ├── screen ├── afdian-[未认证]阿臻.jpg ├── 截屏2025-01-04 09.44.48.png ├── qrcode_for_gh_c985615b5f2b_258.jpg ├── 28613e3f3f23a935b66a7ba31ff4e3f.jpg ├── c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png └── mm_facetoface_collect_qrcode_1738583247120.png ├── .gitignore ├── .env.example ├── logo.py ├── .github └── workflows │ ├── remove-old-artifacts.yml │ ├── temp-build.yml │ └── build.yml ├── README.md ├── CursorKeepAlive.spec ├── logger.py ├── README.EN.md ├── browser_utils.py ├── exit_cursor.py ├── cursor_auth_manager.py ├── test └── get_veri_code_test.py ├── reset_machine.py ├── config.py ├── get_email_code.py ├── patch_cursor_get_machine_id.py └── cursor_pro_keep_alive.py /turnstilePatch/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | DrissionPage==4.1.0.9 2 | colorama==0.4.6 3 | python-dotenv==1.0.0 4 | pyinstaller -------------------------------------------------------------------------------- /screen/afdian-[未认证]阿臻.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/afdian-[未认证]阿臻.jpg -------------------------------------------------------------------------------- /screen/截屏2025-01-04 09.44.48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/截屏2025-01-04 09.44.48.png -------------------------------------------------------------------------------- /screen/qrcode_for_gh_c985615b5f2b_258.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/qrcode_for_gh_c985615b5f2b_258.jpg -------------------------------------------------------------------------------- /screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg -------------------------------------------------------------------------------- /screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png -------------------------------------------------------------------------------- /screen/mm_facetoface_collect_qrcode_1738583247120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udoless/cursor-auto-free/main/screen/mm_facetoface_collect_qrcode_1738583247120.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # PyInstaller 2 | build/ 3 | dist/ 4 | *.spec 5 | !CursorKeepAlive.mac.spec 6 | !CursorKeepAlive.win.spec 7 | 8 | # Python 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # Logs 14 | *.log 15 | 16 | # IDE 17 | .vscode/ 18 | .idea/ 19 | 20 | # Mac 21 | .DS_Store 22 | 23 | venv/ 24 | 25 | node_modules/ 26 | 27 | .env 28 | 29 | screenshots/ -------------------------------------------------------------------------------- /turnstilePatch/script.js: -------------------------------------------------------------------------------- 1 | function getRandomInt(min, max) { 2 | return Math.floor(Math.random() * (max - min + 1)) + min; 3 | } 4 | 5 | // old method wouldn't work on 4k screens 6 | 7 | let screenX = getRandomInt(800, 1200); 8 | let screenY = getRandomInt(400, 600); 9 | 10 | Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); 11 | 12 | Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # 你的CF路由填写的域名 2 | DOMAIN=xxxxx.me 3 | # 邮件服务地址 4 | # 注册临时邮件服务 https://tempmail.plus 5 | TEMP_MAIL=xxxxxx 6 | # 设置的PIN码 7 | TEMP_MAIL_EPIN=xxxxxx 8 | # 使用的后缀 9 | TEMP_MAIL_EXT=@mailto.plus 10 | BROWSER_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36 11 | 12 | # 代理 13 | # BROWSER_PROXY='http://127.0.0.1:2080' 14 | 15 | # 无头模式 默认开启 16 | # BROWSER_HEADLESS='True' 17 | -------------------------------------------------------------------------------- /turnstilePatch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Turnstile Patcher", 4 | "version": "2.1", 5 | "content_scripts": [ 6 | { 7 | "js": [ 8 | "./script.js" 9 | ], 10 | "matches": [ 11 | "" 12 | ], 13 | "run_at": "document_start", 14 | "all_frames": true, 15 | "world": "MAIN" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /logo.py: -------------------------------------------------------------------------------- 1 | CURSOR_LOGO = """ 2 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ 3 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ 4 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ 5 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ 6 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ 7 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ 8 | """ 9 | 10 | 11 | def print_logo(): 12 | print(CURSOR_LOGO) 13 | 14 | 15 | if __name__ == "__main__": 16 | print_logo() 17 | -------------------------------------------------------------------------------- /.github/workflows/remove-old-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Remove old artifacts 2 | 3 | on: 4 | schedule: 5 | # Every day at 1am 6 | - cron: '0 1 * * *' 7 | # 手动 8 | workflow_dispatch: 9 | 10 | 11 | jobs: 12 | remove-old-artifacts: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 10 15 | 16 | steps: 17 | - name: Remove old artifacts 18 | uses: c-hive/gha-remove-artifacts@v1 19 | with: 20 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 21 | age: '5 days' # ' ', e.g. 5 days, 2 years, 90 seconds, parsed by Moment.js 22 | # Optional inputs 23 | # skip-tags: true 24 | # skip-recent: 5 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cursor Pro 自动化工具使用说明 2 | 3 | 4 | [English doc](./README.EN.md) 5 | 6 | 7 | 8 | ## 在线文档 9 | [cursor-auto-free-doc.vercel.app](https://cursor-auto-free-doc.vercel.app) 10 | 11 | 12 | ## 许可证声明 13 | 本项目采用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 许可证。 14 | 这意味着您可以: 15 | - 分享 — 在任何媒介以任何形式复制、发行本作品 16 | 但必须遵守以下条件: 17 | - 非商业性使用 — 您不得将本作品用于商业目的 18 | 19 | ## 声明 20 | - 本项目仅供学习交流使用,请勿用于商业用途。 21 | - 本项目不承担任何法律责任,使用本项目造成的任何后果,由使用者自行承担。 22 | 23 | 24 | 25 | ## 你让开源没有爱啊!!!!(非法商用黑名单) 26 | | 仓库 | 售卖方式 | 27 | | ----- | ----- | 28 | | [gitee海豚](https://gitee.com/ydd_energy/dolphin_-cursor) | survivor_bias_ (微信) | 29 | 30 | 31 | ## 感谢 linuxDo 这个开源社区(一个真正的技术社区) 32 | https://linux.do/ 33 | 34 | 35 | 36 | ## 请我喝杯茶 37 | 38 | 39 | ## 关注公众号,随时获取仓库更新动态 40 | 41 | ![image](./screen/qrcode_for_gh_c985615b5f2b_258.jpg) 42 | 43 | 44 | -------------------------------------------------------------------------------- /CursorKeepAlive.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | import os 3 | 4 | a = Analysis( 5 | ['cursor_pro_keep_alive.py'], 6 | pathex=[], 7 | binaries=[], 8 | datas=[ 9 | ('turnstilePatch', 'turnstilePatch'), 10 | ('cursor_auth_manager.py', '.'), 11 | ], 12 | hiddenimports=[ 13 | 'cursor_auth_manager' 14 | ], 15 | hookspath=[], 16 | hooksconfig={}, 17 | runtime_hooks=[], 18 | excludes=[], 19 | noarchive=False, 20 | ) 21 | 22 | pyz = PYZ(a.pure) 23 | 24 | target_arch = os.environ.get('TARGET_ARCH', None) 25 | 26 | exe = EXE( 27 | pyz, 28 | a.scripts, 29 | a.binaries, 30 | a.datas, 31 | [], 32 | name='CursorPro', 33 | debug=False, 34 | bootloader_ignore_signals=False, 35 | strip=False, 36 | upx=True, 37 | upx_exclude=[], 38 | runtime_tmpdir=None, 39 | console=True, 40 | disable_windowed_traceback=False, 41 | argv_emulation=True, # 对非Mac平台无影响 42 | target_arch=target_arch, # 仅在需要时通过环境变量指定 43 | codesign_identity=None, 44 | entitlements_file=None, 45 | icon=None 46 | ) -------------------------------------------------------------------------------- /logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from datetime import datetime 4 | 5 | # Configure logging 6 | log_dir = "logs" 7 | if not os.path.exists(log_dir): 8 | os.makedirs(log_dir) 9 | 10 | logging.basicConfig( 11 | filename=os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log"), 12 | level=logging.DEBUG, 13 | format="%(asctime)s - %(levelname)s - %(message)s", 14 | encoding="utf-8", 15 | ) 16 | 17 | 18 | # 创建控制台处理器 19 | console_handler = logging.StreamHandler() 20 | console_handler.setLevel(logging.INFO) 21 | console_handler.setFormatter(logging.Formatter("%(message)s")) 22 | 23 | # 将控制台处理器添加到日志记录器 24 | logging.getLogger().addHandler(console_handler) 25 | 26 | # 打印日志目录所在路径 27 | logging.info(f"Logger initialized, log directory: {os.path.abspath(log_dir)}") 28 | 29 | 30 | def main_task(): 31 | """ 32 | Main task execution function. Simulates a workflow and handles errors. 33 | """ 34 | try: 35 | logging.info("Starting the main task...") 36 | 37 | # Simulated task and error condition 38 | if some_condition(): 39 | raise ValueError("Simulated error occurred.") 40 | 41 | logging.info("Main task completed successfully.") 42 | 43 | except ValueError as ve: 44 | logging.error(f"ValueError occurred: {ve}", exc_info=True) 45 | except Exception as e: 46 | logging.error(f"Unexpected error occurred: {e}", exc_info=True) 47 | finally: 48 | logging.info("Task execution finished.") 49 | 50 | 51 | def some_condition(): 52 | """ 53 | Simulates an error condition. Returns True to trigger an error. 54 | Replace this logic with actual task conditions. 55 | """ 56 | return True 57 | 58 | 59 | if __name__ == "__main__": 60 | # Application workflow 61 | logging.info("Application started.") 62 | main_task() 63 | logging.info("Application exited.") 64 | -------------------------------------------------------------------------------- /README.EN.md: -------------------------------------------------------------------------------- 1 | # Cursor Pro Automation Tool User Guide 2 | 3 | README also available in: [中文](./README.md) 4 | 5 | ## Online Documentation 6 | [cursor-auto-free-doc.vercel.app](https://cursor-auto-free-doc.vercel.app) 7 | 8 | ## Note 9 | Recently, some users have sold this software on platforms like Xianyu. Please avoid such practices—there's no need to earn money this way. 10 | 11 | ## Sponsor for More Updates 12 | ![image](./screen/afdian-[未认证]阿臻.jpg) 13 | 14 | ## License 15 | This project is licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). 16 | This means you may: 17 | - **Share** — Copy and redistribute the material in any medium or format. 18 | But you must comply with the following conditions: 19 | - **Non-commercial** — You may not use the material for commercial purposes. 20 | 21 | ## Features 22 | Automated account registration and token refreshing to free your hands. 23 | 24 | ## Important Notes 25 | 1. **Ensure you have Chrome installed. If not, [download here](https://www.google.com/intl/en_pk/chrome/).** 26 | 2. **You must log into your account, regardless of its validity. Logged-in is mandatory.** 27 | 3. **A stable internet connection is required, preferably via an overseas node. Do not enable global proxy.** 28 | 29 | ## Configuration Instructions 30 | Please refer to our [online documentation](https://cursor-auto-free-doc.vercel.app) for detailed configuration instructions. 31 | 32 | ## Download 33 | [https://github.com/chengazhen/cursor-auto-free/releases](https://github.com/chengazhen/cursor-auto-free/releases) 34 | 35 | ## Update Log 36 | - **2025-01-09**: Added logs and auto-build feature. 37 | - **2025-01-10**: Switched to Cloudflare domain email. 38 | - **2025-01-11**: Added headless mode and proxy configuration through .env file. 39 | - **2025-01-20**: Added IMAP to replace tempmail.plus. 40 | 41 | Inspired by [gpt-cursor-auto](https://github.com/hmhm2022/gpt-cursor-auto); optimized verification and email auto-registration logic; solved the issue of not being able to receive email verification codes. 42 | -------------------------------------------------------------------------------- /browser_utils.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | import sys 3 | import os 4 | import logging 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | 10 | class BrowserManager: 11 | def __init__(self): 12 | self.browser = None 13 | 14 | def init_browser(self, user_agent=None): 15 | """初始化浏览器""" 16 | co = self._get_browser_options(user_agent) 17 | self.browser = Chromium(co) 18 | return self.browser 19 | 20 | def _get_browser_options(self, user_agent=None): 21 | """获取浏览器配置""" 22 | co = ChromiumOptions() 23 | try: 24 | extension_path = self._get_extension_path() 25 | co.add_extension(extension_path) 26 | except FileNotFoundError as e: 27 | logging.warning(f"警告: {e}") 28 | 29 | co.set_pref("credentials_enable_service", False) 30 | co.set_argument("--hide-crash-restore-bubble") 31 | proxy = os.getenv("BROWSER_PROXY") 32 | if proxy: 33 | co.set_proxy(proxy) 34 | 35 | co.auto_port() 36 | if user_agent: 37 | co.set_user_agent(user_agent) 38 | 39 | co.headless( 40 | os.getenv("BROWSER_HEADLESS", "True").lower() == "true" 41 | ) # 生产环境使用无头模式 42 | 43 | # Mac 系统特殊处理 44 | if sys.platform == "darwin": 45 | co.set_argument("--no-sandbox") 46 | co.set_argument("--disable-gpu") 47 | 48 | return co 49 | 50 | def _get_extension_path(self): 51 | """获取插件路径""" 52 | root_dir = os.getcwd() 53 | extension_path = os.path.join(root_dir, "turnstilePatch") 54 | 55 | if hasattr(sys, "_MEIPASS"): 56 | extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") 57 | 58 | if not os.path.exists(extension_path): 59 | raise FileNotFoundError(f"插件不存在: {extension_path}") 60 | 61 | return extension_path 62 | 63 | def quit(self): 64 | """关闭浏览器""" 65 | if self.browser: 66 | try: 67 | self.browser.quit() 68 | except: 69 | pass 70 | -------------------------------------------------------------------------------- /exit_cursor.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from logger import logging 3 | import time 4 | 5 | def ExitCursor(timeout=5): 6 | """ 7 | 温和地关闭 Cursor 进程 8 | 9 | Args: 10 | timeout (int): 等待进程自然终止的超时时间(秒) 11 | Returns: 12 | bool: 是否成功关闭所有进程 13 | """ 14 | try: 15 | logging.info("开始退出Cursor...") 16 | cursor_processes = [] 17 | # 收集所有 Cursor 进程 18 | for proc in psutil.process_iter(['pid', 'name']): 19 | try: 20 | if proc.info['name'].lower() in ['cursor.exe', 'cursor']: 21 | cursor_processes.append(proc) 22 | except (psutil.NoSuchProcess, psutil.AccessDenied): 23 | continue 24 | 25 | if not cursor_processes: 26 | logging.info("未发现运行中的 Cursor 进程") 27 | return True 28 | 29 | # 温和地请求进程终止 30 | for proc in cursor_processes: 31 | try: 32 | if proc.is_running(): 33 | proc.terminate() # 发送终止信号 34 | except (psutil.NoSuchProcess, psutil.AccessDenied): 35 | continue 36 | 37 | # 等待进程自然终止 38 | start_time = time.time() 39 | while time.time() - start_time < timeout: 40 | still_running = [] 41 | for proc in cursor_processes: 42 | try: 43 | if proc.is_running(): 44 | still_running.append(proc) 45 | except (psutil.NoSuchProcess, psutil.AccessDenied): 46 | continue 47 | 48 | if not still_running: 49 | logging.info("所有 Cursor 进程已正常关闭") 50 | return True 51 | 52 | # 等待一小段时间再检查 53 | time.sleep(0.5) 54 | 55 | # 如果超时后仍有进程在运行 56 | if still_running: 57 | process_list = ", ".join([str(p.pid) for p in still_running]) 58 | logging.warning(f"以下进程未能在规定时间内关闭: {process_list}") 59 | return False 60 | 61 | return True 62 | 63 | except Exception as e: 64 | logging.error(f"关闭 Cursor 进程时发生错误: {str(e)}") 65 | return False 66 | 67 | if __name__ == "__main__": 68 | ExitCursor() 69 | -------------------------------------------------------------------------------- /cursor_auth_manager.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import sys 4 | 5 | 6 | class CursorAuthManager: 7 | """Cursor认证信息管理器""" 8 | 9 | def __init__(self): 10 | # 判断操作系统 11 | if sys.platform == "win32": # Windows 12 | appdata = os.getenv("APPDATA") 13 | if appdata is None: 14 | raise EnvironmentError("APPDATA 环境变量未设置") 15 | self.db_path = os.path.join( 16 | appdata, "Cursor", "User", "globalStorage", "state.vscdb" 17 | ) 18 | elif sys.platform == "darwin": # macOS 19 | self.db_path = os.path.abspath(os.path.expanduser( 20 | "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" 21 | )) 22 | elif sys.platform == "linux" : # Linux 和其他类Unix系统 23 | self.db_path = os.path.abspath(os.path.expanduser( 24 | "~/.config/Cursor/User/globalStorage/state.vscdb" 25 | )) 26 | else: 27 | raise NotImplementedError(f"不支持的操作系统: {sys.platform}") 28 | 29 | def update_auth(self, email=None, access_token=None, refresh_token=None): 30 | """ 31 | 更新Cursor的认证信息 32 | :param email: 新的邮箱地址 33 | :param access_token: 新的访问令牌 34 | :param refresh_token: 新的刷新令牌 35 | :return: bool 是否成功更新 36 | """ 37 | updates = [] 38 | # 登录状态 39 | updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) 40 | 41 | if email is not None: 42 | updates.append(("cursorAuth/cachedEmail", email)) 43 | if access_token is not None: 44 | updates.append(("cursorAuth/accessToken", access_token)) 45 | if refresh_token is not None: 46 | updates.append(("cursorAuth/refreshToken", refresh_token)) 47 | 48 | if not updates: 49 | print("没有提供任何要更新的值") 50 | return False 51 | 52 | conn = None 53 | try: 54 | conn = sqlite3.connect(self.db_path) 55 | cursor = conn.cursor() 56 | 57 | for key, value in updates: 58 | 59 | # 如果没有更新任何行,说明key不存在,执行插入 60 | # 检查 accessToken 是否存在 61 | check_query = f"SELECT COUNT(*) FROM itemTable WHERE key = ?" 62 | cursor.execute(check_query, (key,)) 63 | if cursor.fetchone()[0] == 0: 64 | insert_query = "INSERT INTO itemTable (key, value) VALUES (?, ?)" 65 | cursor.execute(insert_query, (key, value)) 66 | else: 67 | update_query = "UPDATE itemTable SET value = ? WHERE key = ?" 68 | cursor.execute(update_query, (value, key)) 69 | 70 | if cursor.rowcount > 0: 71 | print(f"成功更新 {key.split('/')[-1]}") 72 | else: 73 | print(f"未找到 {key.split('/')[-1]} 或值未变化") 74 | 75 | conn.commit() 76 | return True 77 | 78 | except sqlite3.Error as e: 79 | print("数据库错误:", str(e)) 80 | return False 81 | except Exception as e: 82 | print("发生错误:", str(e)) 83 | return False 84 | finally: 85 | if conn: 86 | conn.close() 87 | -------------------------------------------------------------------------------- /.github/workflows/temp-build.yml: -------------------------------------------------------------------------------- 1 | name: temp Build Executables 2 | 3 | on: 4 | workflow_dispatch: # 手动触发工作流 5 | 6 | jobs: 7 | build-windows: 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.x' 17 | 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install pyinstaller 22 | pip install -r requirements.txt 23 | 24 | - name: Build EXE 25 | run: | 26 | pyinstaller CursorKeepAlive.spec 27 | 28 | - name: Upload Windows artifact 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: CursorPro-Windows 32 | path: dist/CursorPro.exe 33 | 34 | build-macos-arm64: 35 | runs-on: macos-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - name: Set up Python 41 | uses: actions/setup-python@v2 42 | with: 43 | python-version: '3.x' 44 | 45 | - name: Install dependencies 46 | run: | 47 | python -m pip install --upgrade pip 48 | pip install pyinstaller 49 | pip install -r requirements.txt 50 | 51 | - name: Build MacOS ARM executable 52 | run: | 53 | pyinstaller CursorKeepAlive.spec 54 | 55 | - name: Upload MacOS ARM artifact 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: CursorPro-MacOS-ARM64 59 | path: dist/CursorPro 60 | 61 | build-linux: 62 | runs-on: ubuntu-22.04 63 | 64 | steps: 65 | - uses: actions/checkout@v2 66 | 67 | - name: Set up Python 68 | uses: actions/setup-python@v2 69 | with: 70 | python-version: '3.x' 71 | 72 | - name: Install dependencies 73 | run: | 74 | python -m pip install --upgrade pip 75 | pip install pyinstaller 76 | pip install -r requirements.txt 77 | 78 | - name: Build Linux executable 79 | run: | 80 | pyinstaller CursorKeepAlive.spec 81 | 82 | - name: Upload Linux artifact 83 | uses: actions/upload-artifact@v4 84 | with: 85 | name: CursorPro-Linux 86 | path: dist/CursorPro 87 | 88 | build-macos-intel: 89 | runs-on: macos-latest 90 | 91 | steps: 92 | - uses: actions/checkout@v2 93 | 94 | - name: Set up Python 95 | uses: actions/setup-python@v2 96 | with: 97 | python-version: '3.x' 98 | 99 | - name: Install dependencies 100 | run: | 101 | arch -x86_64 pip3 install --upgrade pip 102 | arch -x86_64 pip3 install pyinstaller 103 | arch -x86_64 pip3 install -r requirements.txt 104 | 105 | - name: Build MacOS Intel executable 106 | env: 107 | TARGET_ARCH: 'x86_64' 108 | run: | 109 | arch -x86_64 python3 -m PyInstaller CursorKeepAlive.spec 110 | 111 | - name: Upload MacOS Intel artifact 112 | uses: actions/upload-artifact@v4 113 | with: 114 | name: CursorPro-MacOS-Intel 115 | path: dist/CursorPro -------------------------------------------------------------------------------- /test/get_veri_code_test.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | from DrissionPage.common import Keys 3 | import time 4 | import re 5 | import sys 6 | import os 7 | 8 | 9 | def get_extension_path(): 10 | """获取插件路径""" 11 | root_dir = os.getcwd() 12 | extension_path = os.path.join(root_dir, "turnstilePatch") 13 | 14 | if hasattr(sys, "_MEIPASS"): 15 | print("运行在打包环境中") 16 | extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") 17 | 18 | print(f"尝试加载插件路径: {extension_path}") 19 | 20 | if not os.path.exists(extension_path): 21 | raise FileNotFoundError( 22 | f"插件不存在: {extension_path}\n请确保 turnstilePatch 文件夹在正确位置" 23 | ) 24 | 25 | return extension_path 26 | 27 | 28 | def get_browser_options(): 29 | co = ChromiumOptions() 30 | try: 31 | extension_path = get_extension_path() 32 | co.add_extension(extension_path) 33 | except FileNotFoundError as e: 34 | print(f"警告: {e}") 35 | 36 | co.set_user_agent( 37 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36" 38 | ) 39 | co.set_pref("credentials_enable_service", False) 40 | co.set_argument("--hide-crash-restore-bubble") 41 | co.auto_port() 42 | 43 | # Mac 系统特殊处理 44 | if sys.platform == "darwin": 45 | co.set_argument("--no-sandbox") 46 | co.set_argument("--disable-gpu") 47 | 48 | return co 49 | 50 | 51 | def get_veri_code(username): 52 | # 使用相同的浏览器配置 53 | co = get_browser_options() 54 | browser = Chromium(co) 55 | code = None 56 | 57 | try: 58 | # 获取当前标签页 59 | tab = browser.latest_tab 60 | tab.run_js("try { turnstile.reset() } catch(e) { }") 61 | 62 | # 打开临时邮箱网站 63 | tab.get("https://tempmail.plus/zh") 64 | time.sleep(2) 65 | 66 | # 设置邮箱用户名 67 | while True: 68 | if tab.ele("@id=pre_button"): 69 | # 点击输入框 70 | tab.actions.click("@id=pre_button") 71 | time.sleep(1) 72 | # 删除之前的内容 73 | tab.run_js('document.getElementById("pre_button").value = ""') 74 | 75 | # 输入新用户名并回车 76 | tab.actions.input(username).key_down(Keys.ENTER).key_up(Keys.ENTER) 77 | break 78 | time.sleep(1) 79 | 80 | # 等待并获取新邮件 81 | while True: 82 | new_mail = tab.ele("@class=mail") 83 | if new_mail: 84 | if new_mail.text: 85 | print("最新的邮件:", new_mail.text) 86 | tab.actions.click("@class=mail") 87 | break 88 | else: 89 | print(new_mail) 90 | break 91 | time.sleep(1) 92 | 93 | # 提取验证码 94 | if tab.ele("@class=overflow-auto mb-20"): 95 | email_content = tab.ele("@class=overflow-auto mb-20").text 96 | verification_code = re.search( 97 | r"verification code is (\d{6})", email_content 98 | ) 99 | if verification_code: 100 | code = verification_code.group(1) 101 | print("验证码:", code) 102 | else: 103 | print("未找到验证码") 104 | 105 | # 删除邮件 106 | if tab.ele("@id=delete_mail"): 107 | tab.actions.click("@id=delete_mail") 108 | time.sleep(1) 109 | 110 | if tab.ele("@id=confirm_mail"): 111 | tab.actions.click("@id=confirm_mail") 112 | print("删除邮件") 113 | 114 | except Exception as e: 115 | print(f"发生错误: {str(e)}") 116 | finally: 117 | browser.quit() 118 | 119 | return code 120 | 121 | 122 | # 测试运行 123 | if __name__ == "__main__": 124 | test_username = "test_user" # 替换为你要测试的用户名 125 | code = get_veri_code(test_username) 126 | print(f"获取到的验证码: {code}") 127 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Executables 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # 添加标签触发条件,匹配 v1.0.0 这样的标签 7 | 8 | jobs: 9 | build-windows: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: '3.x' 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install pyinstaller 24 | pip install -r requirements.txt 25 | 26 | - name: Build EXE 27 | run: | 28 | pyinstaller CursorKeepAlive.spec 29 | 30 | - name: Upload Windows artifact 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: CursorPro-Windows 34 | path: dist/CursorPro.exe 35 | 36 | build-macos-arm64: 37 | runs-on: macos-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v2 41 | 42 | - name: Set up Python 43 | uses: actions/setup-python@v2 44 | with: 45 | python-version: '3.x' 46 | 47 | - name: Install dependencies 48 | run: | 49 | python -m pip install --upgrade pip 50 | pip install pyinstaller 51 | pip install -r requirements.txt 52 | 53 | - name: Build MacOS ARM executable 54 | run: | 55 | pyinstaller CursorKeepAlive.spec 56 | 57 | - name: Upload MacOS ARM artifact 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: CursorPro-MacOS-ARM64 61 | path: dist/CursorPro 62 | 63 | build-linux: 64 | runs-on: ubuntu-22.04 65 | 66 | steps: 67 | - uses: actions/checkout@v2 68 | 69 | - name: Set up Python 70 | uses: actions/setup-python@v2 71 | with: 72 | python-version: '3.x' 73 | 74 | - name: Install dependencies 75 | run: | 76 | python -m pip install --upgrade pip 77 | pip install pyinstaller 78 | pip install -r requirements.txt 79 | 80 | - name: Build Linux executable 81 | run: | 82 | pyinstaller CursorKeepAlive.spec 83 | 84 | - name: Upload Linux artifact 85 | uses: actions/upload-artifact@v4 86 | with: 87 | name: CursorPro-Linux 88 | path: dist/CursorPro 89 | 90 | build-macos-intel: 91 | runs-on: macos-latest 92 | 93 | steps: 94 | - uses: actions/checkout@v2 95 | 96 | - name: Set up Python 97 | uses: actions/setup-python@v2 98 | with: 99 | python-version: '3.x' 100 | 101 | - name: Install dependencies 102 | run: | 103 | arch -x86_64 pip3 install --upgrade pip 104 | arch -x86_64 pip3 install pyinstaller 105 | arch -x86_64 pip3 install -r requirements.txt 106 | 107 | - name: Build MacOS Intel executable 108 | env: 109 | TARGET_ARCH: 'x86_64' 110 | run: | 111 | arch -x86_64 python3 -m PyInstaller CursorKeepAlive.spec 112 | 113 | - name: Upload MacOS Intel artifact 114 | uses: actions/upload-artifact@v4 115 | with: 116 | name: CursorPro-MacOS-Intel 117 | path: dist/CursorPro 118 | 119 | create-release: 120 | needs: [build-windows, build-macos-arm64, build-linux, build-macos-intel] 121 | runs-on: ubuntu-22.04 122 | if: startsWith(github.ref, 'refs/tags/') 123 | 124 | steps: 125 | - name: Download all artifacts 126 | uses: actions/download-artifact@v4 127 | with: 128 | path: artifacts 129 | 130 | - name: Create release archives 131 | run: | 132 | cd artifacts 133 | zip -r CursorPro-Windows.zip CursorPro-Windows/ 134 | zip -r CursorPro-MacOS-ARM64.zip CursorPro-MacOS-ARM64/ 135 | zip -r CursorPro-Linux.zip CursorPro-Linux/ 136 | zip -r CursorPro-MacOS-Intel.zip CursorPro-MacOS-Intel/ 137 | 138 | 139 | - name: Create Release 140 | uses: softprops/action-gh-release@v1 141 | with: 142 | files: | 143 | artifacts/CursorPro-Windows.zip 144 | artifacts/CursorPro-MacOS-ARM64.zip 145 | artifacts/CursorPro-Linux.zip 146 | artifacts/CursorPro-MacOS-Intel.zip 147 | 148 | env: 149 | GITHUB_TOKEN: ${{ secrets.TOKEN }} -------------------------------------------------------------------------------- /reset_machine.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import uuid 5 | import hashlib 6 | import shutil 7 | from colorama import Fore, Style, init 8 | 9 | # 初始化colorama 10 | init() 11 | 12 | # 定义emoji和颜色常量 13 | EMOJI = { 14 | "FILE": "📄", 15 | "BACKUP": "💾", 16 | "SUCCESS": "✅", 17 | "ERROR": "❌", 18 | "INFO": "ℹ️", 19 | "RESET": "🔄", 20 | } 21 | 22 | 23 | class MachineIDResetter: 24 | def __init__(self): 25 | # 判断操作系统 26 | if sys.platform == "win32": # Windows 27 | appdata = os.getenv("APPDATA") 28 | if appdata is None: 29 | raise EnvironmentError("APPDATA 环境变量未设置") 30 | self.db_path = os.path.join( 31 | appdata, "Cursor", "User", "globalStorage", "storage.json" 32 | ) 33 | elif sys.platform == "darwin": # macOS 34 | self.db_path = os.path.abspath( 35 | os.path.expanduser( 36 | "~/Library/Application Support/Cursor/User/globalStorage/storage.json" 37 | ) 38 | ) 39 | elif sys.platform == "linux": # Linux 和其他类Unix系统 40 | self.db_path = os.path.abspath( 41 | os.path.expanduser("~/.config/Cursor/User/globalStorage/storage.json") 42 | ) 43 | else: 44 | raise NotImplementedError(f"不支持的操作系统: {sys.platform}") 45 | 46 | def generate_new_ids(self): 47 | """生成新的机器ID""" 48 | # 生成新的UUID 49 | dev_device_id = str(uuid.uuid4()) 50 | 51 | # 生成新的machineId (64个字符的十六进制) 52 | machine_id = hashlib.sha256(os.urandom(32)).hexdigest() 53 | 54 | # 生成新的macMachineId (128个字符的十六进制) 55 | mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() 56 | 57 | # 生成新的sqmId 58 | sqm_id = "{" + str(uuid.uuid4()).upper() + "}" 59 | 60 | return { 61 | "telemetry.devDeviceId": dev_device_id, 62 | "telemetry.macMachineId": mac_machine_id, 63 | "telemetry.machineId": machine_id, 64 | "telemetry.sqmId": sqm_id, 65 | } 66 | 67 | def reset_machine_ids(self): 68 | """重置机器ID并备份原文件""" 69 | try: 70 | print(f"{Fore.CYAN}{EMOJI['INFO']} 正在检查配置文件...{Style.RESET_ALL}") 71 | 72 | # 检查文件是否存在 73 | if not os.path.exists(self.db_path): 74 | print( 75 | f"{Fore.RED}{EMOJI['ERROR']} 配置文件不存在: {self.db_path}{Style.RESET_ALL}" 76 | ) 77 | return False 78 | 79 | # 检查文件权限 80 | if not os.access(self.db_path, os.R_OK | os.W_OK): 81 | print( 82 | f"{Fore.RED}{EMOJI['ERROR']} 无法读写配置文件,请检查文件权限!{Style.RESET_ALL}" 83 | ) 84 | print( 85 | f"{Fore.RED}{EMOJI['ERROR']} 如果你使用过 go-cursor-help 来修改 ID; 请修改文件只读权限 {self.db_path} {Style.RESET_ALL}" 86 | ) 87 | return False 88 | 89 | # 读取现有配置 90 | print(f"{Fore.CYAN}{EMOJI['FILE']} 读取当前配置...{Style.RESET_ALL}") 91 | with open(self.db_path, "r", encoding="utf-8") as f: 92 | config = json.load(f) 93 | 94 | # 生成新的ID 95 | print(f"{Fore.CYAN}{EMOJI['RESET']} 生成新的机器标识...{Style.RESET_ALL}") 96 | new_ids = self.generate_new_ids() 97 | 98 | # 更新配置 99 | config.update(new_ids) 100 | 101 | # 保存新配置 102 | print(f"{Fore.CYAN}{EMOJI['FILE']} 保存新配置...{Style.RESET_ALL}") 103 | with open(self.db_path, "w", encoding="utf-8") as f: 104 | json.dump(config, f, indent=4) 105 | 106 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} 机器标识重置成功!{Style.RESET_ALL}") 107 | print(f"\n{Fore.CYAN}新的机器标识:{Style.RESET_ALL}") 108 | for key, value in new_ids.items(): 109 | print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") 110 | 111 | return True 112 | 113 | except PermissionError as e: 114 | print(f"{Fore.RED}{EMOJI['ERROR']} 权限错误: {str(e)}{Style.RESET_ALL}") 115 | print( 116 | f"{Fore.YELLOW}{EMOJI['INFO']} 请尝试以管理员身份运行此程序{Style.RESET_ALL}" 117 | ) 118 | return False 119 | except Exception as e: 120 | print(f"{Fore.RED}{EMOJI['ERROR']} 重置过程出错: {str(e)}{Style.RESET_ALL}") 121 | 122 | return False 123 | 124 | 125 | if __name__ == "__main__": 126 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 127 | print(f"{Fore.CYAN}{EMOJI['RESET']} Cursor 机器标识重置工具{Style.RESET_ALL}") 128 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 129 | 130 | resetter = MachineIDResetter() 131 | resetter.reset_machine_ids() 132 | 133 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 134 | input(f"{EMOJI['INFO']} 按回车键退出...") 135 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | import sys 4 | from logger import logging 5 | 6 | 7 | class Config: 8 | def __init__(self): 9 | # 获取应用程序的根目录路径 10 | if getattr(sys, "frozen", False): 11 | # 如果是打包后的可执行文件 12 | application_path = os.path.dirname(sys.executable) 13 | else: 14 | # 如果是开发环境 15 | application_path = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | # 指定 .env 文件的路径 18 | dotenv_path = os.path.join(application_path, ".env") 19 | 20 | if not os.path.exists(dotenv_path): 21 | raise FileNotFoundError(f"文件 {dotenv_path} 不存在") 22 | 23 | # 加载 .env 文件 24 | load_dotenv(dotenv_path) 25 | 26 | self.imap = False 27 | self.temp_mail = os.getenv("TEMP_MAIL", "").strip().split("@")[0] 28 | self.temp_mail_epin = os.getenv("TEMP_MAIL_EPIN", "").strip() 29 | self.temp_mail_ext = os.getenv("TEMP_MAIL_EXT", "").strip() 30 | self.domain = os.getenv("DOMAIN", "").strip() 31 | 32 | # 如果临时邮箱为null则加载IMAP 33 | if self.temp_mail == "null": 34 | self.imap = True 35 | self.imap_server = os.getenv("IMAP_SERVER", "").strip() 36 | self.imap_port = os.getenv("IMAP_PORT", "").strip() 37 | self.imap_user = os.getenv("IMAP_USER", "").strip() 38 | self.imap_pass = os.getenv("IMAP_PASS", "").strip() 39 | self.imap_dir = os.getenv("IMAP_DIR", "inbox").strip() 40 | 41 | self.check_config() 42 | 43 | def get_temp_mail(self): 44 | 45 | return self.temp_mail 46 | 47 | def get_temp_mail_epin(self): 48 | 49 | return self.temp_mail_epin 50 | 51 | def get_temp_mail_ext(self): 52 | 53 | return self.temp_mail_ext 54 | 55 | def get_imap(self): 56 | if not self.imap: 57 | return False 58 | return { 59 | "imap_server": self.imap_server, 60 | "imap_port": self.imap_port, 61 | "imap_user": self.imap_user, 62 | "imap_pass": self.imap_pass, 63 | "imap_dir": self.imap_dir, 64 | } 65 | 66 | def get_domain(self): 67 | return self.domain 68 | 69 | def check_config(self): 70 | """检查配置项是否有效 71 | 72 | 检查规则: 73 | 1. 如果使用 tempmail.plus,需要配置 TEMP_MAIL 和 DOMAIN 74 | 2. 如果使用 IMAP,需要配置 IMAP_SERVER、IMAP_PORT、IMAP_USER、IMAP_PASS 75 | 3. IMAP_DIR 是可选的 76 | """ 77 | # 基础配置检查 78 | required_configs = { 79 | "domain": "域名", 80 | } 81 | 82 | # 检查基础配置 83 | for key, name in required_configs.items(): 84 | if not self.check_is_valid(getattr(self, key)): 85 | raise ValueError(f"{name}未配置,请在 .env 文件中设置 {key.upper()}") 86 | 87 | # 检查邮箱配置 88 | if self.temp_mail != "null": 89 | # tempmail.plus 模式 90 | if not self.check_is_valid(self.temp_mail): 91 | raise ValueError("临时邮箱未配置,请在 .env 文件中设置 TEMP_MAIL") 92 | else: 93 | # IMAP 模式 94 | imap_configs = { 95 | "imap_server": "IMAP服务器", 96 | "imap_port": "IMAP端口", 97 | "imap_user": "IMAP用户名", 98 | "imap_pass": "IMAP密码", 99 | } 100 | 101 | for key, name in imap_configs.items(): 102 | value = getattr(self, key) 103 | if value == "null" or not self.check_is_valid(value): 104 | raise ValueError( 105 | f"{name}未配置,请在 .env 文件中设置 {key.upper()}" 106 | ) 107 | 108 | # IMAP_DIR 是可选的,如果设置了就检查其有效性 109 | if self.imap_dir != "null" and not self.check_is_valid(self.imap_dir): 110 | raise ValueError( 111 | "IMAP收件箱目录配置无效,请在 .env 文件中正确设置 IMAP_DIR" 112 | ) 113 | 114 | def check_is_valid(self, value): 115 | """检查配置项是否有效 116 | 117 | Args: 118 | value: 配置项的值 119 | 120 | Returns: 121 | bool: 配置项是否有效 122 | """ 123 | return isinstance(value, str) and len(str(value).strip()) > 0 124 | 125 | def print_config(self): 126 | if self.imap: 127 | logging.info(f"\033[32mIMAP服务器: {self.imap_server}\033[0m") 128 | logging.info(f"\033[32mIMAP端口: {self.imap_port}\033[0m") 129 | logging.info(f"\033[32mIMAP用户名: {self.imap_user}\033[0m") 130 | logging.info(f"\033[32mIMAP密码: {'*' * len(self.imap_pass)}\033[0m") 131 | logging.info(f"\033[32mIMAP收件箱目录: {self.imap_dir}\033[0m") 132 | if self.temp_mail != "null": 133 | logging.info( 134 | f"\033[32m临时邮箱: {self.temp_mail}{self.temp_mail_ext}\033[0m" 135 | ) 136 | logging.info(f"\033[32m域名: {self.domain}\033[0m") 137 | 138 | 139 | # 使用示例 140 | if __name__ == "__main__": 141 | try: 142 | config = Config() 143 | print("环境变量加载成功!") 144 | config.print_config() 145 | except ValueError as e: 146 | print(f"错误: {e}") 147 | -------------------------------------------------------------------------------- /get_email_code.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | import re 4 | from config import Config 5 | import requests 6 | import email 7 | import imaplib 8 | 9 | 10 | class EmailVerificationHandler: 11 | def __init__(self): 12 | self.imap = Config().get_imap() 13 | self.username = Config().get_temp_mail() 14 | self.epin = Config().get_temp_mail_epin() 15 | self.session = requests.Session() 16 | self.emailExtension = Config().get_temp_mail_ext() 17 | 18 | def get_verification_code(self): 19 | code = None 20 | 21 | try: 22 | print("正在处理...") 23 | 24 | if self.imap is False: 25 | # 等待并获取最新邮件 26 | code, first_id = self._get_latest_mail_code() 27 | # 清理邮件 28 | self._cleanup_mail(first_id) 29 | else: 30 | code = self._get_mail_code_by_imap() 31 | 32 | except Exception as e: 33 | print(f"获取验证码失败: {str(e)}") 34 | 35 | return code 36 | 37 | # 使用imap获取邮件 38 | def _get_mail_code_by_imap(self, retry = 0): 39 | if retry > 0: 40 | time.sleep(3) 41 | if retry >= 20: 42 | raise Exception("获取验证码超时") 43 | try: 44 | # 连接到IMAP服务器 45 | mail = imaplib.IMAP4_SSL(self.imap['imap_server'], self.imap['imap_port']) 46 | mail.login(self.imap['imap_user'], self.imap['imap_pass']) 47 | mail.select(self.imap['imap_dir']) 48 | 49 | status, messages = mail.search(None, 'FROM', '"no-reply@cursor.sh"') 50 | if status != 'OK': 51 | return None 52 | 53 | mail_ids = messages[0].split() 54 | if not mail_ids: 55 | # 没有获取到,就在获取一次 56 | return self._get_mail_code_by_imap(retry=retry + 1) 57 | 58 | latest_mail_id = mail_ids[-1] 59 | 60 | # 获取邮件内容 61 | status, msg_data = mail.fetch(latest_mail_id, '(RFC822)') 62 | if status != 'OK': 63 | return None 64 | 65 | raw_email = msg_data[0][1] 66 | email_message = email.message_from_bytes(raw_email) 67 | 68 | # 提取邮件正文 69 | body = self._extract_imap_body(email_message) 70 | if body: 71 | # 使用正则表达式查找6位数字验证码 72 | code_match = re.search(r"\b\d{6}\b", body) 73 | if code_match: 74 | code = code_match.group() 75 | # 删除邮件 76 | mail.store(latest_mail_id, '+FLAGS', '\\Deleted') 77 | mail.expunge() 78 | mail.logout() 79 | # print(f"找到的验证码: {code}") 80 | return code 81 | # print("未找到验证码") 82 | mail.logout() 83 | return None 84 | except Exception as e: 85 | print(f"发生错误: {e}") 86 | return None 87 | 88 | def _extract_imap_body(self, email_message): 89 | # 提取邮件正文 90 | if email_message.is_multipart(): 91 | for part in email_message.walk(): 92 | content_type = part.get_content_type() 93 | content_disposition = str(part.get("Content-Disposition")) 94 | if content_type == "text/plain" and "attachment" not in content_disposition: 95 | charset = part.get_content_charset() or 'utf-8' 96 | try: 97 | body = part.get_payload(decode=True).decode(charset, errors='ignore') 98 | return body 99 | except Exception as e: 100 | logging.error(f"解码邮件正文失败: {e}") 101 | else: 102 | content_type = email_message.get_content_type() 103 | if content_type == "text/plain": 104 | charset = email_message.get_content_charset() or 'utf-8' 105 | try: 106 | body = email_message.get_payload(decode=True).decode(charset, errors='ignore') 107 | return body 108 | except Exception as e: 109 | logging.error(f"解码邮件正文失败: {e}") 110 | return "" 111 | 112 | # 手动输入验证码 113 | def _get_latest_mail_code(self): 114 | # 获取邮件列表 115 | mail_list_url = f"https://tempmail.plus/api/mails?email={self.username}{self.emailExtension}&limit=20&epin={self.epin}" 116 | mail_list_response = self.session.get(mail_list_url) 117 | mail_list_data = mail_list_response.json() 118 | time.sleep(0.5) 119 | if not mail_list_data.get("result"): 120 | return None, None 121 | 122 | # 获取最新邮件的ID 123 | first_id = mail_list_data.get("first_id") 124 | if not first_id: 125 | return None, None 126 | 127 | # 获取具体邮件内容 128 | mail_detail_url = f"https://tempmail.plus/api/mails/{first_id}?email={self.username}{self.emailExtension}&epin={self.epin}" 129 | mail_detail_response = self.session.get(mail_detail_url) 130 | mail_detail_data = mail_detail_response.json() 131 | time.sleep(0.5) 132 | if not mail_detail_data.get("result"): 133 | return None, None 134 | 135 | # 从邮件文本中提取6位数字验证码 136 | mail_text = mail_detail_data.get("text", "") 137 | # 修改正则表达式,确保 6 位数字不紧跟在字母或域名相关符号后面 138 | code_match = re.search(r"(? logging.Logger: 17 | """配置并返回logger实例""" 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.INFO) 20 | handler = logging.StreamHandler() 21 | formatter = logging.Formatter( 22 | "%(asctime)s - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" 23 | ) 24 | handler.setFormatter(formatter) 25 | logger.addHandler(handler) 26 | return logger 27 | 28 | 29 | logger = setup_logging() 30 | 31 | 32 | def get_cursor_paths() -> Tuple[str, str]: 33 | """ 34 | 根据不同操作系统获取 Cursor 相关路径 35 | 36 | Returns: 37 | Tuple[str, str]: (package.json路径, main.js路径)的元组 38 | 39 | Raises: 40 | OSError: 当找不到有效路径或系统不支持时抛出 41 | """ 42 | system = platform.system() 43 | 44 | paths_map = { 45 | "Darwin": { 46 | "base": "/Applications/Cursor.app/Contents/Resources/app", 47 | "package": "package.json", 48 | "main": "out/main.js", 49 | }, 50 | "Windows": { 51 | "base": os.path.join( 52 | os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app" 53 | ), 54 | "package": "package.json", 55 | "main": "out/main.js", 56 | }, 57 | "Linux": { 58 | "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], 59 | "package": "package.json", 60 | "main": "out/main.js", 61 | }, 62 | } 63 | 64 | if system not in paths_map: 65 | raise OSError(f"不支持的操作系统: {system}") 66 | 67 | if system == "Linux": 68 | for base in paths_map["Linux"]["bases"]: 69 | pkg_path = os.path.join(base, paths_map["Linux"]["package"]) 70 | if os.path.exists(pkg_path): 71 | return (pkg_path, os.path.join(base, paths_map["Linux"]["main"])) 72 | raise OSError("在 Linux 系统上未找到 Cursor 安装路径") 73 | 74 | base_path = paths_map[system]["base"] 75 | return ( 76 | os.path.join(base_path, paths_map[system]["package"]), 77 | os.path.join(base_path, paths_map[system]["main"]), 78 | ) 79 | 80 | 81 | def check_system_requirements(pkg_path: str, main_path: str) -> bool: 82 | """ 83 | 检查系统要求 84 | 85 | Args: 86 | pkg_path: package.json 文件路径 87 | main_path: main.js 文件路径 88 | 89 | Returns: 90 | bool: 检查是否通过 91 | """ 92 | for file_path in [pkg_path, main_path]: 93 | if not os.path.isfile(file_path): 94 | logger.error(f"文件不存在: {file_path}") 95 | return False 96 | 97 | if not os.access(file_path, os.W_OK): 98 | logger.error(f"没有文件写入权限: {file_path}") 99 | return False 100 | 101 | return True 102 | 103 | 104 | def version_check(version: str, min_version: str = "", max_version: str = "") -> bool: 105 | """ 106 | 版本号检查 107 | 108 | Args: 109 | version: 当前版本号 110 | min_version: 最小版本号要求 111 | max_version: 最大版本号要求 112 | 113 | Returns: 114 | bool: 版本号是否符合要求 115 | """ 116 | version_pattern = r"^\d+\.\d+\.\d+$" 117 | try: 118 | if not re.match(version_pattern, version): 119 | logger.error(f"无效的版本号格式: {version}") 120 | return False 121 | 122 | def parse_version(ver: str) -> Tuple[int, ...]: 123 | return tuple(map(int, ver.split("."))) 124 | 125 | current = parse_version(version) 126 | 127 | if min_version and current < parse_version(min_version): 128 | logger.error(f"版本号 {version} 小于最小要求 {min_version}") 129 | return False 130 | 131 | if max_version and current > parse_version(max_version): 132 | logger.error(f"版本号 {version} 大于最大要求 {max_version}") 133 | return False 134 | 135 | return True 136 | 137 | except Exception as e: 138 | logger.error(f"版本检查失败: {str(e)}") 139 | return False 140 | 141 | 142 | def modify_main_js(main_path: str) -> bool: 143 | """ 144 | 修改 main.js 文件 145 | 146 | Args: 147 | main_path: main.js 文件路径 148 | 149 | Returns: 150 | bool: 修改是否成功 151 | """ 152 | try: 153 | # 获取原始文件的权限和所有者信息 154 | original_stat = os.stat(main_path) 155 | original_mode = original_stat.st_mode 156 | original_uid = original_stat.st_uid 157 | original_gid = original_stat.st_gid 158 | 159 | with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: 160 | with open(main_path, "r", encoding="utf-8") as main_file: 161 | content = main_file.read() 162 | 163 | # 执行替换 164 | patterns = { 165 | r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}", 166 | r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}", 167 | } 168 | 169 | for pattern, replacement in patterns.items(): 170 | content = re.sub(pattern, replacement, content) 171 | 172 | tmp_file.write(content) 173 | tmp_path = tmp_file.name 174 | 175 | # 使用 shutil.copy2 保留文件权限 176 | shutil.copy2(main_path, main_path + ".old") 177 | shutil.move(tmp_path, main_path) 178 | 179 | # 恢复原始文件的权限和所有者 180 | os.chmod(main_path, original_mode) 181 | if os.name != "nt": # 在非Windows系统上设置所有者 182 | os.chown(main_path, original_uid, original_gid) 183 | 184 | logger.info("文件修改成功") 185 | return True 186 | 187 | except Exception as e: 188 | logger.error(f"修改文件时发生错误: {str(e)}") 189 | if "tmp_path" in locals(): 190 | os.unlink(tmp_path) 191 | return False 192 | 193 | 194 | def backup_files(pkg_path: str, main_path: str) -> bool: 195 | """ 196 | 备份原始文件 197 | 198 | Args: 199 | pkg_path: package.json 文件路径(未使用) 200 | main_path: main.js 文件路径 201 | 202 | Returns: 203 | bool: 备份是否成功 204 | """ 205 | try: 206 | # 只备份 main.js 207 | if os.path.exists(main_path): 208 | backup_main = f"{main_path}.bak" 209 | shutil.copy2(main_path, backup_main) 210 | logger.info(f"已备份 main.js: {backup_main}") 211 | 212 | return True 213 | except Exception as e: 214 | logger.error(f"备份文件失败: {str(e)}") 215 | return False 216 | 217 | 218 | def restore_backup_files(pkg_path: str, main_path: str) -> bool: 219 | """ 220 | 恢复备份文件 221 | 222 | Args: 223 | pkg_path: package.json 文件路径(未使用) 224 | main_path: main.js 文件路径 225 | 226 | Returns: 227 | bool: 恢复是否成功 228 | """ 229 | try: 230 | # 只恢复 main.js 231 | backup_main = f"{main_path}.bak" 232 | if os.path.exists(backup_main): 233 | shutil.copy2(backup_main, main_path) 234 | logger.info(f"已恢复 main.js") 235 | return True 236 | 237 | logger.error("未找到备份文件") 238 | return False 239 | except Exception as e: 240 | logger.error(f"恢复备份失败: {str(e)}") 241 | return False 242 | 243 | 244 | def patch_cursor_get_machine_id(restore_mode=False) -> None: 245 | """ 246 | 主函数 247 | 248 | Args: 249 | restore_mode: 是否为恢复模式 250 | """ 251 | logger.info("开始执行脚本...") 252 | 253 | try: 254 | # 获取路径 255 | pkg_path, main_path = get_cursor_paths() 256 | 257 | # 检查系统要求 258 | if not check_system_requirements(pkg_path, main_path): 259 | sys.exit(1) 260 | 261 | if restore_mode: 262 | # 恢复备份 263 | if restore_backup_files(pkg_path, main_path): 264 | logger.info("备份恢复完成") 265 | else: 266 | logger.error("备份恢复失败") 267 | return 268 | 269 | # 获取版本号 270 | try: 271 | with open(pkg_path, "r", encoding="utf-8") as f: 272 | version = json.load(f)["version"] 273 | logger.info(f"当前 Cursor 版本: {version}") 274 | except Exception as e: 275 | logger.error(f"无法读取版本号: {str(e)}") 276 | sys.exit(1) 277 | 278 | # 检查版本 279 | if not version_check(version, min_version="0.45.0"): 280 | logger.error("版本不符合要求(需 >= 0.45.x)") 281 | sys.exit(1) 282 | 283 | logger.info("版本检查通过,准备修改文件") 284 | 285 | # 备份文件 286 | if not backup_files(pkg_path, main_path): 287 | logger.error("文件备份失败,终止操作") 288 | sys.exit(1) 289 | 290 | # 修改文件 291 | if not modify_main_js(main_path): 292 | sys.exit(1) 293 | 294 | logger.info("脚本执行完成") 295 | 296 | except Exception as e: 297 | logger.error(f"执行过程中发生错误: {str(e)}") 298 | sys.exit(1) 299 | 300 | 301 | if __name__ == "__main__": 302 | patch_cursor_get_machine_id() 303 | -------------------------------------------------------------------------------- /cursor_pro_keep_alive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import json 4 | import sys 5 | from colorama import Fore, Style 6 | 7 | from exit_cursor import ExitCursor 8 | import patch_cursor_get_machine_id 9 | from reset_machine import MachineIDResetter 10 | 11 | os.environ["PYTHONVERBOSE"] = "0" 12 | os.environ["PYINSTALLER_VERBOSE"] = "0" 13 | 14 | import time 15 | import random 16 | from cursor_auth_manager import CursorAuthManager 17 | import os 18 | from logger import logging 19 | from browser_utils import BrowserManager 20 | from get_email_code import EmailVerificationHandler 21 | from logo import print_logo 22 | from config import Config 23 | from datetime import datetime 24 | 25 | # 定义 EMOJI 字典 26 | EMOJI = {"ERROR": "❌", "WARNING": "⚠️", "INFO": "ℹ️"} 27 | 28 | 29 | def save_screenshot(tab, prefix="turnstile"): 30 | """保存截图 31 | Args: 32 | tab: 浏览器标签页对象 33 | prefix: 文件名前缀 34 | Returns: 35 | str: 截图文件路径 36 | """ 37 | try: 38 | # 创建 screenshots 目录 39 | screenshot_dir = "screenshots" 40 | if not os.path.exists(screenshot_dir): 41 | os.makedirs(screenshot_dir) 42 | 43 | # 生成带时间戳的文件名 44 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 45 | filename = f"{prefix}_{timestamp}.png" 46 | filepath = os.path.join(screenshot_dir, filename) 47 | 48 | # 使用 get_screenshot 方法保存截图 49 | tab.get_screenshot(filepath) 50 | logging.info(f"已保存截图: {filepath}") 51 | return filepath 52 | except Exception as e: 53 | logging.error(f"截图保存失败: {str(e)}") 54 | return None 55 | 56 | 57 | def handle_turnstile(tab): 58 | logging.info("正在检测 Turnstile 验证...") 59 | save_screenshot(tab, "turnstile") 60 | try: 61 | while True: 62 | try: 63 | challengeCheck = ( 64 | tab.ele("@id=cf-turnstile", timeout=2) 65 | .child() 66 | .shadow_root.ele("tag:iframe") 67 | .ele("tag:body") 68 | .sr("tag:input") 69 | ) 70 | 71 | if challengeCheck: 72 | logging.info("检测到 Turnstile 验证,正在处理...") 73 | time.sleep(random.uniform(1, 3)) 74 | challengeCheck.click() 75 | time.sleep(2) 76 | logging.info("Turnstile 验证通过") 77 | save_screenshot(tab, "turnstile_pass") 78 | return True 79 | except: 80 | pass 81 | 82 | if tab.ele("@name=password"): 83 | logging.info("验证成功 - 已到达密码输入页面") 84 | break 85 | if tab.ele("@data-index=0"): 86 | logging.info("验证成功 - 已到达验证码输入页面") 87 | break 88 | if tab.ele("Account Settings"): 89 | logging.info("验证成功 - 已到达账户设置页面") 90 | break 91 | time.sleep(random.uniform(1, 2)) 92 | except Exception as e: 93 | logging.error(f"Turnstile 验证失败: {str(e)}") 94 | return False 95 | 96 | 97 | def get_cursor_session_token(tab, max_attempts=3, retry_interval=2): 98 | """ 99 | 获取Cursor会话token,带有重试机制 100 | :param tab: 浏览器标签页 101 | :param max_attempts: 最大尝试次数 102 | :param retry_interval: 重试间隔(秒) 103 | :return: session token 或 None 104 | """ 105 | logging.info("开始获取cookie") 106 | attempts = 0 107 | 108 | while attempts < max_attempts: 109 | try: 110 | cookies = tab.cookies() 111 | for cookie in cookies: 112 | if cookie.get("name") == "WorkosCursorSessionToken": 113 | return cookie["value"].split("%3A%3A")[1] 114 | 115 | attempts += 1 116 | if attempts < max_attempts: 117 | logging.warning( 118 | f"第 {attempts} 次尝试未获取到CursorSessionToken,{retry_interval}秒后重试..." 119 | ) 120 | time.sleep(retry_interval) 121 | else: 122 | logging.error( 123 | f"已达到最大尝试次数({max_attempts}),获取CursorSessionToken失败" 124 | ) 125 | 126 | except Exception as e: 127 | logging.error(f"获取cookie失败: {str(e)}") 128 | attempts += 1 129 | if attempts < max_attempts: 130 | logging.info(f"将在 {retry_interval} 秒后重试...") 131 | time.sleep(retry_interval) 132 | 133 | return None 134 | 135 | 136 | def update_cursor_auth(email=None, access_token=None, refresh_token=None): 137 | """ 138 | 更新Cursor的认证信息的便捷函数 139 | """ 140 | auth_manager = CursorAuthManager() 141 | return auth_manager.update_auth(email, access_token, refresh_token) 142 | 143 | 144 | def sign_up_account(browser, tab): 145 | logging.info("=== 开始注册账号流程 ===") 146 | logging.info(f"正在访问注册页面: {sign_up_url}") 147 | tab.get(sign_up_url) 148 | 149 | try: 150 | if tab.ele("@name=first_name"): 151 | logging.info("正在填写个人信息...") 152 | tab.actions.click("@name=first_name").input(first_name) 153 | logging.info(f"已输入名字: {first_name}") 154 | time.sleep(random.uniform(1, 3)) 155 | 156 | tab.actions.click("@name=last_name").input(last_name) 157 | logging.info(f"已输入姓氏: {last_name}") 158 | time.sleep(random.uniform(1, 3)) 159 | 160 | tab.actions.click("@name=email").input(account) 161 | logging.info(f"已输入邮箱: {account}") 162 | time.sleep(random.uniform(1, 3)) 163 | 164 | logging.info("提交个人信息...") 165 | tab.actions.click("@type=submit") 166 | 167 | except Exception as e: 168 | logging.error(f"注册页面访问失败: {str(e)}") 169 | return False 170 | 171 | handle_turnstile(tab) 172 | 173 | try: 174 | if tab.ele("@name=password"): 175 | logging.info("正在设置密码...") 176 | tab.ele("@name=password").input(password) 177 | time.sleep(random.uniform(1, 3)) 178 | 179 | logging.info("提交密码...") 180 | tab.ele("@type=submit").click() 181 | logging.info("密码设置完成,等待系统响应...") 182 | 183 | except Exception as e: 184 | logging.error(f"密码设置失败: {str(e)}") 185 | return False 186 | 187 | if tab.ele("This email is not available."): 188 | logging.error("注册失败:邮箱已被使用") 189 | return False 190 | 191 | handle_turnstile(tab) 192 | 193 | while True: 194 | try: 195 | if tab.ele("Account Settings"): 196 | logging.info("注册成功 - 已进入账户设置页面") 197 | break 198 | if tab.ele("@data-index=0"): 199 | logging.info("正在获取邮箱验证码...") 200 | code = email_handler.get_verification_code() 201 | if not code: 202 | logging.error("获取验证码失败") 203 | return False 204 | 205 | logging.info(f"成功获取验证码: {code}") 206 | logging.info("正在输入验证码...") 207 | i = 0 208 | for digit in code: 209 | tab.ele(f"@data-index={i}").input(digit) 210 | time.sleep(random.uniform(0.1, 0.3)) 211 | i += 1 212 | logging.info("验证码输入完成") 213 | break 214 | except Exception as e: 215 | logging.error(f"验证码处理过程出错: {str(e)}") 216 | 217 | handle_turnstile(tab) 218 | wait_time = random.randint(3, 6) 219 | for i in range(wait_time): 220 | logging.info(f"等待系统处理中... 剩余 {wait_time-i} 秒") 221 | time.sleep(1) 222 | 223 | logging.info("正在获取账户信息...") 224 | tab.get(settings_url) 225 | try: 226 | usage_selector = ( 227 | "css:div.col-span-2 > div > div > div > div > " 228 | "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " 229 | "span.font-mono.text-sm\\/\\[0\\.875rem\\]" 230 | ) 231 | usage_ele = tab.ele(usage_selector) 232 | if usage_ele: 233 | usage_info = usage_ele.text 234 | total_usage = usage_info.split("/")[-1].strip() 235 | logging.info(f"账户可用额度上限: {total_usage}") 236 | except Exception as e: 237 | logging.error(f"获取账户额度信息失败: {str(e)}") 238 | 239 | logging.info("\n=== 注册完成 ===") 240 | account_info = f"Cursor 账号信息:\n邮箱: {account}\n密码: {password}" 241 | logging.info(account_info) 242 | time.sleep(5) 243 | return True 244 | 245 | 246 | class EmailGenerator: 247 | def __init__( 248 | self, 249 | password="".join( 250 | random.choices( 251 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", 252 | k=12, 253 | ) 254 | ), 255 | ): 256 | configInstance = Config() 257 | configInstance.print_config() 258 | self.domain = configInstance.get_domain() 259 | self.default_password = password 260 | self.default_first_name = self.generate_random_name() 261 | self.default_last_name = self.generate_random_name() 262 | 263 | def generate_random_name(self, length=6): 264 | """生成随机用户名""" 265 | first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 266 | rest_letters = "".join( 267 | random.choices("abcdefghijklmnopqrstuvwxyz", k=length - 1) 268 | ) 269 | return first_letter + rest_letters 270 | 271 | def generate_email(self, length=8): 272 | """生成随机邮箱地址""" 273 | random_str = "".join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length)) 274 | timestamp = str(int(time.time()))[-6:] # 使用时间戳后6位 275 | return f"{random_str}{timestamp}@{self.domain}" 276 | 277 | def get_account_info(self): 278 | """获取完整的账号信息""" 279 | return { 280 | "email": self.generate_email(), 281 | "password": self.default_password, 282 | "first_name": self.default_first_name, 283 | "last_name": self.default_last_name, 284 | } 285 | 286 | 287 | def get_user_agent(): 288 | """获取user_agent""" 289 | try: 290 | # 使用JavaScript获取user agent 291 | browser_manager = BrowserManager() 292 | browser = browser_manager.init_browser() 293 | user_agent = browser.latest_tab.run_js("return navigator.userAgent") 294 | browser_manager.quit() 295 | return user_agent 296 | except Exception as e: 297 | logging.error(f"获取user agent失败: {str(e)}") 298 | return None 299 | 300 | 301 | def check_cursor_version(): 302 | """检查cursor版本""" 303 | pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths() 304 | with open(pkg_path, "r", encoding="utf-8") as f: 305 | version = json.load(f)["version"] 306 | return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0") 307 | 308 | 309 | def reset_machine_id(greater_than_0_45): 310 | if greater_than_0_45: 311 | # 提示请手动执行脚本 https://github.com/chengazhen/cursor-auto-free/blob/main/patch_cursor_get_machine_id.py 312 | patch_cursor_get_machine_id.patch_cursor_get_machine_id() 313 | else: 314 | MachineIDResetter().reset_machine_ids() 315 | 316 | 317 | if __name__ == "__main__": 318 | print_logo() 319 | greater_than_0_45 = check_cursor_version() 320 | browser_manager = None 321 | try: 322 | logging.info("\n=== 初始化程序 ===") 323 | # 提示用户选择操作模式 324 | print("\n请选择操作模式:") 325 | print("1. 仅重置机器码") 326 | print("2. 完整注册流程") 327 | 328 | while True: 329 | try: 330 | choice = int(input("请输入选项 (1 或 2): ").strip()) 331 | if choice in [1, 2]: 332 | break 333 | else: 334 | print("无效的选项,请重新输入") 335 | except ValueError: 336 | print("请输入有效的数字") 337 | 338 | if choice == 1: 339 | # 仅执行重置机器码 340 | reset_machine_id(greater_than_0_45) 341 | logging.info("机器码重置完成") 342 | sys.exit(0) 343 | 344 | # 小于0.45的版本需要打补丁 345 | if not greater_than_0_45: 346 | ExitCursor() 347 | logging.info("正在初始化浏览器...") 348 | 349 | # 获取user_agent 350 | user_agent = get_user_agent() 351 | if not user_agent: 352 | logging.error("获取user agent失败,使用默认值") 353 | 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" 354 | 355 | # 剔除user_agent中的"HeadlessChrome" 356 | user_agent = user_agent.replace("HeadlessChrome", "Chrome") 357 | 358 | browser_manager = BrowserManager() 359 | browser = browser_manager.init_browser(user_agent) 360 | 361 | # 获取并打印浏览器的user-agent 362 | user_agent = browser.latest_tab.run_js("return navigator.userAgent") 363 | 364 | logging.info("正在初始化邮箱验证模块...") 365 | email_handler = EmailVerificationHandler() 366 | 367 | logging.info("\n=== 配置信息 ===") 368 | login_url = "https://authenticator.cursor.sh" 369 | sign_up_url = "https://authenticator.cursor.sh/sign-up" 370 | settings_url = "https://www.cursor.com/settings" 371 | mail_url = "https://tempmail.plus" 372 | 373 | logging.info("正在生成随机账号信息...") 374 | email_generator = EmailGenerator() 375 | account = email_generator.generate_email() 376 | password = email_generator.default_password 377 | first_name = email_generator.default_first_name 378 | last_name = email_generator.default_last_name 379 | 380 | logging.info(f"生成的邮箱账号: {account}") 381 | auto_update_cursor_auth = True 382 | 383 | tab = browser.latest_tab 384 | 385 | tab.run_js("try { turnstile.reset() } catch(e) { }") 386 | 387 | logging.info("\n=== 开始注册流程 ===") 388 | logging.info(f"正在访问登录页面: {login_url}") 389 | tab.get(login_url) 390 | 391 | if sign_up_account(browser, tab): 392 | logging.info("正在获取会话令牌...") 393 | token = get_cursor_session_token(tab) 394 | if token: 395 | logging.info("更新认证信息...") 396 | update_cursor_auth( 397 | email=account, access_token=token, refresh_token=token 398 | ) 399 | 400 | logging.info("重置机器码...") 401 | reset_machine_id(greater_than_0_45) 402 | logging.info("所有操作已完成") 403 | else: 404 | logging.error("获取会话令牌失败,注册流程未完成") 405 | 406 | except Exception as e: 407 | logging.error(f"程序执行出现错误: {str(e)}") 408 | import traceback 409 | 410 | logging.error(traceback.format_exc()) 411 | finally: 412 | if browser_manager: 413 | browser_manager.quit() 414 | input("\n程序执行完毕,按回车键退出...") 415 | --------------------------------------------------------------------------------