Repository: chengazhen/cursor-auto-free Branch: main Commit: 3b44c1a93bd1 Files: 29 Total size: 359.1 KB Directory structure: gitextract_s73u_9ea/ ├── .github/ │ └── workflows/ │ ├── build.yml │ ├── remove-old-artifacts.yml │ └── temp-build.yml ├── .gitignore ├── README.EN.md ├── README.md ├── browser_utils.py ├── build.bat ├── build.mac.command ├── build.py ├── build.sh ├── config.py ├── cursor_auth_manager.py ├── cursor_pro_keep_alive.py ├── exit_cursor.py ├── get_email_code.py ├── go_cursor_help.py ├── language.py ├── logger.py ├── logo.py ├── names-dataset.txt ├── patch_cursor_get_machine_id.py ├── requirements.txt ├── reset_machine.py ├── test/ │ └── get_veri_code_test.py ├── test_email.py └── turnstilePatch/ ├── manifest.json ├── readme.txt └── script.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build Executables on: push: tags: - 'v*' # 添加标签触发条件,匹配 v1.0.0 这样的标签 jobs: build-windows: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build EXE run: | pyinstaller CursorKeepAlive.spec - name: Upload Windows artifact uses: actions/upload-artifact@v4 with: name: CursorPro-Windows path: dist/CursorPro.exe build-macos-arm64: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build MacOS ARM executable run: | pyinstaller CursorKeepAlive.spec - name: Upload MacOS ARM artifact uses: actions/upload-artifact@v4 with: name: CursorPro-MacOS-ARM64 path: dist/CursorPro build-linux: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build Linux executable run: | pyinstaller CursorKeepAlive.spec - name: Upload Linux artifact uses: actions/upload-artifact@v4 with: name: CursorPro-Linux path: dist/CursorPro build-macos-intel: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | arch -x86_64 pip3 install --upgrade pip arch -x86_64 pip3 install pyinstaller arch -x86_64 pip3 install -r requirements.txt - name: Build MacOS Intel executable env: TARGET_ARCH: 'x86_64' run: | arch -x86_64 python3 -m PyInstaller CursorKeepAlive.spec - name: Upload MacOS Intel artifact uses: actions/upload-artifact@v4 with: name: CursorPro-MacOS-Intel path: dist/CursorPro create-release: needs: [build-windows, build-macos-arm64, build-linux, build-macos-intel] runs-on: ubuntu-22.04 if: startsWith(github.ref, 'refs/tags/') steps: - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts - name: Create release archives run: | cd artifacts zip -r CursorPro-Windows.zip CursorPro-Windows/ zip -r CursorPro-MacOS-ARM64.zip CursorPro-MacOS-ARM64/ zip -r CursorPro-Linux.zip CursorPro-Linux/ zip -r CursorPro-MacOS-Intel.zip CursorPro-MacOS-Intel/ - name: Create Release uses: softprops/action-gh-release@v1 with: files: | artifacts/CursorPro-Windows.zip artifacts/CursorPro-MacOS-ARM64.zip artifacts/CursorPro-Linux.zip artifacts/CursorPro-MacOS-Intel.zip env: GITHUB_TOKEN: ${{ secrets.TOKEN }} ================================================ FILE: .github/workflows/remove-old-artifacts.yml ================================================ name: Remove old artifacts on: schedule: # Every day at 1am - cron: '0 1 * * *' # 手动 workflow_dispatch: jobs: remove-old-artifacts: runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Remove old artifacts uses: c-hive/gha-remove-artifacts@v1 with: GITHUB_TOKEN: ${{ secrets.TOKEN }} age: '5 days' # ' ', e.g. 5 days, 2 years, 90 seconds, parsed by Moment.js # Optional inputs # skip-tags: true # skip-recent: 5 ================================================ FILE: .github/workflows/temp-build.yml ================================================ name: temp Build Executables on: workflow_dispatch: # 手动触发工作流 jobs: build-windows: runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build EXE run: | pyinstaller CursorKeepAlive.spec - name: Upload Windows artifact uses: actions/upload-artifact@v4 with: name: CursorPro-Windows path: dist/CursorPro.exe build-macos-arm64: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build MacOS ARM executable run: | pyinstaller CursorKeepAlive.spec - name: Upload MacOS ARM artifact uses: actions/upload-artifact@v4 with: name: CursorPro-MacOS-ARM64 path: dist/CursorPro build-linux: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install -r requirements.txt - name: Build Linux executable run: | pyinstaller CursorKeepAlive.spec - name: Upload Linux artifact uses: actions/upload-artifact@v4 with: name: CursorPro-Linux path: dist/CursorPro build-macos-intel: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | arch -x86_64 pip3 install --upgrade pip arch -x86_64 pip3 install pyinstaller arch -x86_64 pip3 install -r requirements.txt - name: Build MacOS Intel executable env: TARGET_ARCH: 'x86_64' run: | arch -x86_64 python3 -m PyInstaller CursorKeepAlive.spec - name: Upload MacOS Intel artifact uses: actions/upload-artifact@v4 with: name: CursorPro-MacOS-Intel path: dist/CursorPro ================================================ FILE: .gitignore ================================================ # PyInstaller build/ dist/ *.spec !CursorKeepAlive.mac.spec !CursorKeepAlive.win.spec # Python __pycache__/ *.py[cod] *$py.class # Logs *.log # IDE .vscode/ .idea/ # Mac .DS_Store venv/ node_modules/ .env screenshots/ ================================================ FILE: README.EN.md ================================================ # Cursor Pro Automation Tool User Guide README also available in: [中文](./README.md) ## Online Documentation [cursor-auto-free-doc.vercel.app](https://cursor-auto-free-doc.vercel.app) ## Note Recently, some users have sold this software on platforms like Xianyu. Please avoid such practices—there's no need to earn money this way. ## Sponsor for More Updates ![image](./screen/afdian-[未认证]阿臻.jpg) ## License This project is licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). This means you may: - **Share** — Copy and redistribute the material in any medium or format. But you must comply with the following conditions: - **Non-commercial** — You may not use the material for commercial purposes. ## Features Automated account registration and token refreshing to free your hands. ## Important Notes 1. **Ensure you have Chrome installed. If not, [download here](https://www.google.com/intl/en_pk/chrome/).** 2. **You must log into your account, regardless of its validity. Logged-in is mandatory.** 3. **A stable internet connection is required, preferably via an overseas node. Do not enable global proxy.** ## Configuration Instructions Please refer to our [online documentation](https://cursor-auto-free-doc.vercel.app) for detailed configuration instructions. ## Download [https://github.com/chengazhen/cursor-auto-free/releases](https://github.com/chengazhen/cursor-auto-free/releases) ## Update Log - **2025-01-09**: Added logs and auto-build feature. - **2025-01-10**: Switched to Cloudflare domain email. - **2025-01-11**: Added headless mode and proxy configuration through .env file. - **2025-01-20**: Added IMAP to replace tempmail.plus. ## Special Thanks This project has received support and help from many open source projects and community members. We would like to express our special gratitude to: ### Open Source Projects - [go-cursor-help](https://github.com/yuaotian/go-cursor-help) - An excellent Cursor machine code reset tool with 9.1k Stars. Our machine code reset functionality is implemented using this project, which is one of the most popular Cursor auxiliary tools. 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. ================================================ FILE: README.md ================================================ # Cursor Pro 自动化工具使用说明 [English doc](./README.EN.md) ## 在线文档 [cursor-auto-free-doc.vercel.app](https://cursor-auto-free-doc.vercel.app) ## 公众号 ![公众号](./screen/qrcode_for_gh_c985615b5f2b_258.jpg) ## 许可证声明 本项目采用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 许可证。 这意味着您可以: - 分享 — 在任何媒介以任何形式复制、发行本作品 但必须遵守以下条件: - 非商业性使用 — 您不得将本作品用于商业目的 ## 声明 - 本项目仅供学习交流使用,请勿用于商业用途。 - 本项目不承担任何法律责任,使用本项目造成的任何后果,由使用者自行承担。 ## 特别鸣谢 本项目的开发过程中得到了众多开源项目和社区成员的支持与帮助,在此特别感谢: ## 请我喝杯茶 | buy me a cup of tea ================================================ FILE: browser_utils.py ================================================ from DrissionPage import ChromiumOptions, Chromium import sys import os import logging from dotenv import load_dotenv load_dotenv() class BrowserManager: def __init__(self): self.browser = None def init_browser(self, user_agent=None): """初始化浏览器""" co = self._get_browser_options(user_agent) self.browser = Chromium(co) return self.browser def _get_browser_options(self, user_agent=None): """获取浏览器配置""" co = ChromiumOptions() try: extension_path = self._get_extension_path("turnstilePatch") co.add_extension(extension_path) except FileNotFoundError as e: logging.warning(f"警告: {e}") browser_path = os.getenv("BROWSER_PATH") if browser_path: co.set_paths(browser_path=browser_path) co.set_pref("credentials_enable_service", False) co.set_argument("--hide-crash-restore-bubble") proxy = os.getenv("BROWSER_PROXY") if proxy: co.set_proxy(proxy) co.auto_port() if user_agent: co.set_user_agent(user_agent) co.headless( os.getenv("BROWSER_HEADLESS", "True").lower() == "true" ) # 生产环境使用无头模式 # Mac 系统特殊处理 if sys.platform == "darwin": co.set_argument("--no-sandbox") co.set_argument("--disable-gpu") return co def _get_extension_path(self,exname='turnstilePatch'): """获取插件路径""" root_dir = os.getcwd() extension_path = os.path.join(root_dir, exname) if hasattr(sys, "_MEIPASS"): extension_path = os.path.join(sys._MEIPASS, exname) if not os.path.exists(extension_path): raise FileNotFoundError(f"插件不存在: {extension_path}") return extension_path def quit(self): """关闭浏览器""" if self.browser: try: self.browser.quit() except: pass ================================================ FILE: build.bat ================================================ @echo off set PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage echo Building Cursor Keep Alive... :: Check if virtual environment exists if not exist "venv" ( python -m venv venv if errorlevel 1 ( echo Failed to create virtual environment! exit /b 1 ) ) :: Activate virtual environment and wait for activation to complete call venv\Scripts\activate.bat timeout /t 2 /nobreak > nul :: Install dependencies echo Installing dependencies... python -m pip install --upgrade pip pip install -r requirements.txt :: Run build script echo Starting build process... python build.py :: Deactivate virtual environment deactivate :: Keep window open echo Build completed! pause ================================================ FILE: build.mac.command ================================================ #!/bin/bash export PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage # Get script directory cd "$(dirname "$0")" echo "Creating virtual environment..." # Check if virtual environment exists if [ ! -d "venv" ]; then python3 -m venv venv if [ $? -ne 0 ]; then echo "Failed to create virtual environment!" exit 1 fi fi # Activate virtual environment source venv/bin/activate # Install dependencies echo "Installing dependencies..." python -m pip install --upgrade pip pip install -r requirements.txt # Run build script echo "Starting build process..." python build.py # Keep window open echo "Build completed!" echo "Press any key to exit..." read -n 1 ================================================ FILE: build.py ================================================ import warnings import os import platform import subprocess import time import threading # Ignore specific SyntaxWarning warnings.filterwarnings("ignore", category=SyntaxWarning, module="DrissionPage") CURSOR_LOGO = """ ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ """ class LoadingAnimation: def __init__(self): self.is_running = False self.animation_thread = None def start(self, message="Building"): self.is_running = True self.animation_thread = threading.Thread(target=self._animate, args=(message,)) self.animation_thread.start() def stop(self): self.is_running = False if self.animation_thread: self.animation_thread.join() print("\r" + " " * 70 + "\r", end="", flush=True) # Clear the line def _animate(self, message): animation = "|/-\\" idx = 0 while self.is_running: print(f"\r{message} {animation[idx % len(animation)]}", end="", flush=True) idx += 1 time.sleep(0.1) def print_logo(): print("\033[96m" + CURSOR_LOGO + "\033[0m") print("\033[93m" + "Building Cursor Keep Alive...".center(56) + "\033[0m\n") def progress_bar(progress, total, prefix="", length=50): filled = int(length * progress // total) bar = "█" * filled + "░" * (length - filled) percent = f"{100 * progress / total:.1f}" print(f"\r{prefix} |{bar}| {percent}% Complete", end="", flush=True) if progress == total: print() def simulate_progress(message, duration=1.0, steps=20): print(f"\033[94m{message}\033[0m") for i in range(steps + 1): time.sleep(duration / steps) progress_bar(i, steps, prefix="Progress:", length=40) def filter_output(output): """ImportantMessage""" if not output: return "" important_lines = [] for line in output.split("\n"): # Only keep lines containing specific keywords if any( keyword in line.lower() for keyword in ["error:", "failed:", "completed", "directory:"] ): important_lines.append(line) return "\n".join(important_lines) def build(): # Clear screen os.system("cls" if platform.system().lower() == "windows" else "clear") # Print logo print_logo() system = platform.system().lower() spec_file = os.path.join("CursorKeepAlive.spec") # if system not in ["darwin", "windows"]: # print(f"\033[91mUnsupported operating system: {system}\033[0m") # return output_dir = f"dist/{system if system != 'darwin' else 'mac'}" # Create output directory os.makedirs(output_dir, exist_ok=True) simulate_progress("Creating output directory...", 0.5) # Run PyInstaller with loading animation pyinstaller_command = [ "pyinstaller", spec_file, "--distpath", output_dir, "--workpath", f"build/{system}", "--noconfirm", ] loading = LoadingAnimation() try: simulate_progress("Running PyInstaller...", 2.0) loading.start("Building in progress") result = subprocess.run( pyinstaller_command, check=True, capture_output=True, text=True ) loading.stop() if result.stderr: filtered_errors = [ line for line in result.stderr.split("\n") if any( keyword in line.lower() for keyword in ["error:", "failed:", "completed", "directory:"] ) ] if filtered_errors: print("\033[93mBuild Warnings/Errors:\033[0m") print("\n".join(filtered_errors)) except subprocess.CalledProcessError as e: loading.stop() print(f"\033[91mBuild failed with error code {e.returncode}\033[0m") if e.stderr: print("\033[91mError Details:\033[0m") print(e.stderr) return except FileNotFoundError: loading.stop() print( "\033[91mError: Please ensure PyInstaller is installed (pip install pyinstaller)\033[0m" ) return except KeyboardInterrupt: loading.stop() print("\n\033[91mBuild cancelled by user\033[0m") return finally: loading.stop() # Copy config file if os.path.exists("config.ini.example"): simulate_progress("Copying configuration file...", 0.5) if system == "windows": subprocess.run( ["copy", "config.ini.example", f"{output_dir}\\config.ini"], shell=True ) else: subprocess.run(["cp", "config.ini.example", f"{output_dir}/config.ini"]) # Copy .env.example file if os.path.exists(".env.example"): simulate_progress("Copying environment file...", 0.5) if system == "windows": subprocess.run(["copy", ".env.example", f"{output_dir}\\.env"], shell=True) else: subprocess.run(["cp", ".env.example", f"{output_dir}/.env"]) print( f"\n\033[92mBuild completed successfully! Output directory: {output_dir}\033[0m" ) if __name__ == "__main__": build() ================================================ FILE: build.sh ================================================ #!/bin/bash export PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage echo "Creating virtual environment..." # Check if virtual environment exists if [ ! -d "venv" ]; then python3 -m venv venv if [ $? -ne 0 ]; then echo "Failed to create virtual environment!" exit 1 fi fi # Activate virtual environment source venv/bin/activate # Install dependencies echo "Installing dependencies..." python -m pip install --upgrade pip pip install -r requirements.txt # Run build script echo "Starting build process..." python build.py # Complete echo "Build completed!" ================================================ FILE: config.py ================================================ from dotenv import load_dotenv import os import sys from logger import logging from language import get_translation class Config: def __init__(self): # 获取应用程序的根目录路径 if getattr(sys, "frozen", False): # 如果是打包后的可执行文件 application_path = os.path.dirname(sys.executable) else: # 如果是开发环境 application_path = os.path.dirname(os.path.abspath(__file__)) # 指定 .env 文件的路径 dotenv_path = os.path.join(application_path, ".env") if not os.path.exists(dotenv_path): raise FileNotFoundError(get_translation("file_not_exists", path=dotenv_path)) # 加载 .env 文件 load_dotenv(dotenv_path) self.imap = False self.temp_mail = os.getenv("TEMP_MAIL", "").strip().split("@")[0] self.temp_mail_epin = os.getenv("TEMP_MAIL_EPIN", "").strip() self.temp_mail_ext = os.getenv("TEMP_MAIL_EXT", "").strip() self.domain = os.getenv("DOMAIN", "").strip() # 如果临时邮箱为null则加载IMAP if self.temp_mail == "null": self.imap = True self.imap_server = os.getenv("IMAP_SERVER", "").strip() self.imap_port = os.getenv("IMAP_PORT", "").strip() self.imap_user = os.getenv("IMAP_USER", "").strip() self.imap_pass = os.getenv("IMAP_PASS", "").strip() self.imap_dir = os.getenv("IMAP_DIR", "inbox").strip() self.check_config() def get_temp_mail(self): return self.temp_mail def get_temp_mail_epin(self): return self.temp_mail_epin def get_temp_mail_ext(self): return self.temp_mail_ext def get_imap(self): if not self.imap: return False return { "imap_server": self.imap_server, "imap_port": self.imap_port, "imap_user": self.imap_user, "imap_pass": self.imap_pass, "imap_dir": self.imap_dir, } def get_domain(self): return self.domain def get_protocol(self): """获取邮件协议类型 Returns: str: 'IMAP' 或 'POP3' """ return os.getenv('IMAP_PROTOCOL', 'POP3') def check_config(self): """检查配置项是否有效 检查规则: 1. 如果使用 tempmail.plus,需要配置 TEMP_MAIL 和 DOMAIN 2. 如果使用 IMAP,需要配置 IMAP_SERVER、IMAP_PORT、IMAP_USER、IMAP_PASS 3. IMAP_DIR 是可选的 """ # 基础配置检查 required_configs = { "domain": "domain_not_configured", } # 检查基础配置 for key, error_key in required_configs.items(): if not self.check_is_valid(getattr(self, key)): raise ValueError(get_translation(error_key)) # 检查邮箱配置 if self.temp_mail != "null": # tempmail.plus 模式 if not self.check_is_valid(self.temp_mail): raise ValueError(get_translation("temp_mail_not_configured")) else: # IMAP 模式 imap_configs = { "imap_server": "imap_server_not_configured", "imap_port": "imap_port_not_configured", "imap_user": "imap_user_not_configured", "imap_pass": "imap_pass_not_configured", } for key, error_key in imap_configs.items(): value = getattr(self, key) if value == "null" or not self.check_is_valid(value): raise ValueError(get_translation(error_key)) # IMAP_DIR 是可选的,如果设置了就检查其有效性 if self.imap_dir != "null" and not self.check_is_valid(self.imap_dir): raise ValueError(get_translation("imap_dir_invalid")) def check_is_valid(self, value): """检查配置项是否有效 Args: value: 配置项的值 Returns: bool: 配置项是否有效 """ return isinstance(value, str) and len(str(value).strip()) > 0 def print_config(self): if self.imap: logging.info(get_translation("imap_server", server=self.imap_server)) logging.info(get_translation("imap_port", port=self.imap_port)) logging.info(get_translation("imap_username", username=self.imap_user)) logging.info(get_translation("imap_password", password='*' * len(self.imap_pass))) logging.info(get_translation("imap_inbox_dir", dir=self.imap_dir)) if self.temp_mail != "null": logging.info(get_translation("temp_mail", mail=f"{self.temp_mail}{self.temp_mail_ext}")) logging.info(get_translation("domain", domain=self.domain)) # 使用示例 if __name__ == "__main__": try: config = Config() print(get_translation("env_variables_loaded")) config.print_config() except ValueError as e: print(get_translation("error_prefix", error=e)) ================================================ FILE: cursor_auth_manager.py ================================================ import sqlite3 import os import sys class CursorAuthManager: """Cursor认证信息管理器""" def __init__(self): # 判断操作系统 if sys.platform == "win32": # Windows appdata = os.getenv("APPDATA") if appdata is None: raise EnvironmentError("APPDATA 环境变量未设置") self.db_path = os.path.join( appdata, "Cursor", "User", "globalStorage", "state.vscdb" ) elif sys.platform == "darwin": # macOS self.db_path = os.path.abspath(os.path.expanduser( "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" )) elif sys.platform == "linux" : # Linux 和其他类Unix系统 self.db_path = os.path.abspath(os.path.expanduser( "~/.config/Cursor/User/globalStorage/state.vscdb" )) else: raise NotImplementedError(f"不支持的操作系统: {sys.platform}") def update_auth(self, email=None, access_token=None, refresh_token=None): """ 更新Cursor的认证信息 :param email: 新的邮箱地址 :param access_token: 新的访问令牌 :param refresh_token: 新的刷新令牌 :return: bool 是否成功更新 """ 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)) if not updates: print("没有提供任何要更新的值") return False conn = None try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() for key, value in updates: # 如果没有更新任何行,说明key不存在,执行插入 # 检查 accessToken 是否存在 check_query = f"SELECT COUNT(*) FROM itemTable WHERE key = ?" cursor.execute(check_query, (key,)) if cursor.fetchone()[0] == 0: insert_query = "INSERT INTO itemTable (key, value) VALUES (?, ?)" cursor.execute(insert_query, (key, value)) else: update_query = "UPDATE itemTable SET value = ? WHERE key = ?" cursor.execute(update_query, (value, key)) if cursor.rowcount > 0: print(f"成功更新 {key.split('/')[-1]}") else: print(f"未找到 {key.split('/')[-1]} 或值未变化") conn.commit() return True except sqlite3.Error as e: print("数据库错误:", str(e)) return False except Exception as e: print("发生错误:", str(e)) return False finally: if conn: conn.close() ================================================ FILE: cursor_pro_keep_alive.py ================================================ import os import platform import json import sys from colorama import Fore, Style from enum import Enum from typing import Optional from exit_cursor import ExitCursor import go_cursor_help import patch_cursor_get_machine_id from reset_machine import MachineIDResetter from language import language, get_translation os.environ["PYTHONVERBOSE"] = "0" os.environ["PYINSTALLER_VERBOSE"] = "0" import time import random from cursor_auth_manager import CursorAuthManager import os from logger import logging from browser_utils import BrowserManager from get_email_code import EmailVerificationHandler from logo import print_logo from config import Config from datetime import datetime # Define EMOJI dictionary EMOJI = {"ERROR": get_translation("error"), "WARNING": get_translation("warning"), "INFO": get_translation("info")} class VerificationStatus(Enum): """Verification status enum""" PASSWORD_PAGE = "@name=password" CAPTCHA_PAGE = "@data-index=0" ACCOUNT_SETTINGS = "Account Settings" class TurnstileError(Exception): """Turnstile verification related exception""" pass def save_screenshot(tab, stage: str, timestamp: bool = True) -> None: """ Save a screenshot of the page Args: tab: Browser tab object stage: Stage identifier for the screenshot timestamp: Whether to add a timestamp """ try: # Create screenshots directory screenshot_dir = "screenshots" if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) # Generate filename if timestamp: filename = f"turnstile_{stage}_{int(time.time())}.png" else: filename = f"turnstile_{stage}.png" filepath = os.path.join(screenshot_dir, filename) # Save screenshot tab.get_screenshot(filepath) logging.debug(f"Screenshot saved: {filepath}") except Exception as e: logging.warning(f"Failed to save screenshot: {str(e)}") def check_verification_success(tab) -> Optional[VerificationStatus]: """ Check if verification was successful Returns: VerificationStatus: The corresponding status if successful, None if failed """ for status in VerificationStatus: if tab.ele(status.value): logging.info(get_translation("verification_success", status=status.name)) return status return None def handle_turnstile(tab, max_retries: int = 2, retry_interval: tuple = (1, 2)) -> bool: """ Handle Turnstile verification Args: tab: Browser tab object max_retries: Maximum number of retries retry_interval: Retry interval range (min, max) Returns: bool: Whether verification was successful Raises: TurnstileError: Exception during verification process """ logging.info(get_translation("detecting_turnstile")) save_screenshot(tab, "start") retry_count = 0 try: while retry_count < max_retries: retry_count += 1 logging.debug(get_translation("retry_verification", count=retry_count)) try: # Locate verification frame element challenge_check = ( tab.ele("@id=cf-turnstile", timeout=2) .child() .shadow_root.ele("tag:iframe") .ele("tag:body") .sr("tag:input") ) if challenge_check: logging.info(get_translation("detected_turnstile")) # Random delay before clicking verification time.sleep(random.uniform(1, 3)) challenge_check.click() time.sleep(2) # Save screenshot after verification save_screenshot(tab, "clicked") # Check verification result if check_verification_success(tab): logging.info(get_translation("turnstile_verification_passed")) save_screenshot(tab, "success") return True except Exception as e: logging.debug(f"Current attempt unsuccessful: {str(e)}") # Check if already verified if check_verification_success(tab): return True # Random delay before next attempt time.sleep(random.uniform(*retry_interval)) # Exceeded maximum retries logging.error(get_translation("verification_failed_max_retries", max_retries=max_retries)) logging.error( "Please visit the open source project for more information: https://github.com/chengazhen/cursor-auto-free" ) save_screenshot(tab, "failed") return False except Exception as e: error_msg = get_translation("turnstile_exception", error=str(e)) logging.error(error_msg) save_screenshot(tab, "error") raise TurnstileError(error_msg) def get_cursor_session_token(tab, max_attempts=3, retry_interval=2): """ Get Cursor session token with retry mechanism :param tab: Browser tab :param max_attempts: Maximum number of attempts :param retry_interval: Retry interval (seconds) :return: Session token or None """ logging.info(get_translation("getting_cookie")) attempts = 0 while attempts < max_attempts: try: cookies = tab.cookies() for cookie in cookies: if cookie.get("name") == "WorkosCursorSessionToken": return cookie["value"].split("%3A%3A")[1] attempts += 1 if attempts < max_attempts: logging.warning( get_translation("cookie_attempt_failed", attempts=attempts, retry_interval=retry_interval) ) time.sleep(retry_interval) else: logging.error( get_translation("cookie_max_attempts", max_attempts=max_attempts) ) except Exception as e: logging.error(get_translation("cookie_failure", error=str(e))) attempts += 1 if attempts < max_attempts: logging.info(get_translation("retry_in_seconds", seconds=retry_interval)) time.sleep(retry_interval) return None def update_cursor_auth(email=None, access_token=None, refresh_token=None): """ Update Cursor authentication information """ auth_manager = CursorAuthManager() return auth_manager.update_auth(email, access_token, refresh_token) def sign_up_account(browser, tab): logging.info(get_translation("start_account_registration")) logging.info(get_translation("visiting_registration_page", url=sign_up_url)) tab.get(sign_up_url) try: if tab.ele("@name=first_name"): logging.info(get_translation("filling_personal_info")) tab.actions.click("@name=first_name").input(first_name) logging.info(get_translation("input_first_name", name=first_name)) time.sleep(random.uniform(1, 3)) tab.actions.click("@name=last_name").input(last_name) logging.info(get_translation("input_last_name", name=last_name)) time.sleep(random.uniform(1, 3)) tab.actions.click("@name=email").input(account) logging.info(get_translation("input_email", email=account)) time.sleep(random.uniform(1, 3)) logging.info(get_translation("submitting_personal_info")) tab.actions.click("@type=submit") except Exception as e: logging.error(get_translation("registration_page_access_failed", error=str(e))) return False handle_turnstile(tab) try: if tab.ele("@name=password"): logging.info(get_translation("setting_password")) tab.ele("@name=password").input(password) time.sleep(random.uniform(1, 3)) logging.info(get_translation("submitting_password")) tab.ele("@type=submit").click() logging.info(get_translation("password_setup_complete")) except Exception as e: logging.error(get_translation("password_setup_failed", error=str(e))) return False if tab.ele("This email is not available."): logging.error(get_translation("registration_failed_email_used")) return False handle_turnstile(tab) while True: try: if tab.ele("Account Settings"): logging.info(get_translation("registration_success")) break if tab.ele("@data-index=0"): logging.info(get_translation("getting_email_verification")) code = email_handler.get_verification_code() if not code: logging.error(get_translation("verification_code_failure")) return False logging.info(get_translation("verification_code_success", code=code)) logging.info(get_translation("inputting_verification_code")) i = 0 for digit in code: tab.ele(f"@data-index={i}").input(digit) time.sleep(random.uniform(0.1, 0.3)) i += 1 logging.info(get_translation("verification_code_input_complete")) break except Exception as e: logging.error(get_translation("verification_code_process_error", error=str(e))) handle_turnstile(tab) wait_time = random.randint(3, 6) for i in range(wait_time): logging.info(get_translation("waiting_system_processing", seconds=wait_time-i)) time.sleep(1) logging.info(get_translation("getting_account_info")) tab.get(settings_url) try: usage_selector = ( "css:div.col-span-2 > div > div > div > div > " "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " "span.font-mono.text-sm\\/\\[0\\.875rem\\]" ) usage_ele = tab.ele(usage_selector) if usage_ele: usage_info = usage_ele.text total_usage = usage_info.split("/")[-1].strip() logging.info(get_translation("account_usage_limit", limit=total_usage)) logging.info( "Please visit the open source project for more information: https://github.com/chengazhen/cursor-auto-free" ) except Exception as e: logging.error(get_translation("account_usage_info_failure", error=str(e))) logging.info(get_translation("registration_complete")) account_info = get_translation("cursor_account_info", email=account, password=password) logging.info(account_info) time.sleep(5) return True class EmailGenerator: def __init__( self, password="".join( random.choices( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", k=12, ) ), ): configInstance = Config() configInstance.print_config() self.domain = configInstance.get_domain() self.names = self.load_names() self.default_password = password self.default_first_name = self.generate_random_name() self.default_last_name = self.generate_random_name() def load_names(self): try: with open("names-dataset.txt", "r") as file: return file.read().split() except FileNotFoundError: logging.warning(get_translation("names_file_not_found")) # Fallback to a small set of default names if the file is not found return ["John", "Jane", "Alex", "Emma", "Michael", "Olivia", "William", "Sophia", "James", "Isabella", "Robert", "Mia", "David", "Charlotte", "Joseph", "Amelia"] def generate_random_name(self): """Generate a random username""" return random.choice(self.names) def generate_email(self, length=4): """Generate a random email address""" length = random.randint(0, length) # Generate a random int between 0 and length timestamp = str(int(time.time()))[-length:] # Use the last length digits of timestamp return f"{self.default_first_name}{timestamp}@{self.domain}" def get_account_info(self): """Get complete account information""" return { "email": self.generate_email(), "password": self.default_password, "first_name": self.default_first_name, "last_name": self.default_last_name, } def get_user_agent(): """Get user_agent""" try: # Use JavaScript to get user agent browser_manager = BrowserManager() browser = browser_manager.init_browser() user_agent = browser.latest_tab.run_js("return navigator.userAgent") browser_manager.quit() return user_agent except Exception as e: logging.error(f"Failed to get user agent: {str(e)}") return None def check_cursor_version(): """Check cursor version""" pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths() with open(pkg_path, "r", encoding="utf-8") as f: version = json.load(f)["version"] return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0") def reset_machine_id(greater_than_0_45): if greater_than_0_45: # Prompt to manually execute script https://github.com/chengazhen/cursor-auto-free/blob/main/patch_cursor_get_machine_id.py go_cursor_help.go_cursor_help() else: MachineIDResetter().reset_machine_ids() def print_end_message(): logging.info("\n\n\n\n\n") logging.info("=" * 30) logging.info(get_translation("all_operations_completed")) logging.info("\n=== Get More Information ===") logging.info("📺 Bilibili UP: 想回家的前端") logging.info("🔥 WeChat Official Account: code 未来") logging.info("=" * 30) logging.info( "Please visit the open source project for more information: https://github.com/chengazhen/cursor-auto-free" ) if __name__ == "__main__": print_logo() # Add language selection print("\n") language.select_language_prompt() greater_than_0_45 = check_cursor_version() browser_manager = None try: logging.info(get_translation("initializing_program")) ExitCursor() # Prompt user to select operation mode print(get_translation("select_operation_mode")) print(get_translation("reset_machine_code_only")) print(get_translation("complete_registration")) while True: try: choice = int(input(get_translation("enter_option")).strip()) if choice in [1, 2]: break else: print(get_translation("invalid_option")) except ValueError: print(get_translation("enter_valid_number")) if choice == 1: # Only reset machine code reset_machine_id(greater_than_0_45) logging.info(get_translation("machine_code_reset_complete")) print_end_message() sys.exit(0) logging.info(get_translation("initializing_browser")) # Get user_agent user_agent = get_user_agent() if not user_agent: logging.error(get_translation("get_user_agent_failed")) 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" # Remove "HeadlessChrome" from user_agent user_agent = user_agent.replace("HeadlessChrome", "Chrome") browser_manager = BrowserManager() browser = browser_manager.init_browser(user_agent) # Get and print browser's user-agent user_agent = browser.latest_tab.run_js("return navigator.userAgent") logging.info( "Please visit the open source project for more information: https://github.com/chengazhen/cursor-auto-free" ) logging.info(get_translation("configuration_info")) login_url = "https://authenticator.cursor.sh" sign_up_url = "https://authenticator.cursor.sh/sign-up" settings_url = "https://www.cursor.com/settings" mail_url = "https://tempmail.plus" logging.info(get_translation("generating_random_account")) email_generator = EmailGenerator() first_name = email_generator.default_first_name last_name = email_generator.default_last_name account = email_generator.generate_email() password = email_generator.default_password logging.info(get_translation("generated_email_account", email=account)) logging.info(get_translation("initializing_email_verification")) email_handler = EmailVerificationHandler(account) auto_update_cursor_auth = True tab = browser.latest_tab tab.run_js("try { turnstile.reset() } catch(e) { }") logging.info(get_translation("starting_registration")) logging.info(get_translation("visiting_login_page", url=login_url)) tab.get(login_url) if sign_up_account(browser, tab): logging.info(get_translation("getting_session_token")) token = get_cursor_session_token(tab) if token: logging.info(get_translation("updating_auth_info")) update_cursor_auth( email=account, access_token=token, refresh_token=token ) logging.info( "Please visit the open source project for more information: https://github.com/chengazhen/cursor-auto-free" ) logging.info(get_translation("resetting_machine_code")) reset_machine_id(greater_than_0_45) logging.info(get_translation("all_operations_completed")) print_end_message() else: logging.error(get_translation("session_token_failed")) except Exception as e: logging.error(get_translation("program_error", error=str(e))) finally: if browser_manager: browser_manager.quit() input(get_translation("program_exit_message")) ================================================ FILE: exit_cursor.py ================================================ import psutil from logger import logging import time def ExitCursor(timeout=5): """ 温和地关闭 Cursor 进程 Args: timeout (int): 等待进程自然终止的超时时间(秒) Returns: bool: 是否成功关闭所有进程 """ try: logging.info("开始退出Cursor...") cursor_processes = [] # 收集所有 Cursor 进程 for proc in psutil.process_iter(['pid', 'name']): try: if proc.info['name'].lower() in ['cursor.exe', 'cursor']: cursor_processes.append(proc) except (psutil.NoSuchProcess, psutil.AccessDenied): continue if not cursor_processes: logging.info("未发现运行中的 Cursor 进程") return True # 温和地请求进程终止 for proc in cursor_processes: try: if proc.is_running(): proc.terminate() # 发送终止信号 except (psutil.NoSuchProcess, psutil.AccessDenied): continue # 等待进程自然终止 start_time = time.time() while time.time() - start_time < timeout: still_running = [] for proc in cursor_processes: try: if proc.is_running(): still_running.append(proc) except (psutil.NoSuchProcess, psutil.AccessDenied): continue if not still_running: logging.info("所有 Cursor 进程已正常关闭") return True # 等待一小段时间再检查 time.sleep(0.5) # 如果超时后仍有进程在运行 if still_running: process_list = ", ".join([str(p.pid) for p in still_running]) logging.warning(f"以下进程未能在规定时间内关闭: {process_list}") return False return True except Exception as e: logging.error(f"关闭 Cursor 进程时发生错误: {str(e)}") return False if __name__ == "__main__": ExitCursor() ================================================ FILE: get_email_code.py ================================================ from datetime import datetime import logging import time import re from config import Config import requests import email import imaplib import poplib from email.parser import Parser class EmailVerificationHandler: def __init__(self,account): self.imap = Config().get_imap() self.username = Config().get_temp_mail() self.epin = Config().get_temp_mail_epin() self.session = requests.Session() self.emailExtension = Config().get_temp_mail_ext() # 获取协议类型,默认为 POP3 self.protocol = Config().get_protocol() or 'POP3' self.account = account def get_verification_code(self, max_retries=5, retry_interval=60): """ 获取验证码,带有重试机制。 Args: max_retries: 最大重试次数。 retry_interval: 重试间隔时间(秒)。 Returns: 验证码 (字符串或 None)。 """ for attempt in range(max_retries): try: logging.info(f"尝试获取验证码 (第 {attempt + 1}/{max_retries} 次)...") if not self.imap: verify_code, first_id = self._get_latest_mail_code() if verify_code is not None and first_id is not None: self._cleanup_mail(first_id) return verify_code else: if self.protocol.upper() == 'IMAP': verify_code = self._get_mail_code_by_imap() else: verify_code = self._get_mail_code_by_pop3() if verify_code is not None: return verify_code if attempt < max_retries - 1: # 除了最后一次尝试,都等待 logging.warning(f"未获取到验证码,{retry_interval} 秒后重试...") time.sleep(retry_interval) except Exception as e: logging.error(f"获取验证码失败: {e}") # 记录更一般的异常 if attempt < max_retries - 1: logging.error(f"发生错误,{retry_interval} 秒后重试...") time.sleep(retry_interval) else: raise Exception(f"获取验证码失败且已达最大重试次数: {e}") from e raise Exception(f"经过 {max_retries} 次尝试后仍未获取到验证码。") # 使用imap获取邮件 def _get_mail_code_by_imap(self, retry = 0): if retry > 0: time.sleep(3) if retry >= 20: raise Exception("获取验证码超时") try: # 连接到IMAP服务器 mail = imaplib.IMAP4_SSL(self.imap['imap_server'], self.imap['imap_port']) mail.login(self.imap['imap_user'], self.imap['imap_pass']) search_by_date=False # 针对网易系邮箱,imap登录后需要附带联系信息,且后续邮件搜索逻辑更改为获取当天的未读邮件 if self.imap['imap_user'].endswith(('@163.com', '@126.com', '@yeah.net')): imap_id = ("name", self.imap['imap_user'].split('@')[0], "contact", self.imap['imap_user'], "version", "1.0.0", "vendor", "imaplib") mail.xatom('ID', '("' + '" "'.join(imap_id) + '")') search_by_date=True mail.select(self.imap['imap_dir']) if search_by_date: date = datetime.now().strftime("%d-%b-%Y") status, messages = mail.search(None, f'ON {date} UNSEEN') else: status, messages = mail.search(None, 'TO', '"'+self.account+'"') if status != 'OK': return None mail_ids = messages[0].split() if not mail_ids: # 没有获取到,就在获取一次 return self._get_mail_code_by_imap(retry=retry + 1) for mail_id in reversed(mail_ids): status, msg_data = mail.fetch(mail_id, '(RFC822)') if status != 'OK': continue raw_email = msg_data[0][1] email_message = email.message_from_bytes(raw_email) # 如果是按日期搜索的邮件,需要进一步核对收件人地址是否对应 if search_by_date and email_message['to'] !=self.account: continue body = self._extract_imap_body(email_message) if body: # 避免 6 位数字的域名被误识别成验证码 body = body.replace(self.account, '') code_match = re.search(r"\b\d{6}\b", body) if code_match: code = code_match.group() # 删除找到验证码的邮件 mail.store(mail_id, '+FLAGS', '\\Deleted') mail.expunge() mail.logout() return code # print("未找到验证码") mail.logout() return None except Exception as e: print(f"发生错误: {e}") return None def _extract_imap_body(self, email_message): # 提取邮件正文 if email_message.is_multipart(): for part in email_message.walk(): content_type = part.get_content_type() content_disposition = str(part.get("Content-Disposition")) if content_type == "text/plain" and "attachment" not in content_disposition: charset = part.get_content_charset() or 'utf-8' try: body = part.get_payload(decode=True).decode(charset, errors='ignore') return body except Exception as e: logging.error(f"解码邮件正文失败: {e}") else: content_type = email_message.get_content_type() if content_type == "text/plain": charset = email_message.get_content_charset() or 'utf-8' try: body = email_message.get_payload(decode=True).decode(charset, errors='ignore') return body except Exception as e: logging.error(f"解码邮件正文失败: {e}") return "" # 使用 POP3 获取邮件 def _get_mail_code_by_pop3(self, retry = 0): if retry > 0: time.sleep(3) if retry >= 20: raise Exception("获取验证码超时") pop3 = None try: # 连接到服务器 pop3 = poplib.POP3_SSL(self.imap['imap_server'], int(self.imap['imap_port'])) pop3.user(self.imap['imap_user']) pop3.pass_(self.imap['imap_pass']) # 获取最新的10封邮件 num_messages = len(pop3.list()[1]) for i in range(num_messages, max(1, num_messages-9), -1): response, lines, octets = pop3.retr(i) msg_content = b'\r\n'.join(lines).decode('utf-8') msg = Parser().parsestr(msg_content) # 检查发件人 if 'no-reply@cursor.sh' in msg.get('From', ''): # 提取邮件正文 body = self._extract_pop3_body(msg) if body: # 查找验证码 code_match = re.search(r"\b\d{6}\b", body) if code_match: code = code_match.group() pop3.quit() return code pop3.quit() return self._get_mail_code_by_pop3(retry=retry + 1) except Exception as e: print(f"发生错误: {e}") if pop3: try: pop3.quit() except: pass return None def _extract_pop3_body(self, email_message): # 提取邮件正文 if email_message.is_multipart(): for part in email_message.walk(): content_type = part.get_content_type() content_disposition = str(part.get("Content-Disposition")) if content_type == "text/plain" and "attachment" not in content_disposition: try: body = part.get_payload(decode=True).decode('utf-8', errors='ignore') return body except Exception as e: logging.error(f"解码邮件正文失败: {e}") else: try: body = email_message.get_payload(decode=True).decode('utf-8', errors='ignore') return body except Exception as e: logging.error(f"解码邮件正文失败: {e}") return "" # 手动输入验证码 def _get_latest_mail_code(self): # 获取邮件列表 mail_list_url = f"https://tempmail.plus/api/mails?email={self.username}{self.emailExtension}&limit=20&epin={self.epin}" mail_list_response = self.session.get(mail_list_url) mail_list_data = mail_list_response.json() time.sleep(0.5) if not mail_list_data.get("result"): return None, None # 获取最新邮件的ID first_id = mail_list_data.get("first_id") if not first_id: return None, None # 获取具体邮件内容 mail_detail_url = f"https://tempmail.plus/api/mails/{first_id}?email={self.username}{self.emailExtension}&epin={self.epin}" mail_detail_response = self.session.get(mail_detail_url) mail_detail_data = mail_detail_response.json() time.sleep(0.5) if not mail_detail_data.get("result"): return None, None # 从邮件文本中提取6位数字验证码 mail_text = mail_detail_data.get("text", "") mail_subject = mail_detail_data.get("subject", "") logging.info(f"找到邮件主题: {mail_subject}") # 修改正则表达式,确保 6 位数字不紧跟在字母或域名相关符号后面 code_match = re.search(r"(? logging.Logger: """配置并返回logger实例""" logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) handler = logging.StreamHandler() formatter = logging.Formatter( "%(asctime)s - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) handler.setFormatter(formatter) logger.addHandler(handler) return logger logger = setup_logging() def get_cursor_paths() -> Tuple[str, str]: """ 根据不同操作系统获取 Cursor 相关路径 Returns: Tuple[str, str]: (package.json路径, main.js路径)的元组 Raises: OSError: 当找不到有效路径或系统不支持时抛出 """ system = platform.system() paths_map = { "Darwin": { "base": "/Applications/Cursor.app/Contents/Resources/app", "package": "package.json", "main": "out/main.js", }, "Windows": { "base": os.path.join( os.getenv("USERAPPPATH") or os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app") ), "package": "package.json", "main": "out/main.js", }, "Linux": { "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], "package": "package.json", "main": "out/main.js", }, } if system not in paths_map: raise OSError(f"不支持的操作系统: {system}") if system == "Linux": for base in paths_map["Linux"]["bases"]: pkg_path = os.path.join(base, paths_map["Linux"]["package"]) if os.path.exists(pkg_path): return (pkg_path, os.path.join(base, paths_map["Linux"]["main"])) raise OSError("在 Linux 系统上未找到 Cursor 安装路径") base_path = paths_map[system]["base"] # 判断Windows是否存在这个文件夹,如果不存在,提示需要创建软连接后重试 if system == "Windows": if not os.path.exists(base_path): logging.info('可能您的Cursor不是默认安装路径,请创建软连接,命令如下:') logging.info('cmd /c mklink /d "C:\\Users\\\\AppData\\Local\\Programs\\Cursor" "默认安装路径"') logging.info('例如:') logging.info('cmd /c mklink /d "C:\\Users\\\\AppData\\Local\\Programs\\Cursor" "D:\\SoftWare\\cursor"') input("\n程序执行完毕,按回车键退出...") return ( os.path.join(base_path, paths_map[system]["package"]), os.path.join(base_path, paths_map[system]["main"]), ) def check_system_requirements(pkg_path: str, main_path: str) -> bool: """ 检查系统要求 Args: pkg_path: package.json 文件路径 main_path: main.js 文件路径 Returns: bool: 检查是否通过 """ for file_path in [pkg_path, main_path]: if not os.path.isfile(file_path): logger.error(f"文件不存在: {file_path}") return False if not os.access(file_path, os.W_OK): logger.error(f"没有文件写入权限: {file_path}") return False return True def version_check(version: str, min_version: str = "", max_version: str = "") -> bool: """ 版本号检查 Args: version: 当前版本号 min_version: 最小版本号要求 max_version: 最大版本号要求 Returns: bool: 版本号是否符合要求 """ version_pattern = r"^\d+\.\d+\.\d+$" try: if not re.match(version_pattern, version): logger.error(f"无效的版本号格式: {version}") return False def parse_version(ver: str) -> Tuple[int, ...]: return tuple(map(int, ver.split("."))) current = parse_version(version) if min_version and current < parse_version(min_version): logger.error(f"版本号 {version} 小于最小要求 {min_version}") return False if max_version and current > parse_version(max_version): logger.error(f"版本号 {version} 大于最大要求 {max_version}") return False return True except Exception as e: logger.error(f"版本检查失败: {str(e)}") return False def modify_main_js(main_path: str) -> bool: """ 修改 main.js 文件 Args: main_path: main.js 文件路径 Returns: bool: 修改是否成功 """ try: # 获取原始文件的权限和所有者信息 original_stat = os.stat(main_path) original_mode = original_stat.st_mode original_uid = original_stat.st_uid original_gid = original_stat.st_gid with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: with open(main_path, "r", encoding="utf-8") as main_file: content = main_file.read() # 执行替换 patterns = { r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}", r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}", } for pattern, replacement in patterns.items(): content = re.sub(pattern, replacement, content) tmp_file.write(content) tmp_path = tmp_file.name # 使用 shutil.copy2 保留文件权限 shutil.copy2(main_path, main_path + ".old") shutil.move(tmp_path, main_path) # 恢复原始文件的权限和所有者 os.chmod(main_path, original_mode) if os.name != "nt": # 在非Windows系统上设置所有者 os.chown(main_path, original_uid, original_gid) logger.info("文件修改成功") return True except Exception as e: logger.error(f"修改文件时发生错误: {str(e)}") if "tmp_path" in locals(): os.unlink(tmp_path) return False def backup_files(pkg_path: str, main_path: str) -> bool: """ 备份原始文件 Args: pkg_path: package.json 文件路径(未使用) main_path: main.js 文件路径 Returns: bool: 备份是否成功 """ try: # 只备份 main.js if os.path.exists(main_path): backup_main = f"{main_path}.bak" shutil.copy2(main_path, backup_main) logger.info(f"已备份 main.js: {backup_main}") return True except Exception as e: logger.error(f"备份文件失败: {str(e)}") return False def restore_backup_files(pkg_path: str, main_path: str) -> bool: """ 恢复备份文件 Args: pkg_path: package.json 文件路径(未使用) main_path: main.js 文件路径 Returns: bool: 恢复是否成功 """ try: # 只恢复 main.js backup_main = f"{main_path}.bak" if os.path.exists(backup_main): shutil.copy2(backup_main, main_path) logger.info(f"已恢复 main.js") return True logger.error("未找到备份文件") return False except Exception as e: logger.error(f"恢复备份失败: {str(e)}") return False def patch_cursor_get_machine_id(restore_mode=False) -> None: """ 主函数 Args: restore_mode: 是否为恢复模式 """ logger.info("开始执行脚本...") try: # 获取路径 pkg_path, main_path = get_cursor_paths() # 检查系统要求 if not check_system_requirements(pkg_path, main_path): sys.exit(1) if restore_mode: # 恢复备份 if restore_backup_files(pkg_path, main_path): logger.info("备份恢复完成") else: logger.error("备份恢复失败") return # 获取版本号 try: with open(pkg_path, "r", encoding="utf-8") as f: version = json.load(f)["version"] logger.info(f"当前 Cursor 版本: {version}") except Exception as e: logger.error(f"无法读取版本号: {str(e)}") sys.exit(1) # 检查版本 if not version_check(version, min_version="0.45.0"): logger.error("版本不符合要求(需 >= 0.45.x)") sys.exit(1) logger.info("版本检查通过,准备修改文件") # 备份文件 if not backup_files(pkg_path, main_path): logger.error("文件备份失败,终止操作") sys.exit(1) # 修改文件 if not modify_main_js(main_path): sys.exit(1) logger.info("脚本执行完成") except Exception as e: logger.error(f"执行过程中发生错误: {str(e)}") sys.exit(1) if __name__ == "__main__": patch_cursor_get_machine_id() ================================================ FILE: requirements.txt ================================================ DrissionPage==4.1.0.9 colorama==0.4.6 python-dotenv pyinstaller requests ================================================ FILE: reset_machine.py ================================================ import os import sys import json import uuid import hashlib import shutil from colorama import Fore, Style, init # 初始化colorama init() # 定义emoji和颜色常量 EMOJI = { "FILE": "📄", "BACKUP": "💾", "SUCCESS": "✅", "ERROR": "❌", "INFO": "ℹ️", "RESET": "🔄", } class MachineIDResetter: def __init__(self): # 判断操作系统 if sys.platform == "win32": # Windows appdata = os.getenv("APPDATA") if appdata is None: raise EnvironmentError("APPDATA 环境变量未设置") self.db_path = os.path.join( appdata, "Cursor", "User", "globalStorage", "storage.json" ) elif sys.platform == "darwin": # macOS self.db_path = os.path.abspath( os.path.expanduser( "~/Library/Application Support/Cursor/User/globalStorage/storage.json" ) ) elif sys.platform == "linux": # Linux 和其他类Unix系统 self.db_path = os.path.abspath( os.path.expanduser("~/.config/Cursor/User/globalStorage/storage.json") ) else: raise NotImplementedError(f"不支持的操作系统: {sys.platform}") def generate_new_ids(self): """生成新的机器ID""" # 生成新的UUID dev_device_id = str(uuid.uuid4()) # 生成新的machineId (64个字符的十六进制) machine_id = hashlib.sha256(os.urandom(32)).hexdigest() # 生成新的macMachineId (128个字符的十六进制) mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() # 生成新的sqmId sqm_id = "{" + str(uuid.uuid4()).upper() + "}" return { "telemetry.devDeviceId": dev_device_id, "telemetry.macMachineId": mac_machine_id, "telemetry.machineId": machine_id, "telemetry.sqmId": sqm_id, } def reset_machine_ids(self): """重置机器ID并备份原文件""" try: print(f"{Fore.CYAN}{EMOJI['INFO']} 正在检查配置文件...{Style.RESET_ALL}") # 检查文件是否存在 if not os.path.exists(self.db_path): print( f"{Fore.RED}{EMOJI['ERROR']} 配置文件不存在: {self.db_path}{Style.RESET_ALL}" ) return False # 检查文件权限 if not os.access(self.db_path, os.R_OK | os.W_OK): print( f"{Fore.RED}{EMOJI['ERROR']} 无法读写配置文件,请检查文件权限!{Style.RESET_ALL}" ) print( f"{Fore.RED}{EMOJI['ERROR']} 如果你使用过 go-cursor-help 来修改 ID; 请修改文件只读权限 {self.db_path} {Style.RESET_ALL}" ) return False # 读取现有配置 print(f"{Fore.CYAN}{EMOJI['FILE']} 读取当前配置...{Style.RESET_ALL}") with open(self.db_path, "r", encoding="utf-8") as f: config = json.load(f) # 生成新的ID print(f"{Fore.CYAN}{EMOJI['RESET']} 生成新的机器标识...{Style.RESET_ALL}") new_ids = self.generate_new_ids() # 更新配置 config.update(new_ids) # 保存新配置 print(f"{Fore.CYAN}{EMOJI['FILE']} 保存新配置...{Style.RESET_ALL}") with open(self.db_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=4) print(f"{Fore.GREEN}{EMOJI['SUCCESS']} 机器标识重置成功!{Style.RESET_ALL}") print(f"\n{Fore.CYAN}新的机器标识:{Style.RESET_ALL}") for key, value in new_ids.items(): print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") return True except PermissionError as e: print(f"{Fore.RED}{EMOJI['ERROR']} 权限错误: {str(e)}{Style.RESET_ALL}") print( f"{Fore.YELLOW}{EMOJI['INFO']} 请尝试以管理员身份运行此程序{Style.RESET_ALL}" ) return False except Exception as e: print(f"{Fore.RED}{EMOJI['ERROR']} 重置过程出错: {str(e)}{Style.RESET_ALL}") return False if __name__ == "__main__": print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['RESET']} Cursor 机器标识重置工具{Style.RESET_ALL}") print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") resetter = MachineIDResetter() resetter.reset_machine_ids() print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") input(f"{EMOJI['INFO']} 按回车键退出...") ================================================ FILE: test/get_veri_code_test.py ================================================ from DrissionPage import ChromiumOptions, Chromium from DrissionPage.common import Keys import time import re import sys import os def get_extension_path(): """获取插件路径""" root_dir = os.getcwd() extension_path = os.path.join(root_dir, "turnstilePatch") if hasattr(sys, "_MEIPASS"): print("运行在打包环境中") extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") print(f"尝试加载插件路径: {extension_path}") if not os.path.exists(extension_path): raise FileNotFoundError( f"插件不存在: {extension_path}\n请确保 turnstilePatch 文件夹在正确位置" ) return extension_path def get_browser_options(): co = ChromiumOptions() try: extension_path = get_extension_path() co.add_extension(extension_path) except FileNotFoundError as e: print(f"警告: {e}") co.set_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" ) co.set_pref("credentials_enable_service", False) co.set_argument("--hide-crash-restore-bubble") co.auto_port() # Mac 系统特殊处理 if sys.platform == "darwin": co.set_argument("--no-sandbox") co.set_argument("--disable-gpu") return co def get_veri_code(username): # 使用相同的浏览器配置 co = get_browser_options() browser = Chromium(co) code = None try: # 获取当前标签页 tab = browser.latest_tab tab.run_js("try { turnstile.reset() } catch(e) { }") # 打开临时邮箱网站 tab.get("https://tempmail.plus/zh") time.sleep(2) # 设置邮箱用户名 while True: if tab.ele("@id=pre_button"): # 点击输入框 tab.actions.click("@id=pre_button") time.sleep(1) # 删除之前的内容 tab.run_js('document.getElementById("pre_button").value = ""') # 输入新用户名并回车 tab.actions.input(username).key_down(Keys.ENTER).key_up(Keys.ENTER) break time.sleep(1) # 等待并获取新邮件 while True: new_mail = tab.ele("@class=mail") if new_mail: if new_mail.text: print("最新的邮件:", new_mail.text) tab.actions.click("@class=mail") break else: print(new_mail) break time.sleep(1) # 提取验证码 if tab.ele("@class=overflow-auto mb-20"): email_content = tab.ele("@class=overflow-auto mb-20").text verification_code = re.search( r"verification code is (\d{6})", email_content ) if verification_code: code = verification_code.group(1) print("验证码:", code) else: print("未找到验证码") # 删除邮件 if tab.ele("@id=delete_mail"): tab.actions.click("@id=delete_mail") time.sleep(1) if tab.ele("@id=confirm_mail"): tab.actions.click("@id=confirm_mail") print("删除邮件") except Exception as e: print(f"发生错误: {str(e)}") finally: browser.quit() return code # 测试运行 if __name__ == "__main__": test_username = "test_user" # 替换为你要测试的用户名 code = get_veri_code(test_username) print(f"获取到的验证码: {code}") ================================================ FILE: test_email.py ================================================ import os from dotenv import load_dotenv from get_email_code import EmailVerificationHandler import logging def test_temp_mail(): """测试临时邮箱方式""" handler = EmailVerificationHandler() print("\n=== 测试临时邮箱模式 ===") print(f"临时邮箱: {os.getenv('TEMP_MAIL')}@mailto.plus") code = handler.get_verification_code() if code: print(f"成功获取验证码: {code}") else: print("未能获取验证码") def test_email_server(): """测试邮箱服务器方式(POP3/IMAP)""" handler = EmailVerificationHandler() protocol = os.getenv('IMAP_PROTOCOL', 'POP3') print(f"\n=== 测试 {protocol} 模式 ===") print(f"邮箱服务器: {os.getenv('IMAP_SERVER')}") print(f"邮箱账号: {os.getenv('IMAP_USER')}") code = handler.get_verification_code() if code: print(f"成功获取验证码: {code}") else: print("未能获取验证码") def print_config(): """打印当前配置""" print("\n当前环境变量配置:") print(f"TEMP_MAIL: {os.getenv('TEMP_MAIL')}") if os.getenv('TEMP_MAIL') == 'null': print(f"IMAP_SERVER: {os.getenv('IMAP_SERVER')}") print(f"IMAP_PORT: {os.getenv('IMAP_PORT')}") print(f"IMAP_USER: {os.getenv('IMAP_USER')}") print(f"IMAP_PROTOCOL: {os.getenv('IMAP_PROTOCOL', 'POP3')}") print(f"DOMAIN: {os.getenv('DOMAIN')}") def main(): # 加载环境变量 load_dotenv() # 打印初始配置 print_config() try: # 根据配置决定测试哪种模式 if os.getenv('TEMP_MAIL') != 'null': test_temp_mail() else: test_email_server() except Exception as e: print(f"测试过程中发生错误: {str(e)}") if __name__ == "__main__": main() ================================================ FILE: turnstilePatch/manifest.json ================================================ { "manifest_version": 3, "name": "Turnstile Patcher", "version": "2.1", "content_scripts": [ { "js": [ "./script.js" ], "matches": [ "" ], "run_at": "document_start", "all_frames": true, "world": "MAIN" } ] } ================================================ FILE: turnstilePatch/readme.txt ================================================ ================================================ FILE: turnstilePatch/script.js ================================================ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // old method wouldn't work on 4k screens let screenX = getRandomInt(800, 1200); let screenY = getRandomInt(400, 600); Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY });