Compare commits

..

32 Commits

Author SHA1 Message Date
yeongpin
ff358588bb feat: Refactor Project Structure and Add Configuration Management
- Add `config.py` for centralized configuration management
- Add `utils.py` for cross-platform utility functions
- Remove `browser.py` and `control.py` to simplify project structure
- Update version to 1.7.06
- Modify build specification to reflect new file structure
- Update localization files with new update confirmation messages
- Enhance configuration loading and path detection across different platforms
2025-03-11 11:49:17 +08:00
yeongpin
6ca80ccb10 Merge branch 'main' of https://github.com/yeongpin/cursor-free-vip 2025-03-11 10:12:57 +08:00
yeongpin
2fca5218fb remove md 2025-03-11 10:12:54 +08:00
Pin Studios
71ecf5a201 Update issue templates 2025-03-11 10:12:41 +08:00
Pin Studios
4570b174ab Update issue templates 2025-03-10 18:36:36 +08:00
yeongpin
f708ce443b fix: Correct Syntax in Linux Installation Script
- Fix syntax error in install script's conditional statements
- Remove unnecessary braces and correct shell script syntax
- Ensure proper handling of Linux architecture detection
2025-03-10 17:54:09 +08:00
yeongpin
4f6f3fe814 fix: Improve Cursor Version Check and Configuration Handling
- Enhance version checking in `reset_machine_manual.py` with more robust error handling
- Add detailed error messages and logging for version detection
- Update configuration paths in `new_signup.py` to include Cursor application path
- Add configuration initialization in `main.py`
- Update localization files with new error and version-related messages
2025-03-10 17:41:07 +08:00
yeongpin
54ecf2d752 feat: Add Linux ARM64 Support and Update Build Workflow
- Extend GitHub Actions workflow to build Linux x64 and ARM64 executables
- Update install script to detect Linux architecture (x64 or ARM64)
- Modify release process to include both Linux architecture artifacts
- Rename Linux build job to clarify x64 architecture
2025-03-10 16:28:26 +08:00
yeongpin
1f1231d1a9 remove br in readme 2025-03-10 14:43:35 +08:00
yeongpin
d10999a517 Update Readme Doc 2025-03-10 14:43:09 +08:00
yeongpin
fb4be6334a docs: Add Detailed Comments and Localization for Configuration Settings
- Add bilingual comments (English and Chinese) for OSPaths section
- Provide clear descriptions for timing-related configuration parameters
- Enhance readability of README.md configuration settings
- Improve documentation for storage, SQLite, and machine ID paths
2025-03-10 13:42:18 +08:00
Pin Studios
786eba5371 Update README.md 2025-03-10 13:41:05 +08:00
yeongpin
41ddbf519e hotfix: Optimize Verification Code Handling and Timing Configuration
- Refactor verification code retrieval with dynamic wait time generation
- Use `get_random_wait_time()` for more flexible retry intervals
- Update version to 1.7.04 across project files
- Minor improvements to timing and retry logic in signup process
2025-03-10 13:27:41 +08:00
yeongpin
ffd48201fd hotfix: Improve Signup Flow and Timing Configuration
- Add comprehensive timing configuration for signup process
- Refactor random wait time generation with more flexible config options
- Update form filling and verification code handling with configurable wait times
- Enhance localization support for new timing-related messages
- Update version to 1.7.03 across project files
2025-03-10 12:24:43 +08:00
yeongpin
cd4f36725c refactor: Optimize Signup Flow and Error Handling
- Implement direct URL-based signup with pre-filled parameters
- Enhance password setting process with improved error detection
- Add more robust retry mechanism for password input
- Improve error handling for email availability and submission
- Refactor signup and password setting methods for better reliability
2025-03-10 11:44:37 +08:00
yeongpin
dccb524bd7 Update Readme add some doc 2025-03-10 11:21:42 +08:00
yeongpin
90e9a5b287 feat: Enhance Configuration Management for Cursor Paths
- Add OS-specific path configurations for Cursor storage, SQLite, and machine ID files
- Integrate configuration file management in `new_signup.py` and `reset_machine_manual.py`
- Remove Workbench Cursor path and main.js modification steps
- Improve cross-platform path detection and configuration handling
- Update version to 1.7.02
2025-03-10 11:18:21 +08:00
Pin Studios
66a67fce8b Update README.md 2025-03-07 11:20:04 +08:00
Pin Studios
0981f00b9c Update build.yml 2025-03-07 11:19:49 +08:00
yeongpin
02851c9a09 feat: Implement Configuration Management and Enhance Browser Setup
- Add `setup_config` function to manage configuration file across platforms
- Extract configuration-related code from `setup_driver` into a separate function
- Implement dynamic Chrome path detection for Windows, macOS, and Linux
- Add configurable Turnstile verification settings
- Update README.md with configuration file details
- Enhance localization support for configuration-related messages
- Improve code maintainability and platform compatibility
2025-03-07 11:18:43 +08:00
yeongpin
6312d66813 chore: Bump version to 1.6.03
- Update version in .env file
- Update CHANGELOG.md with version 1.6.03 details
- Minor hotfix and maintenance release
2025-03-06 18:37:04 +08:00
yeongpin
1b1a21f3d7 feat: Add Machine ID File Update Functionality
- Implement `update_machine_id_file` method in `MachineIDResetter` class
- Add new `get_cursor_machine_id_path` function to detect machineId file path across platforms
- Enhance machine ID reset process with file-level machine ID update
- Implement backup mechanism for existing machineId file
- Add cross-platform support for Linux, macOS, and Windows
- Improve error handling and logging with colorful console output
2025-03-06 18:36:00 +08:00
yeongpin
c94bd605e5 fix: Update GitHub Link in Workbench JS Modification
- Correct GitHub repository link to point to specific project repository
- Modify onClick handler to open the correct GitHub page for the project
2025-03-06 18:14:34 +08:00
yeongpin
9f6eee77e0 feat: Add Workbench JS Modification and Cursor Path Detection
- Update `MachineIDResetter` to include workbench.desktop.main.js modification step
- Enhance platform support for Linux, macOS, and Windows
- Update CHANGELOG.md to version 1.6.02
2025-03-06 18:11:40 +08:00
Pin Studios
2bdcc2f633 chore: Bump version to 1.6.01 2025-03-06 12:19:26 +08:00
Pin Studios
4aabe2e403 chore: Bump version to 1.5.04 and improve system compatibility
- Update version in .env file to 1.5.04
- Add Mac-specific run_venv script to .gitignore
- Enhance Cursor Auth platform detection with more precise sys.platform checks
- Add maximum retry mechanism for email creation
- Improve error handling and platform support in cursor_auth.py
2025-03-06 12:18:55 +08:00
yeongpin
005aa2cd95 feat: Improve Version Update Mechanism with Robust Error Handling
- Enhance GitHub API version check with proper headers and timeout
- Add comprehensive error handling for version retrieval
- Implement more reliable update script download and execution
- Improve cross-platform update process with better error reporting
- Add fallback mechanisms for network and update failures
2025-03-06 11:18:21 +08:00
yeongpin
14f6dfc29d fix: Improve Browser Startup and Error Handling
- Add `--no-sandbox` flag to resolve browser startup issues
- Enhance error handling in temp email creation
- Update localization files with new email-related messages
- Improve translation support for email creation process
2025-03-06 11:01:34 +08:00
Pin Studios
2e9bd269ad Update README.md 2025-03-03 15:24:36 +08:00
Pin Studios
f9b7e23253 Update README.md 2025-03-03 15:19:44 +08:00
yeongpin
0d979f7543 chore: Bump default version to 1.5.03 in build workflow 2025-03-03 11:59:15 +08:00
yeongpin
cce3025f7f feat: Enhance Name Generation and Improve Account Registration Process
- Implement realistic name generation with predefined name lists
- Modify first name letter for uniqueness
- Add more descriptive console output with emojis and translations
- Update localization files with new registration-related keys
- Optimize random name generation in registration modules
2025-03-03 11:58:28 +08:00
22 changed files with 1388 additions and 793 deletions

4
.env
View File

@@ -1,2 +1,2 @@
version=1.5.01 version=1.7.06
VERSION=1.5.01 VERSION=1.7.06

View File

@@ -6,7 +6,7 @@ on:
version: version:
description: 'Version number (e.g. 1.0.9)' description: 'Version number (e.g. 1.0.9)'
required: true required: true
default: '1.4.07' default: '1.7.06'
permissions: permissions:
contents: write contents: write
@@ -98,7 +98,7 @@ jobs:
name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64 name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64 path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
build-linux: build-linux-x64:
needs: create-tag needs: create-tag
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -120,20 +120,55 @@ jobs:
pip install pyinstaller pip install pyinstaller
pip install -r requirements.txt pip install -r requirements.txt
- name: Build Linux executable - name: Build Linux x64 executable
env: env:
VERSION: ${{ env.VERSION }} VERSION: ${{ env.VERSION }}
run: | run: |
pyinstaller build.spec pyinstaller build.spec
mv "dist/CursorFreeVIP_${{ env.VERSION }}_linux" "dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64"
echo "Contents of dist directory:" echo "Contents of dist directory:"
ls -la dist/ ls -la dist/
- name: Upload Linux artifact - name: Upload Linux x64 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: CursorFreeVIP_${{ env.VERSION }}_linux name: CursorFreeVIP_${{ env.VERSION }}_linux_x64
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64
build-linux-arm64:
needs: create-tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: arm64
- name: Set version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Build in ARM64 Docker container
run: |
docker run --rm --platform linux/arm64 -v ${{ github.workspace }}:/app -w /app arm64v8/python:3.9-slim bash -c "
apt-get update && apt-get install -y build-essential
pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt
python -m PyInstaller build.spec
mv /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
"
echo "Contents of dist directory:"
ls -la dist/
- name: Upload Linux ARM64 artifact
uses: actions/upload-artifact@v4
with:
name: CursorFreeVIP_${{ env.VERSION }}_linux_arm64
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
build-macos-intel: build-macos-intel:
needs: create-tag needs: create-tag
@@ -171,9 +206,8 @@ jobs:
name: CursorFreeVIP_${{ env.VERSION }}_mac_intel name: CursorFreeVIP_${{ env.VERSION }}_mac_intel
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel
create-release: create-release:
needs: [build-windows, build-macos-arm64, build-linux, build-macos-intel] needs: [build-windows, build-macos-arm64, build-linux-x64, build-linux-arm64, build-macos-intel]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@@ -201,7 +235,8 @@ jobs:
files: | files: |
artifacts/CursorFreeVIP_${{ env.VERSION }}_windows.exe/CursorFreeVIP_${{ env.VERSION }}_windows.exe artifacts/CursorFreeVIP_${{ env.VERSION }}_windows.exe/CursorFreeVIP_${{ env.VERSION }}_windows.exe
artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_arm64/CursorFreeVIP_${{ env.VERSION }}_mac_arm64 artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_arm64/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
artifacts/CursorFreeVIP_${{ env.VERSION }}_linux/CursorFreeVIP_${{ env.VERSION }}_linux artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_x64/CursorFreeVIP_${{ env.VERSION }}_linux_x64
artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_arm64/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel
draft: false draft: false
prerelease: false prerelease: false

4
.gitignore vendored
View File

@@ -13,6 +13,7 @@ build.mac.command
build.py build.py
build.sh build.sh
ENV/ ENV/
test.py
install.bat install.bat
run.bat run.bat
@@ -45,3 +46,6 @@ Thumbs.db
*.log *.log
*.db *.db
*.sqlite3 *.sqlite3
# Mac
run_venv.mac.command

View File

@@ -1,5 +1,67 @@
# Change Log # Change Log
## v1.7.06
1. Add: Update Confirm | 增加更新確認
2. Add: Update Skipped | 增加更新跳過
3. Add: Invalid Choice | 增加無效選擇
4. Fix: Cursor Path | 修復Cursor路徑
5. Fix: Path Encoding | 修復路徑編碼
6. Fix: Getting Verification Code | 修復獲取驗證碼
7. Fix: Setting Password | 修復設置密碼
8. Fix: Disable Auto Update | 修復禁用自動更新
9. Add Config.py | 增加Config.py
10. Add utils.py | 增加utils.py
11. Rebuild some logic | 重新構建一些邏輯
## v1.7.05
1. Fix: Cursor Version Check | 修復Cursor版本檢查
2. Fix: Small Problem | 修復一些小問題
## v1.7.04
1. Hotfix: Small Problem | 修復一些小問題
## v1.7.03
1. Hotfix: Small Problem | 修復一些小問題
## v1.7.02
1. Fix: Cursor Path | 修復Cursor路徑
2. Add: Config File | 增加配置文件
3. Remove: Workbench Cursor Path | 移除Workbench Cursor路徑
4. Remove: Cursor Main JS | 移除Cursor main.js
## v1.7.01
- Refactoring: Extract configuration-related code from the `setup_driver` function to an independent `setup_config` function
- Optimization: Improve code maintainability and make configuration management and browser settings more clear
- Improvement: The creation and update logic of the configuration file is clearer and more independent
## v1.6.03
1. Hotfix: Small Problem | 修復一些問題
## v1.6.02
1. Hotfix: Small Problem | 修復一些問題
2. Add: Test some Bypass Code | 測試一些繞過代碼
## v1.6.01
1. Fix: Cursor Auth | 修復Cursor Auth
2. Add: Create Account Maximum Retry | 增加創建賬號最大重試次數
3. Fix: Cursor Auth Error | 修復Cursor Auth錯誤
4. Fix: Update Curl Faild | 修復更新Curl失敗
## v1.5.03
1. HOTFIX: Stuck on starting browser | 修復啟動瀏覽器卡住問題
2. Small Fix: Error Handling | 小修錯誤處理
3. Small Fix: Translation | 小修翻譯
4. Small Fix: Performance | 小修性能
## v1.5.02
1. Add: Generate Random Name Alias | 增加生成隨機真實姓名
2. Add: Realistic Name Input | 增加真實姓名輸入
3. Optimize: Error Handling | 優化錯誤處理
4. Optimize: Translation | 優化翻譯
5. Optimize: Performance | 優化性能
## v1.5.01 ## v1.5.01
1. Add: Check Latest Version | 增加檢查最新版本 1. Add: Check Latest Version | 增加檢查最新版本
2. Add: Update Command | 增加更新命令 2. Add: Update Command | 增加更新命令

View File

@@ -12,7 +12,7 @@
[![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a)](https://github.com/yeongpin/cursor-free-vip/releases/latest) [![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a)](https://github.com/yeongpin/cursor-free-vip/releases/latest)
</p> </p>
<h4>Support Latest 0.46.3 Version | 支持最新0.46.3本</h4> <h4>Support Latest 0.46.10 Version | 支持最新0.46.10版本</h4>
This is a tool to automatically register , support Windows and macOS systems, complete Auth verification, and reset Cursor's configuration. This is a tool to automatically register , support Windows and macOS systems, complete Auth verification, and reset Cursor's configuration.
@@ -86,6 +86,62 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
## ❗ Note | 注意事項 ## ❗ Note | 注意事項
📝 Config | 文件配置
`Win / Macos / Linux Path | 路徑 [Documents/.cursor-free-vip/config.ini]`
<details>
<summary><b>⭐ Config | 文件配置</b></summary>
```
[Chrome]
# Default Google Chrome Path | 默認Google Chrome 遊覽器路徑
chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe
[Turnstile]
# Handle Tuenstile Wait Time | 等待人機驗證時間
handle_turnstile_time = 2
# Handle Tuenstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
handle_turnstile_random_time = 1-3
[OSPaths]
# Storage Path | 存儲路徑
storage_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/storage.json
# SQLite Path | SQLite路徑
sqlite_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/state.vscdb
# Machine ID Path | 機器ID路徑
machine_id_path = /Users/username/Library/Application Support/Cursor/machineId
[Timing]
# Min Random Time | 最小隨機時間
min_random_time = 0.1
# Max Random Time | 最大隨機時間
max_random_time = 0.8
# Page Load Wait | 頁面加載等待時間
page_load_wait = 0.1-0.8
# Input Wait | 輸入等待時間
input_wait = 0.3-0.8
# Submit Wait | 提交等待時間
submit_wait = 0.5-1.5
# Verification Code Input | 驗證碼輸入等待時間
verification_code_input = 0.1-0.3
# Verification Success Wait | 驗證成功等待時間
verification_success_wait = 2-3
# Verification Retry Wait | 驗證重試等待時間
verification_retry_wait = 2-3
# Email Check Initial Wait | 郵件檢查初始等待時間
email_check_initial_wait = 4-6
# Email Refresh Wait | 郵件刷新等待時間
email_refresh_wait = 2-4
# Settings Page Load Wait | 設置頁面加載等待時間
settings_page_load_wait = 1-2
# Failed Retry Time | 失敗重試時間
failed_retry_time = 0.5-1
# Retry Interval | 重試間隔
retry_interval = 8-12
# Max Timeout | 最大超時時間
max_timeout = 160
```
</details>
* Use administrator to run the script <br>請使用管理員身份運行腳本 * Use administrator to run the script <br>請使用管理員身份運行腳本
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br> * Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>
@@ -98,9 +154,9 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
## 🚨 Common Issues | 常見問題 ## 🚨 Common Issues | 常見問題
|如果遇到權限問題,請確保:|If you encounter permission issues, please ensure:| |如果遇到權限問題,請確保:| 此腳本以管理員身份運行 |
|:---:|:---:| |:---:|:---:|
| 此腳本以管理員身份運行 | This script is run with administrator privileges | |If you encounter permission issues, please ensure: | This script is run with administrator privileges |
@@ -109,6 +165,11 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
歡迎提交 Issue 和 Pull Request 歡迎提交 Issue 和 Pull Request
<a href="https://github.com/yeongpin/cursor-free-vip/graphs/contributors">
<img src="https://contrib.rocks/image?repo=yeongpin/cursor-free-vip" />
</a>
<br /><br />
## 📩 Disclaimer | 免責聲明 ## 📩 Disclaimer | 免責聲明

View File

@@ -1,95 +0,0 @@
from DrissionPage import ChromiumOptions, ChromiumPage
import sys
import os
import logging
import random
class BrowserManager:
def __init__(self, noheader=False):
self.browser = None
self.noheader = noheader
def init_browser(self):
"""初始化浏览器"""
co = self._get_browser_options()
# 如果设置了 noheader添加相应的参数
if self.noheader:
co.set_argument('--headless=new')
self.browser = ChromiumPage(co)
return self.browser
def _get_browser_options(self):
"""获取浏览器配置"""
co = ChromiumOptions()
try:
extension_path = self._get_extension_path()
co.add_extension(extension_path)
co.set_argument("--allow-extensions-in-incognito")
extension_block_path = self.get_extension_block()
co.add_extension(extension_block_path)
co.set_argument("--allow-extensions-in-incognito")
except FileNotFoundError as e:
logging.warning(f"警告: {e}")
# 设置更真实的用户代理
co.set_user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
# 基本设置
co.set_pref("credentials_enable_service", False)
# 随机端口
co.auto_port()
# 系统特定设置
if sys.platform == "darwin": # macOS
co.set_argument("--disable-gpu")
co.set_argument("--no-sandbox")
elif sys.platform == "win32": # Windows
co.set_argument("--disable-software-rasterizer")
# 设置窗口大小
window_width = random.randint(1024, 1920)
window_height = random.randint(768, 1080)
co.set_argument(f"--window-size={window_width},{window_height}")
return co
def _get_extension_path(self):
"""获取插件路径"""
root_dir = os.getcwd()
extension_path = os.path.join(root_dir, "turnstilePatch")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
if not os.path.exists(extension_path):
raise FileNotFoundError(f"插件不存在: {extension_path}")
return extension_path
def get_extension_block(self):
"""获取插件路径"""
root_dir = os.getcwd()
extension_path = os.path.join(root_dir, "PBlock")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "PBlock")
if not os.path.exists(extension_path):
raise FileNotFoundError(f"插件不存在: {extension_path}")
return extension_path
def quit(self):
"""关闭浏览器"""
if self.browser:
try:
self.browser.quit()
except:
pass

View File

@@ -29,15 +29,19 @@ a = Analysis(
('cursor_auth.py', '.'), ('cursor_auth.py', '.'),
('reset_machine_manual.py', '.'), ('reset_machine_manual.py', '.'),
('cursor_register.py', '.'), ('cursor_register.py', '.'),
('browser.py', '.'), ('new_signup.py', '.'),
('control.py', '.'), ('new_tempemail.py', '.'),
('quit_cursor.py', '.'),
('cursor_register_manual.py', '.'),
('.env', '.') ('.env', '.')
], ],
hiddenimports=[ hiddenimports=[
'cursor_auth', 'cursor_auth',
'reset_machine_manual', 'reset_machine_manual',
'browser', 'new_signup',
'control' 'new_tempemail',
'quit_cursor',
'cursor_register_manual'
], ],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},

117
config.py Normal file
View File

@@ -0,0 +1,117 @@
import os
import sys
import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
os.makedirs(config_dir, exist_ok=True)
config = configparser.ConfigParser()
# 默認配置
default_config = {
'Chrome': {
'chromepath': get_default_chrome_path()
},
'Turnstile': {
'handle_turnstile_time': '2',
'handle_turnstile_random_time': '1-3'
},
'Timing': {
'min_random_time': '0.1',
'max_random_time': '0.8',
'page_load_wait': '0.1-0.8',
'input_wait': '0.3-0.8',
'submit_wait': '0.5-1.5',
'verification_code_input': '0.1-0.3',
'verification_success_wait': '2-3',
'verification_retry_wait': '2-3',
'email_check_initial_wait': '4-6',
'email_refresh_wait': '2-4',
'settings_page_load_wait': '1-2',
'failed_retry_time': '0.5-1',
'retry_interval': '8-12',
'max_timeout': '160'
}
}
# 添加系統特定路徑配置
if sys.platform == "win32":
appdata = os.getenv("APPDATA")
localappdata = os.getenv("LOCALAPPDATA", "")
default_config['WindowsPaths'] = {
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
'updater_path': os.path.join(localappdata, "cursor-updater")
}
elif sys.platform == "darwin":
default_config['MacPaths'] = {
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater")
}
elif sys.platform == "linux":
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
default_config['LinuxPaths'] = {
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"),
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.expanduser("~/.config/cursor-updater")
}
# 讀取現有配置並合併
if os.path.exists(config_file):
config.read(config_file, encoding='utf-8')
config_modified = False
for section, options in default_config.items():
if not config.has_section(section):
config.add_section(section)
config_modified = True
for option, value in options.items():
if not config.has_option(section, option):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW} {translator.get('register.config_option_added', option=f'{section}.{option}')}{Style.RESET_ALL}")
if config_modified:
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{translator.get('register.config_updated')}{Style.RESET_ALL}")
else:
for section, options in default_config.items():
config.add_section(section)
for option, value in options.items():
config.set(section, option, str(value))
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
return None
def get_config(translator=None):
"""Get existing config or create new one"""
return setup_config(translator)

View File

@@ -1,240 +0,0 @@
import time
import random
import os
from colorama import Fore, Style, init
# 初始化colorama
init()
# 定义emoji常量
EMOJI = {
'MAIL': '📧',
'REFRESH': '🔄',
'SUCCESS': '',
'ERROR': '',
'INFO': '',
'CODE': '📱'
}
class BrowserControl:
def __init__(self, browser, translator=None):
self.browser = browser
self.translator = translator # 保存translator
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
self.current_tab = None # 当前标签页
self.signup_tab = None # 注册标签页
self.email_tab = None # 邮箱标签页
def create_new_tab(self):
"""创建新标签页"""
try:
# 保存当前标签页
self.current_tab = self.browser
# 创建新的浏览器实例
from browser import BrowserManager
browser_manager = BrowserManager()
new_browser = browser_manager.init_browser()
# 保存新标签页
self.signup_tab = new_browser
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.create_new_tab_success')}{Style.RESET_ALL}")
return new_browser
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.create_new_tab_failed', error=str(e))}{Style.RESET_ALL}")
return None
def switch_to_tab(self, browser):
"""切换到指定浏览器窗口"""
try:
self.browser = browser
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.switch_tab_success')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.switch_tab_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_current_tab(self):
"""获取当前标签页"""
return self.browser
def wait_for_page_load(self, seconds=2):
"""等待页面加载"""
time.sleep(seconds)
def navigate_to(self, url):
"""导航到指定URL"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.navigate_to', url=url)}...{Style.RESET_ALL}")
self.browser.get(url)
self.wait_for_page_load()
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.browser_error', error=str(e))}{Style.RESET_ALL}")
return False
def get_verification_code(self):
"""从邮件中获取验证码"""
try:
# 尝试所有可能的样式组合
selectors = [
# 新样式
'xpath://div[contains(@style, "font-family:-apple-system") and contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px") and contains(@style, "color:#202020")]',
# 带行高的样式
'xpath://div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px") and contains(@style, "line-height:30px")]',
# rgba 颜色样式
'xpath://div[contains(@style, "font-size: 28px") and contains(@style, "letter-spacing: 2px") and contains(@style, "color: rgba(32, 32, 32, 1)")]',
# 宽松样式
'xpath://div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px")]'
]
# 依次尝试每个选择器
for selector in selectors:
code_div = self.browser.ele(selector)
if code_div:
verification_code = code_div.text.strip()
if verification_code.isdigit() and len(verification_code) == 6:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.found_verification_code')}: {verification_code}{Style.RESET_ALL}")
return verification_code
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.no_valid_verification_code')}{Style.RESET_ALL}")
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_verification_code_error', error=str(e))}{Style.RESET_ALL}")
return None
def fill_verification_code(self, code):
"""填写验证码"""
try:
if not code or len(code) != 6:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.verification_code_format_error')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.fill_verification_code')}...{Style.RESET_ALL}")
# 记住当前标签页(邮箱页面)
email_tab = self.browser
# 切换回注册页面标签
self.switch_to_tab(self.signup_tab)
time.sleep(1)
# 输入验证码
for digit in code:
self.browser.actions.input(digit)
time.sleep(random.uniform(0.1, 0.3))
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.verification_code_filled')}{Style.RESET_ALL}")
# 等待页面加载和登录完成
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.wait_for_login')}...{Style.RESET_ALL}")
time.sleep(5)
# 先访问登录页面确保登录状态
login_url = "https://authenticator.cursor.sh"
self.browser.get(login_url)
time.sleep(3) # 增加等待时间
# 获取cookies第一次尝试
token = self.get_cursor_session_token()
if not token:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_token_failed')}...{Style.RESET_ALL}")
time.sleep(3)
token = self.get_cursor_session_token()
if token:
self.save_token_to_file(token)
# 获取到token后再访问设置页面
settings_url = "https://www.cursor.com/settings"
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_account_info')}...{Style.RESET_ALL}")
self.browser.get(settings_url)
time.sleep(2)
# 获取账户额度信息
try:
usage_selector = (
"css:div.col-span-2 > div > div > div > div > "
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
)
usage_ele = self.browser.ele(usage_selector)
if usage_ele:
usage_info = usage_ele.text
total_usage = usage_info.split("/")[-1].strip()
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('control.account_usage_limit')}: {total_usage}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_account_usage_failed', error=str(e))}{Style.RESET_ALL}")
# 切换回邮箱页面
self.switch_to_tab(email_tab)
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.fill_verification_code_failed', error=str(e))}{Style.RESET_ALL}")
return False
def check_and_click_turnstile(self):
"""检查并点击 Turnstile 验证框"""
try:
# 等待验证框出现
time.sleep(1)
# 查找验证框
verify_checkbox = self.browser.ele('xpath://label[contains(@class, "cb-lb")]//input[@type="checkbox"]')
if verify_checkbox:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.find_turnstile_verification_box')}...{Style.RESET_ALL}")
verify_checkbox.click()
time.sleep(2) # 等待验证完成
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.clicked_turnstile_verification_box')}{Style.RESET_ALL}")
return True
return False
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.check_and_click_turnstile_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_cursor_session_token(self, max_attempts=3, retry_interval=2):
"""获取Cursor会话token"""
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_cursor_session_token')}...{Style.RESET_ALL}")
attempts = 0
while attempts < max_attempts:
try:
# 直接从浏览器对象获取cookies
all_cookies = self.browser.get_cookies()
# 遍历查找目标cookie
for cookie in all_cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
token = cookie["value"].split("%3A%3A")[1]
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.get_cursor_session_token_success')}: {token}{Style.RESET_ALL}")
return token
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_cursor_session_token_failed', attempts=attempts, retry_interval=retry_interval)}...{Style.RESET_ALL}")
time.sleep(retry_interval)
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.reach_max_attempts', max_attempts=max_attempts)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_cookie_failed', error=str(e))}{Style.RESET_ALL}")
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.will_retry_in', retry_interval=retry_interval)}...{Style.RESET_ALL}")
time.sleep(retry_interval)
return None
def save_token_to_file(self, token):
"""保存token到文件"""
try:
with open('cursor_tokens.txt', 'a', encoding='utf-8') as f:
f.write(f"Token: {token}\n")
f.write("-" * 50 + "\n")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.token_saved_to_file')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.save_token_failed', error=str(e))}{Style.RESET_ALL}")

View File

@@ -2,6 +2,7 @@ import sqlite3
import os import os
import sys import sys
from colorama import Fore, Style, init from colorama import Fore, Style, init
from config import get_config
# 初始化colorama # 初始化colorama
init() init()
@@ -21,19 +22,41 @@ EMOJI = {
class CursorAuth: class CursorAuth:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
# 判断操作系统
if os.name == "nt": # Windows # 获取配置
self.db_path = os.path.join( config = get_config(translator)
os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb" if not config:
) print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}")
elif os.name =='posix': sys.exit(1)
self.db_path = os.path.expanduser(
"~/.config/Cursor/User/globalStorage/state.vscdb" # 根据操作系统获取路径
) try:
else: # macOS if sys.platform == "win32": # Windows
self.db_path = os.path.expanduser( if not config.has_section('WindowsPaths'):
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" raise ValueError("Windows paths not configured")
) self.db_path = config.get('WindowsPaths', 'sqlite_path')
elif sys.platform == 'linux': # Linux
if not config.has_section('LinuxPaths'):
raise ValueError("Linux paths not configured")
self.db_path = config.get('LinuxPaths', 'sqlite_path')
elif sys.platform == 'darwin': # macOS
if not config.has_section('MacPaths'):
raise ValueError("macOS paths not configured")
self.db_path = config.get('MacPaths', 'sqlite_path')
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}")
sys.exit(1)
# 验证路径是否存在
if not os.path.exists(os.path.dirname(self.db_path)):
raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}")
sys.exit(1)
# 检查数据库文件是否存在 # 检查数据库文件是否存在
if not os.path.exists(self.db_path): if not os.path.exists(self.db_path):
@@ -87,13 +110,16 @@ class CursorAuth:
# 设置要更新的键值对 # 设置要更新的键值对
updates = [] updates = []
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
if email is not None: if email is not None:
updates.append(("cursorAuth/cachedEmail", email)) updates.append(("cursorAuth/cachedEmail", email))
if access_token is not None: if access_token is not None:
updates.append(("cursorAuth/accessToken", access_token)) updates.append(("cursorAuth/accessToken", access_token))
if refresh_token is not None: if refresh_token is not None:
updates.append(("cursorAuth/refreshToken", refresh_token)) updates.append(("cursorAuth/refreshToken", refresh_token))
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
# 使用事务来确保数据完整性 # 使用事务来确保数据完整性
cursor.execute("BEGIN TRANSACTION") cursor.execute("BEGIN TRANSACTION")
@@ -131,5 +157,3 @@ class CursorAuth:
if conn: if conn:
conn.close() conn.close()
print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}") print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}")

View File

@@ -2,18 +2,16 @@ import os
from colorama import Fore, Style, init from colorama import Fore, Style, init
import time import time
import random import random
from browser import BrowserManager
from control import BrowserControl
from cursor_auth import CursorAuth from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter from reset_machine_manual import MachineIDResetter
os.environ["PYTHONVERBOSE"] = "0" os.environ["PYTHONVERBOSE"] = "0"
os.environ["PYINSTALLER_VERBOSE"] = "0" os.environ["PYINSTALLER_VERBOSE"] = "0"
# 初始化colorama # Initialize colorama
init() init()
# 定义emoji常量 # Define emoji constants
EMOJI = { EMOJI = {
'START': '🚀', 'START': '🚀',
'FORM': '📝', 'FORM': '📝',
@@ -33,9 +31,8 @@ EMOJI = {
class CursorRegistration: class CursorRegistration:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
# 设置为显示模式 # Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False' os.environ['BROWSER_HEADLESS'] = 'False'
self.browser_manager = BrowserManager()
self.browser = None self.browser = None
self.controller = None self.controller = None
self.mail_url = "https://yopmail.com/zh/email-generator" self.mail_url = "https://yopmail.com/zh/email-generator"
@@ -47,42 +44,49 @@ class CursorRegistration:
# 账号信息 # 账号信息
self.password = self._generate_password() self.password = self._generate_password()
self.first_name = self._generate_name() # Generate first name and last name separately
self.last_name = self._generate_name() first_name = random.choice([
print(f"Password: {self.password}\n") "James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
print(f"First Name: {self.first_name}\n") "Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
print(f"Last Name: {self.last_name}\n") "Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
])
self.last_name = random.choice([
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
])
# Modify first letter of first name
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
self.first_name = new_first_letter + first_name[1:]
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
def _generate_password(self, length=12): def _generate_password(self, length=12):
"""Generate Random Password""" """Generate Random Password"""
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length)) return ''.join(random.choices(chars, k=length))
def _generate_name(self, length=6):
"""Generate Random Name"""
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rest_letters = ''.join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length-1))
return first_letter + rest_letters
def setup_email(self): def setup_email(self):
"""设置邮箱""" """Setup Email"""
try: try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.browser_start')}...{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.browser_start')}...{Style.RESET_ALL}")
# 使用 new_tempemail 创建临时邮箱,传入 translator # Create a temporary email using new_tempemail, passing translator
from new_tempemail import NewTempEmail from new_tempemail import NewTempEmail
self.temp_email = NewTempEmail(self.translator) # 传入 translator self.temp_email = NewTempEmail(self.translator) # Pass translator
# 创建临时邮箱 # Create a temporary email
email_address = self.temp_email.create_email() email_address = self.temp_email.create_email()
if not email_address: if not email_address:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_create_failed')}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_create_failed')}{Style.RESET_ALL}")
return False return False
# 保存邮箱地址 # Save email address
self.email_address = email_address self.email_address = email_address
print(f"Email Address: {self.email_address}\n") self.email_tab = self.temp_email # Pass NewTempEmail instance
self.email_tab = self.temp_email # 传递 NewTempEmail 实例
return True return True
@@ -96,10 +100,10 @@ class CursorRegistration:
try: try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
# 直接使用 new_signup.py 进行注册 # Directly use new_signup.py to sign up
from new_signup import main as new_signup_main from new_signup import main as new_signup_main
# 执行新的注册流程,传入 translator # Execute the new registration process, passing translator
result, browser_tab = new_signup_main( result, browser_tab = new_signup_main(
email=self.email_address, email=self.email_address,
password=self.password, password=self.password,
@@ -111,11 +115,11 @@ class CursorRegistration:
) )
if result: if result:
# 使用返回的浏览器实例获取账户信息 # Use the returned browser instance to get account information
self.signup_tab = browser_tab # 保存浏览器实例 self.signup_tab = browser_tab # Save browser instance
success = self._get_account_info() success = self._get_account_info()
# 获取信息后关闭浏览器 # Close browser after getting information
if browser_tab: if browser_tab:
try: try:
browser_tab.quit() browser_tab.quit()
@@ -130,7 +134,7 @@ class CursorRegistration:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
return False return False
finally: finally:
# 确保在任何情况下都关闭浏览器 # Ensure browser is closed in any case
if browser_tab: if browser_tab:
try: try:
browser_tab.quit() browser_tab.quit()
@@ -138,7 +142,7 @@ class CursorRegistration:
pass pass
def _get_account_info(self): def _get_account_info(self):
"""获取账户信息和 Token""" """Get Account Information and Token"""
try: try:
self.signup_tab.get(self.settings_url) self.signup_tab.get(self.settings_url)
time.sleep(2) time.sleep(2)
@@ -153,7 +157,7 @@ class CursorRegistration:
if usage_ele: if usage_ele:
total_usage = usage_ele.text.split("/")[-1].strip() total_usage = usage_ele.text.split("/")[-1].strip()
print(f"Total Usage: {total_usage}\n") print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('register.total_usage', usage=total_usage)}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
max_attempts = 30 max_attempts = 30
retry_interval = 2 retry_interval = 2
@@ -190,7 +194,7 @@ class CursorRegistration:
return False return False
def _save_account_info(self, token, total_usage): def _save_account_info(self, token, total_usage):
"""保存账户信息到文件""" """Save Account Information to File"""
try: try:
# 先更新认证信息 # 先更新认证信息
print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
@@ -205,7 +209,7 @@ class CursorRegistration:
if not resetter.reset_machine_ids(): # 直接调用reset_machine_ids方法 if not resetter.reset_machine_ids(): # 直接调用reset_machine_ids方法
raise Exception("Failed to reset machine ID") raise Exception("Failed to reset machine ID")
# 保存账户信息到文件 # Save account information to file
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f: with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
f.write(f"\n{'='*50}\n") f.write(f"\n{'='*50}\n")
f.write(f"Email: {self.email_address}\n") f.write(f"Email: {self.email_address}\n")
@@ -222,7 +226,7 @@ class CursorRegistration:
return False return False
def start(self): def start(self):
"""启动注册流程""" """Start Registration Process"""
try: try:
if self.setup_email(): if self.setup_email():
if self.register_cursor(): if self.register_cursor():
@@ -230,7 +234,7 @@ class CursorRegistration:
return True return True
return False return False
finally: finally:
# 关闭邮箱标签页 # Close email tab
if hasattr(self, 'temp_email'): if hasattr(self, 'temp_email'):
try: try:
self.temp_email.close() self.temp_email.close()
@@ -238,7 +242,7 @@ class CursorRegistration:
pass pass
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None): def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
"""更新Cursor的认证信息的便捷函数""" """Update Cursor Auth Info"""
auth_manager = CursorAuth(translator=self.translator) auth_manager = CursorAuth(translator=self.translator)
return auth_manager.update_auth(email, access_token, refresh_token) return auth_manager.update_auth(email, access_token, refresh_token)

View File

@@ -2,8 +2,6 @@ import os
from colorama import Fore, Style, init from colorama import Fore, Style, init
import time import time
import random import random
from browser import BrowserManager
from control import BrowserControl
from cursor_auth import CursorAuth from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter from reset_machine_manual import MachineIDResetter
@@ -35,7 +33,6 @@ class CursorRegistration:
self.translator = translator self.translator = translator
# Set to display mode # Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False' os.environ['BROWSER_HEADLESS'] = 'False'
self.browser_manager = BrowserManager()
self.browser = None self.browser = None
self.controller = None self.controller = None
self.sign_up_url = "https://authenticator.cursor.sh/sign-up" self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
@@ -44,25 +41,33 @@ class CursorRegistration:
self.signup_tab = None self.signup_tab = None
self.email_tab = None self.email_tab = None
# Account information # Generate account information
self.password = self._generate_password() self.password = self._generate_password()
self.first_name = self._generate_name() # Generate first name and last name separately
self.last_name = self._generate_name() first_name = random.choice([
print(f"Password: {self.password}\n") "James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
print(f"First Name: {self.first_name}\n") "Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
print(f"Last Name: {self.last_name}\n") "Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
])
self.last_name = random.choice([
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
])
# Modify first letter of first name
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
self.first_name = new_first_letter + first_name[1:]
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
def _generate_password(self, length=12): def _generate_password(self, length=12):
"""Generate Random Password""" """Generate Random Password"""
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length)) return ''.join(random.choices(chars, k=length))
def _generate_name(self, length=6):
"""Generate Random Name"""
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rest_letters = ''.join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length-1))
return first_letter + rest_letters
def setup_email(self): def setup_email(self):
"""Setup Email""" """Setup Email"""
try: try:
@@ -73,7 +78,7 @@ class CursorRegistration:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}")
return False return False
print(f"Email Address: {self.email_address}\n") print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}\n{Style.RESET_ALL}")
return True return True
except Exception as e: except Exception as e:

View File

@@ -4,6 +4,7 @@ import platform
import shutil import shutil
from colorama import Fore, Style, init from colorama import Fore, Style, init
import subprocess import subprocess
from config import get_config
# Initialize colorama # Initialize colorama
init() init()
@@ -24,11 +25,24 @@ class AutoUpdateDisabler:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
self.system = platform.system() self.system = platform.system()
self.updater_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"), # 从配置文件获取路径
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"), config = get_config(translator)
"Linux": os.path.expanduser("~/.config/cursor-updater") if config:
} if self.system == "Windows":
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
elif self.system == "Darwin":
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
elif self.system == "Linux":
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
else:
# 如果配置加载失败,使用默认路径
self.updater_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
"Linux": os.path.expanduser("~/.config/cursor-updater")
}
self.updater_path = self.updater_paths.get(self.system)
def _kill_cursor_processes(self): def _kill_cursor_processes(self):
"""End all Cursor processes""" """End all Cursor processes"""
@@ -50,7 +64,7 @@ class AutoUpdateDisabler:
def _remove_updater_directory(self): def _remove_updater_directory(self):
"""Delete updater directory""" """Delete updater directory"""
try: try:
updater_path = self.updater_paths.get(self.system) updater_path = self.updater_path
if not updater_path: if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
@@ -72,7 +86,7 @@ class AutoUpdateDisabler:
def _create_blocking_file(self): def _create_blocking_file(self):
"""Create blocking file""" """Create blocking file"""
try: try:
updater_path = self.updater_paths.get(self.system) updater_path = self.updater_path
if not updater_path: if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")

View File

@@ -68,7 +68,24 @@
"version_less_than_0_45": "Cursor Version < 0.45.0, Skip Patching getMachineId", "version_less_than_0_45": "Cursor Version < 0.45.0, Skip Patching getMachineId",
"detecting_version": "Detecting Cursor Version", "detecting_version": "Detecting Cursor Version",
"patching_getmachineid": "Patching getMachineId", "patching_getmachineid": "Patching getMachineId",
"version_greater_than_0_45": "Cursor Version >= 0.45.0, Patching getMachineId" "version_greater_than_0_45": "Cursor Version >= 0.45.0, Patching getMachineId",
"permission_denied": "Permission Denied: {error}",
"backup_created": "Backup Created",
"update_success": "Update Success",
"update_failed": "Update Failed: {error}",
"windows_machine_guid_updated": "Windows Machine GUID Updated Successfully",
"reading_package_json": "Reading package.json {path}",
"invalid_json_object": "Invalid JSON Object",
"no_version_field": "No Version Field Found in package.json",
"version_field_empty": "Version Field is Empty",
"invalid_version_format": "Invalid Version Format: {version}",
"found_version": "Found Version: {version}",
"version_parse_error": "Version Parse Error: {error}",
"package_not_found": "Package.json Not Found: {path}",
"check_version_failed": "Check Version Failed: {error}",
"stack_trace": "Stack Trace",
"version_too_low": "Cursor Version Too Low: {version} < 0.45.0"
}, },
"register": { "register": {
"title": "Cursor Registration Tool", "title": "Cursor Registration Tool",
@@ -131,7 +148,21 @@
"register_process_error": "Register Process Error: {error}", "register_process_error": "Register Process Error: {error}",
"setting_password": "Setting Password", "setting_password": "Setting Password",
"manual_code_input": "Manual Code Input", "manual_code_input": "Manual Code Input",
"manual_email_input": "Manual Email Input" "manual_email_input": "Manual Email Input",
"password": "Password",
"first_name": "First Name",
"last_name": "Last Name",
"exit_signal": "Exit Signal",
"email_address": "Email Address",
"config_created": "Config Created",
"verification_failed": "Verification Failed",
"verification_error": "Verification Error: {error}",
"config_option_added": "Config Option Added: {option}",
"config_updated": "Config Updated",
"password_submitted": "Password Submitted",
"total_usage": "Total Usage: {usage}",
"setting_on_password": "Setting Password",
"getting_code": "Getting Verification Code, Will Try in 60s"
}, },
"auth": { "auth": {
"title": "Cursor Auth Manager", "title": "Cursor Auth Manager",
@@ -219,7 +250,8 @@
"blocked_domains_loaded_timeout": "Blocked Domains Loaded Timeout: {timeout}s", "blocked_domains_loaded_timeout": "Blocked Domains Loaded Timeout: {timeout}s",
"blocked_domains_loaded_timeout_error": "Blocked Domains Loaded Timeout Error: {error}", "blocked_domains_loaded_timeout_error": "Blocked Domains Loaded Timeout Error: {error}",
"available_domains_loaded": "Available Domains Loaded: {count}", "available_domains_loaded": "Available Domains Loaded: {count}",
"domains_filtered": "Domains Filtered: {count}" "domains_filtered": "Domains Filtered: {count}",
"trying_to_create_email": "Trying to create email: {email}"
}, },
"update": { "update": {
"title": "Disable Cursor Auto Update", "title": "Disable Cursor Auto Update",
@@ -240,6 +272,9 @@
"updating": "Updating to the latest version. The program will restart automatically.", "updating": "Updating to the latest version. The program will restart automatically.",
"up_to_date": "You are using the latest version.", "up_to_date": "You are using the latest version.",
"check_failed": "Failed to check for updates: {error}", "check_failed": "Failed to check for updates: {error}",
"continue_anyway": "Continuing with current version..." "continue_anyway": "Continuing with current version...",
"update_confirm": "Do you want to update to the latest version? (Y/n)",
"update_skipped": "Skipping update.",
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'."
} }
} }

View File

@@ -68,7 +68,23 @@
"version_less_than_0_45": "Cursor版本 < 0.45.0跳过getMachineId修补", "version_less_than_0_45": "Cursor版本 < 0.45.0跳过getMachineId修补",
"detecting_version": "检测Cursor版本", "detecting_version": "检测Cursor版本",
"patching_getmachineid": "修补getMachineId", "patching_getmachineid": "修补getMachineId",
"version_greater_than_0_45": "Cursor版本 >= 0.45.0修补getMachineId" "version_greater_than_0_45": "Cursor版本 >= 0.45.0修补getMachineId",
"permission_denied": "权限拒绝: {error}",
"backup_created": "备份已创建",
"update_success": "更新成功",
"update_failed": "更新失败: {error}",
"windows_machine_guid_updated": "Windows机器GUID更新成功",
"reading_package_json": "读取package.json {path}",
"invalid_json_object": "JSON对象无效",
"no_version_field": "package.json中没有版本字段",
"version_field_empty": "版本字段为空",
"invalid_version_format": "版本格式无效: {version}",
"found_version": "找到版本: {version}",
"version_parse_error": "版本解析错误: {error}",
"package_not_found": "package.json未找到: {path}",
"check_version_failed": "检查版本失败: {error}",
"stack_trace": "堆栈跟踪",
"version_too_low": "Cursor版本太低: {version} < 0.45.0"
}, },
"register": { "register": {
"title": "Cursor 注册工具", "title": "Cursor 注册工具",
@@ -131,7 +147,21 @@
"update_cursor_auth_info": "更新Cursor认证信息", "update_cursor_auth_info": "更新Cursor认证信息",
"setting_password": "设置密码", "setting_password": "设置密码",
"manual_code_input": "手动输入验证码", "manual_code_input": "手动输入验证码",
"manual_email_input": "手动输入邮箱" "manual_email_input": "手动输入邮箱",
"password": "密码",
"first_name": "名字",
"last_name": "姓氏",
"exit_signal": "退出信号",
"email_address": "邮箱地址",
"config_created": "配置已创建",
"verification_failed": "验证失败",
"verification_error": "验证错误: {error}",
"config_option_added": "配置项已添加: {option}",
"config_updated": "配置已更新",
"password_submitted": "密码已提交",
"total_usage": "总使用量: {usage}",
"setting_on_password": "设置密码",
"getting_code": "获取验证码将在60秒内尝试..."
}, },
"auth": { "auth": {
"title": "Cursor 认证管理器", "title": "Cursor 认证管理器",
@@ -216,7 +246,8 @@
"blocked_domains_loaded_timeout": "加载被屏蔽的域名超时: {timeout}秒", "blocked_domains_loaded_timeout": "加载被屏蔽的域名超时: {timeout}秒",
"blocked_domains_loaded_timeout_error": "加载被屏蔽的域名超时错误: {error}", "blocked_domains_loaded_timeout_error": "加载被屏蔽的域名超时错误: {error}",
"available_domains_loaded": "获取到 {count} 个可用域名", "available_domains_loaded": "获取到 {count} 个可用域名",
"domains_filtered": "过滤后剩餘 {count} 個可用域名" "domains_filtered": "过滤后剩餘 {count} 個可用域名",
"trying_to_create_email": "尝试创建邮箱: {email}"
}, },
"update": { "update": {
"title": "禁用 Cursor 自动更新", "title": "禁用 Cursor 自动更新",
@@ -237,6 +268,9 @@
"updating": "正在更新到最新版本。程序将自动重启。", "updating": "正在更新到最新版本。程序将自动重启。",
"up_to_date": "您使用的是最新版本。", "up_to_date": "您使用的是最新版本。",
"check_failed": "检查更新失败: {error}", "check_failed": "检查更新失败: {error}",
"continue_anyway": "继续使用当前版本..." "continue_anyway": "继续使用当前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'."
} }
} }

View File

@@ -68,8 +68,25 @@
"version_less_than_0_45": "Cursor版本 < 0.45.0跳过getMachineId修补", "version_less_than_0_45": "Cursor版本 < 0.45.0跳过getMachineId修补",
"detecting_version": "檢測Cursor版本", "detecting_version": "檢測Cursor版本",
"patching_getmachineid": "修補getMachineId", "patching_getmachineid": "修補getMachineId",
"version_greater_than_0_45": "Cursor版本 >= 0.45.0修補getMachineId" "version_greater_than_0_45": "Cursor版本 >= 0.45.0修補getMachineId",
"permission_denied": "權限拒絕: {error}",
"backup_created": "備份已創建",
"update_success": "更新成功",
"update_failed": "更新失敗: {error}",
"windows_machine_guid_updated": "Windows機器GUID更新成功",
"reading_package_json": "讀取package.json {path}",
"invalid_json_object": "JSON對象無效",
"no_version_field": "package.json中沒有版本字段",
"version_field_empty": "版本字段為空",
"invalid_version_format": "版本格式無效: {version}",
"found_version": "找到版本: {version}",
"version_parse_error": "版本解析錯誤: {error}",
"package_not_found": "package.json未找到: {path}",
"check_version_failed": "檢查版本失敗: {error}",
"stack_trace": "堆疊跟踪",
"version_too_low": "Cursor版本太低: {version} < 0.45.0"
}, },
"register": { "register": {
"title": "Cursor 註冊工具", "title": "Cursor 註冊工具",
"start": "正在啟動註冊流程...", "start": "正在啟動註冊流程...",
@@ -112,7 +129,21 @@
"update_cursor_auth_info": "更新Cursor認證信息", "update_cursor_auth_info": "更新Cursor認證信息",
"setting_password": "設置密碼", "setting_password": "設置密碼",
"manual_code_input": "手動輸入驗證碼", "manual_code_input": "手動輸入驗證碼",
"manual_email_input": "手動輸入郵箱地址" "manual_email_input": "手動輸入郵箱地址",
"password": "密碼",
"first_name": "名字",
"last_name": "姓氏",
"exit_signal": "退出信號",
"email_address": "郵箱地址",
"config_created": "配置已創建",
"verification_failed": "驗證失敗",
"verification_error": "驗證錯誤: {error}",
"config_option_added": "配置項已添加: {option}",
"config_updated": "配置已更新",
"password_submitted": "密碼已提交",
"total_usage": "總使用量: {usage}",
"setting_on_password": "設置密碼",
"getting_code": "正在獲取驗證碼將在60秒內嘗試..."
}, },
"auth": { "auth": {
"title": "Cursor 認證管理器", "title": "Cursor 認證管理器",
@@ -197,7 +228,8 @@
"blocked_domains_loaded_timeout": "加載被屏蔽的域名超時: {timeout}秒", "blocked_domains_loaded_timeout": "加載被屏蔽的域名超時: {timeout}秒",
"blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}", "blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}",
"available_domains_loaded": "獲取到 {count} 個可用域名", "available_domains_loaded": "獲取到 {count} 個可用域名",
"domains_filtered": "過濾後剩餘 {count} 個可用域名" "domains_filtered": "過濾後剩餘 {count} 個可用域名",
"trying_to_create_email": "嘗試創建郵箱: {email}"
}, },
"update": { "update": {
"title": "禁用 Cursor 自动更新", "title": "禁用 Cursor 自动更新",
@@ -218,6 +250,9 @@
"updating": "正在更新到最新版本。程序將自動重啟。", "updating": "正在更新到最新版本。程序將自動重啟。",
"up_to_date": "您使用的是最新版本。", "up_to_date": "您使用的是最新版本。",
"check_failed": "檢查更新失敗: {error}", "check_failed": "檢查更新失敗: {error}",
"continue_anyway": "繼續使用當前版本..." "continue_anyway": "繼續使用當前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'."
} }
} }

93
main.py
View File

@@ -9,6 +9,7 @@ import locale
import platform import platform
import requests import requests
import subprocess import subprocess
from config import get_config
# 只在 Windows 系统上导入 windll # 只在 Windows 系统上导入 windll
if platform.system() == 'Windows': if platform.system() == 'Windows':
@@ -210,33 +211,99 @@ def check_latest_version():
try: try:
print(f"\n{Fore.CYAN}{EMOJI['UPDATE']} {translator.get('updater.checking')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}{EMOJI['UPDATE']} {translator.get('updater.checking')}{Style.RESET_ALL}")
# Get latest version from GitHub API with timeout # Get latest version from GitHub API with timeout and proper headers
response = requests.get("https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest", timeout=5) headers = {
latest_version = response.json()["tag_name"].lstrip('v') 'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'CursorFreeVIP-Updater'
}
response = requests.get(
"https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest",
headers=headers,
timeout=10
)
# Check if response is successful
if response.status_code != 200:
raise Exception(f"GitHub API returned status code {response.status_code}")
response_data = response.json()
if "tag_name" not in response_data:
raise Exception("No version tag found in GitHub response")
latest_version = response_data["tag_name"].lstrip('v')
# Validate version format
if not latest_version:
raise Exception("Invalid version format received")
if latest_version != version: if latest_version != version:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}") print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
# Execute update command based on platform # 詢問用戶是否要更新
if platform.system() == 'Windows': while True:
update_command = 'irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex' choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('updater.update_confirm', choices='Y/n')}: {Style.RESET_ALL}").lower()
subprocess.run(['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', update_command], check=True) if choice in ['', 'y', 'yes']:
else: break
update_command = 'curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh -o install.sh && chmod +x install.sh && ./install.sh' elif choice in ['n', 'no']:
subprocess.Popen(update_command, shell=True) print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.update_skipped')}{Style.RESET_ALL}")
return
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.updating')}{Style.RESET_ALL}") try:
sys.exit(0) # Execute update command based on platform
if platform.system() == 'Windows':
update_command = 'irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex'
subprocess.run(['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', update_command], check=True)
else:
# For Linux/Mac, download and execute the install script
install_script_url = 'https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh'
# First verify the script exists
script_response = requests.get(install_script_url, timeout=5)
if script_response.status_code != 200:
raise Exception("Installation script not found")
# Save and execute the script
with open('install.sh', 'wb') as f:
f.write(script_response.content)
os.chmod('install.sh', 0o755) # Make executable
subprocess.run(['./install.sh'], check=True)
# Clean up
if os.path.exists('install.sh'):
os.remove('install.sh')
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.updating')}{Style.RESET_ALL}")
sys.exit(0)
except Exception as update_error:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.update_failed', error=str(update_error))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.manual_update_required')}{Style.RESET_ALL}")
return
else: else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.up_to_date')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.up_to_date')}{Style.RESET_ALL}")
except requests.exceptions.RequestException as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.network_error', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}")
return
except Exception as e: except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.check_failed', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.check_failed', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}") print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}")
return # Continue with the program instead of blocking return
def main(): def main():
print_logo() print_logo()
# 初始化配置
config = get_config(translator)
if not config:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
return
check_latest_version() # Add version check before showing menu check_latest_version() # Add version check before showing menu
print_menu() print_menu()

View File

@@ -4,6 +4,10 @@ import os
import signal import signal
import random import random
from colorama import Fore, Style from colorama import Fore, Style
import configparser
from pathlib import Path
import sys
from config import get_config
# 在文件开头添加全局变量 # 在文件开头添加全局变量
_translator = None _translator = None
@@ -34,22 +38,20 @@ def signal_handler(signum, frame):
cleanup_chrome_processes(_translator) cleanup_chrome_processes(_translator)
os._exit(0) os._exit(0)
def simulate_human_input(page, url, translator=None): def simulate_human_input(page, url, config, translator=None):
"""访问网址""" """访问网址"""
if translator: if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}") print(f"{Fore.CYAN}🚀 {translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
else:
print("正在访问网址...")
# 先访问空白页面 # 先访问空白页面
page.get('about:blank') page.get('about:blank')
time.sleep(random.uniform(1.0, 2.0)) time.sleep(get_random_wait_time(config, 'page_load_wait'))
# 访问目标页面 # 访问目标页面
page.get(url) page.get(url)
time.sleep(random.uniform(2.0, 3.0)) # 等待页面加载 time.sleep(get_random_wait_time(config, 'page_load_wait'))
def fill_signup_form(page, first_name, last_name, email, translator=None): def fill_signup_form(page, first_name, last_name, email, config, translator=None):
"""填写注册表单""" """填写注册表单"""
try: try:
if translator: if translator:
@@ -61,25 +63,25 @@ def fill_signup_form(page, first_name, last_name, email, translator=None):
first_name_input = page.ele("@name=first_name") first_name_input = page.ele("@name=first_name")
if first_name_input: if first_name_input:
first_name_input.input(first_name) first_name_input.input(first_name)
time.sleep(random.uniform(0.5, 1.0)) time.sleep(get_random_wait_time(config, 'input_wait'))
# 填写姓氏 # 填写姓氏
last_name_input = page.ele("@name=last_name") last_name_input = page.ele("@name=last_name")
if last_name_input: if last_name_input:
last_name_input.input(last_name) last_name_input.input(last_name)
time.sleep(random.uniform(0.5, 1.0)) time.sleep(get_random_wait_time(config, 'input_wait'))
# 填写邮箱 # 填写邮箱
email_input = page.ele("@name=email") email_input = page.ele("@name=email")
if email_input: if email_input:
email_input.input(email) email_input.input(email)
time.sleep(random.uniform(0.5, 1.0)) time.sleep(get_random_wait_time(config, 'input_wait'))
# 点击提交按钮 # 点击提交按钮
submit_button = page.ele("@type=submit") submit_button = page.ele("@type=submit")
if submit_button: if submit_button:
submit_button.click() submit_button.click()
time.sleep(random.uniform(2.0, 3.0)) time.sleep(get_random_wait_time(config, 'submit_wait'))
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.form_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.form_success')}{Style.RESET_ALL}")
@@ -94,40 +96,132 @@ def fill_signup_form(page, first_name, last_name, email, translator=None):
print(f"填写表单时出错: {e}") print(f"填写表单时出错: {e}")
return False return False
def setup_driver(translator=None): def get_default_chrome_path():
"""设置浏览器驱动""" """Get default Chrome path"""
co = ChromiumOptions() if sys.platform == "win32":
paths = [
# 使用无痕模式 os.path.join(os.environ.get('PROGRAMFILES', ''), 'Google/Chrome/Application/chrome.exe'),
co.set_argument("--incognito") os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'Google/Chrome/Application/chrome.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google/Chrome/Application/chrome.exe')
# 设置随机端口 ]
co.auto_port() elif sys.platform == "darwin":
paths = [
# 使用有头模式(一定要设置为False模擬人類操作) "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
co.headless(False) ]
else: # Linux
paths = [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable"
]
for path in paths:
if os.path.exists(path):
return path
return ""
def get_user_documents_path():
"""Get user Documents folder path"""
if sys.platform == "win32":
return os.path.join(os.path.expanduser("~"), "Documents")
elif sys.platform == "darwin":
return os.path.join(os.path.expanduser("~"), "Documents")
else: # Linux
# Get actual user's home directory
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
return os.path.join("/home", sudo_user, "Documents")
return os.path.join(os.path.expanduser("~"), "Documents")
def get_random_wait_time(config, timing_type='page_load_wait'):
"""
Get random wait time from config
Args:
config: ConfigParser object
timing_type: Type of timing to get (page_load_wait, input_wait, submit_wait)
Returns:
float: Random wait time or fixed time
"""
try: try:
# 加载插件 if not config.has_section('Timing'):
extension_path = os.path.join(os.getcwd(), "turnstilePatch") return random.uniform(0.1, 0.8) # 默认值
if os.path.exists(extension_path):
co.set_argument("--allow-extensions-in-incognito") if timing_type == 'random':
co.add_extension(extension_path) min_time = float(config.get('Timing', 'min_random_time', fallback='0.1'))
max_time = float(config.get('Timing', 'max_random_time', fallback='0.8'))
return random.uniform(min_time, max_time)
time_value = config.get('Timing', timing_type, fallback='0.1-0.8')
# 检查是否为固定时间值
if '-' not in time_value and ',' not in time_value:
return float(time_value) # 返回固定时间
# 处理范围时间
min_time, max_time = map(float, time_value.split('-' if '-' in time_value else ','))
return random.uniform(min_time, max_time)
except:
return random.uniform(0.1, 0.8) # 出错时返回默认值
def setup_driver(translator=None):
"""Setup browser driver"""
try:
# 获取配置
config = get_config(translator)
# Get Chrome path
chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path())
if not chrome_path or not os.path.exists(chrome_path):
if translator:
print(f"{Fore.YELLOW}⚠️ {translator.get('register.chrome_path_invalid') if translator else 'Chrome路径无效使用默认路径'}{Style.RESET_ALL}")
chrome_path = get_default_chrome_path()
# Set browser options
co = ChromiumOptions()
# Set Chrome path
co.set_browser_path(chrome_path)
# Use incognito mode
co.set_argument("--incognito")
# 设置随机端口
co.set_argument("--no-sandbox")
# 设置随机端口
co.auto_port()
# 使用有头模式(一定要设置为False模拟人类操作)
co.headless(False)
try:
# 加载插件
extension_path = os.path.join(os.getcwd(), "turnstilePatch")
if os.path.exists(extension_path):
co.set_argument("--allow-extensions-in-incognito")
co.add_extension(extension_path)
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.extension_load_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"加载插件失败: {e}")
if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.starting_browser')}{Style.RESET_ALL}")
else:
print("正在启动浏览器...")
page = ChromiumPage(co)
return config, page
except Exception as e: except Exception as e:
if translator: if translator:
print(f"{Fore.RED}{translator.get('register.extension_load_error', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.browser_setup_error', error=str(e))}{Style.RESET_ALL}")
else: else:
print(f"加载插件失败: {e}") print(f"设置浏览器时出错: {e}")
raise
if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.starting_browser')}{Style.RESET_ALL}")
else:
print("正在启动浏览器...")
page = ChromiumPage(co)
return page
def handle_turnstile(page, translator=None): def handle_turnstile(page, config, translator=None):
"""处理 Turnstile 验证""" """处理 Turnstile 验证"""
try: try:
if translator: if translator:
@@ -135,6 +229,16 @@ def handle_turnstile(page, translator=None):
else: else:
print("\n正在处理 Turnstile 验证...") print("\n正在处理 Turnstile 验证...")
# from config
turnstile_time = float(config.get('Turnstile', 'handle_turnstile_time', fallback='2'))
random_time_str = config.get('Turnstile', 'handle_turnstile_random_time', fallback='1-3')
# 解析随机时间范围
try:
min_time, max_time = map(float, random_time_str.split('-'))
except:
min_time, max_time = 1, 3 # 默认值
max_retries = 2 max_retries = 2
retry_count = 0 retry_count = 0
@@ -148,7 +252,7 @@ def handle_turnstile(page, translator=None):
try: try:
# 尝试重置 turnstile # 尝试重置 turnstile
page.run_js("try { turnstile.reset() } catch(e) { }") page.run_js("try { turnstile.reset() } catch(e) { }")
time.sleep(2) time.sleep(turnstile_time) # from config
# 定位验证框元素 # 定位验证框元素
challenge_check = ( challenge_check = (
@@ -165,12 +269,12 @@ def handle_turnstile(page, translator=None):
else: else:
print("检测到验证框...") print("检测到验证框...")
# 随机延时后点击验证 # from config
time.sleep(random.uniform(1, 3)) time.sleep(random.uniform(min_time, max_time))
challenge_check.click() challenge_check.click()
time.sleep(2) time.sleep(turnstile_time) # from config
# 检查验证结果 # check verification result
if check_verification_success(page, translator): if check_verification_success(page, translator):
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
@@ -192,7 +296,7 @@ def handle_turnstile(page, translator=None):
print("验证通过!") print("验证通过!")
return True return True
time.sleep(random.uniform(1, 2)) time.sleep(random.uniform(min_time, max_time))
if translator: if translator:
print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}")
@@ -237,43 +341,39 @@ def generate_password(length=12):
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length)) return ''.join(random.choices(chars, k=length))
def fill_password(page, password, translator=None): def fill_password(page, password: str, config, translator=None):
"""填写密码""" """
填写密码表单
"""
try: try:
if translator: print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password')}{Style.RESET_ALL}")
else: # 填写密码
print(f"\n{translator.get('register.setting_password')}")
password_input = page.ele("@name=password") password_input = page.ele("@name=password")
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_on_password')}: {password}{Style.RESET_ALL}")
if password_input: if password_input:
password_input.input(password) password_input.input(password)
time.sleep(random.uniform(0.5, 1.0))
# 点击提交按钮
submit_button = page.ele("@type=submit") submit_button = page.ele("@type=submit")
if submit_button: if submit_button:
submit_button.click() submit_button.click()
time.sleep(random.uniform(2.0, 3.0)) time.sleep(get_random_wait_time(config, 'submit_wait'))
if translator:
print(f"{Fore.GREEN}{translator.get('register.password_success')}{Style.RESET_ALL}")
else:
print(f"{translator.get('register.password_success')}: {password}")
return True
print(f"{Fore.GREEN}{translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}")
return True
except Exception as e: except Exception as e:
if translator: print(f"{Fore.RED}{translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.RED}{translator.get('register.password_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{translator.get('register.password_error')}: {e}")
return False return False
def handle_verification_code(browser_tab, email_tab, controller, email, password, translator=None): def handle_verification_code(browser_tab, email_tab, controller, config, translator=None):
"""处理验证码""" """处理验证码"""
try: try:
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
else:
print(f"\n{translator.get('register.waiting_for_verification_code')}")
# 检查是否使用手动输入验证码 # 检查是否使用手动输入验证码
if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式 if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式
@@ -282,35 +382,33 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
# 在注册页面填写验证码 # 在注册页面填写验证码
for i, digit in enumerate(verification_code): for i, digit in enumerate(verification_code):
browser_tab.ele(f"@data-index={i}").input(digit) browser_tab.ele(f"@data-index={i}").input(digit)
time.sleep(random.uniform(0.1, 0.3)) time.sleep(get_random_wait_time(config, 'verification_code_input'))
print(f"{translator.get('register.verification_success')}") print(f"{translator.get('register.verification_success')}")
time.sleep(3) time.sleep(get_random_wait_time(config, 'verification_success_wait'))
# 处理最后一次 Turnstile 验证 # 处理最后一次 Turnstile 验证
if handle_turnstile(browser_tab, translator): if handle_turnstile(browser_tab, config, translator):
if translator: if translator:
print(f"{translator.get('register.verification_success')}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
print(f"{translator.get('register.verification_success')}")
time.sleep(2)
# 访问设置页面 # 访问设置页面
print(f"{translator.get('register.visiting_url')}: https://www.cursor.com/settings") print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
browser_tab.get("https://www.cursor.com/settings") browser_tab.get("https://www.cursor.com/settings")
time.sleep(3) # 等待页面加载 time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
return True, browser_tab return True, browser_tab
return False, None return False, None
# 自动获取验证码逻辑 # 自动获取验证码逻辑
elif email_tab: elif email_tab:
print(f"{translator.get('register.waiting_for_verification_code')}") print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
time.sleep(5) # 等待验证码邮件 time.sleep(get_random_wait_time(config, 'email_check_initial_wait'))
# 使用已有的 email_tab 刷新邮箱 # 使用已有的 email_tab 刷新邮箱
email_tab.refresh_inbox() email_tab.refresh_inbox()
time.sleep(3) time.sleep(get_random_wait_time(config, 'email_refresh_wait'))
# 检查邮箱是否有验证码邮件 # 检查邮箱是否有验证码邮件
if email_tab.check_for_cursor_email(): if email_tab.check_for_cursor_email():
@@ -319,28 +417,23 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
# 在注册页面填写验证码 # 在注册页面填写验证码
for i, digit in enumerate(verification_code): for i, digit in enumerate(verification_code):
browser_tab.ele(f"@data-index={i}").input(digit) browser_tab.ele(f"@data-index={i}").input(digit)
time.sleep(random.uniform(0.1, 0.3)) time.sleep(get_random_wait_time(config, 'verification_code_input'))
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'verification_success_wait'))
print("验证码填写完成")
time.sleep(3)
# 处理最后一次 Turnstile 验证 # 处理最后一次 Turnstile 验证
if handle_turnstile(browser_tab, translator): if handle_turnstile(browser_tab, config, translator):
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
print("最后一次验证通过!")
time.sleep(2)
# 访问设置页面 # 访问设置页面
if translator: if translator:
print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}") print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
else:
print("访问设置页面...")
browser_tab.get("https://www.cursor.com/settings") browser_tab.get("https://www.cursor.com/settings")
time.sleep(3) # 等待页面加载 time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
return True, browser_tab return True, browser_tab
else: else:
@@ -353,69 +446,55 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
# 获取验证码,设置超时 # 获取验证码,设置超时
verification_code = None verification_code = None
max_attempts = 20 max_attempts = 20
retry_interval = 10 retry_interval = get_random_wait_time(config, 'retry_interval') # 使用 get_random_wait_time
start_time = time.time() start_time = time.time()
timeout = 160 timeout = float(config.get('Timing', 'max_timeout', fallback='160')) # 這個可以保持不變因為是固定值
if translator: if translator:
print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}") print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}")
else:
print("开始获取验证码...")
for attempt in range(max_attempts): for attempt in range(max_attempts):
# 检查是否超时 # 检查是否超时
if time.time() - start_time > timeout: if time.time() - start_time > timeout:
if translator: if translator:
print(f"{Fore.RED}{translator.get('register.verification_timeout')}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.verification_timeout')}{Style.RESET_ALL}")
else:
print("获取验证码超时...")
break break
verification_code = controller.get_verification_code() verification_code = controller.get_verification_code()
if verification_code: if verification_code:
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else:
print(f"成功获取验证码: {verification_code}")
break break
remaining_time = int(timeout - (time.time() - start_time)) remaining_time = int(timeout - (time.time() - start_time))
if translator: if translator:
print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}") print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}")
else:
print(f"{attempt + 1} 次尝试获取验证码,剩余时间: {remaining_time}秒...")
# 刷新邮箱 # 刷新邮箱
email_tab.refresh_inbox() email_tab.refresh_inbox()
time.sleep(retry_interval) time.sleep(retry_interval) # 使用 get_random_wait_time
if verification_code: if verification_code:
# 在注册页面填写验证码 # 在注册页面填写验证码
for i, digit in enumerate(verification_code): for i, digit in enumerate(verification_code):
browser_tab.ele(f"@data-index={i}").input(digit) browser_tab.ele(f"@data-index={i}").input(digit)
time.sleep(random.uniform(0.1, 0.3)) time.sleep(get_random_wait_time(config, 'verification_code_input'))
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'verification_success_wait'))
print("验证码填写完成")
time.sleep(3)
# 处理最后一次 Turnstile 验证 # 处理最后一次 Turnstile 验证
if handle_turnstile(browser_tab, translator): if handle_turnstile(browser_tab, config, translator):
if translator: if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}") print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
print("最后一次验证通过!")
time.sleep(2)
# 直接访问设置页面 # 直接访问设置页面
if translator: if translator:
print(f"{Fore.CYAN}{translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}") print(f"{Fore.CYAN}{translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
else:
print("访问设置页面...")
browser_tab.get("https://www.cursor.com/settings") browser_tab.get("https://www.cursor.com/settings")
time.sleep(3) # 等待页面加载 time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
# 直接返回成功,让 cursor_register.py 处理账户信息获取 # 直接返回成功,让 cursor_register.py 处理账户信息获取
return True, browser_tab return True, browser_tab
@@ -423,8 +502,6 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
else: else:
if translator: if translator:
print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}")
else:
print("最后一次验证失败")
return False, None return False, None
return False, None return False, None
@@ -432,8 +509,6 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
except Exception as e: except Exception as e:
if translator: if translator:
print(f"{Fore.RED}{translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"处理验证码时出错: {e}")
return False, None return False, None
def handle_sign_in(browser_tab, email, password, translator=None): def handle_sign_in(browser_tab, email, password, translator=None):
@@ -496,26 +571,18 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
page = None page = None
success = False success = False
try: try:
page = setup_driver(translator) config, page = setup_driver(translator)
if translator: if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}") print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}")
else:
print("浏览器已启动")
# 访问注册页面 # 访问注册页面
url = "https://authenticator.cursor.sh/sign-up" url = "https://authenticator.cursor.sh/sign-up"
if translator:
print(f"\n{Fore.CYAN}{translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
else:
print(f"\n正在访问: {url}")
# 访问页面 # 访问页面
simulate_human_input(page, url, translator) simulate_human_input(page, url, config, translator)
if translator: if translator:
print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}") print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
else: time.sleep(get_random_wait_time(config, 'page_load_wait'))
print("等待页面加载...")
time.sleep(5)
# 如果没有提供账号信息,则生成随机信息 # 如果没有提供账号信息,则生成随机信息
if not all([email, password, first_name, last_name]): if not all([email, password, first_name, last_name]):
@@ -532,44 +599,35 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
f.write(f"{'='*50}\n") f.write(f"{'='*50}\n")
# 填写表单 # 填写表单
if fill_signup_form(page, first_name, last_name, email, translator): if fill_signup_form(page, first_name, last_name, email, config, translator):
if translator: if translator:
print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}") print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}")
else:
print("\n表单已提交,开始验证...")
# 处理第一次 Turnstile 验证 # 处理第一次 Turnstile 验证
if handle_turnstile(page, translator): if handle_turnstile(page, config, translator):
if translator: if translator:
print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}") print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}")
else:
print("\n第一阶段验证通过!")
# 填写密码 # 填写密码
if fill_password(page, password, translator): if fill_password(page, password, config, translator):
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
else:
print("\n等待第二次验证...")
time.sleep(2)
# 处理第二次 Turnstile 验证 # 处理第二次 Turnstile 验证
if handle_turnstile(page, translator): if handle_turnstile(page, config, translator):
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
else: if handle_verification_code(page, email_tab, controller, config, translator):
print("\n开始处理验证码...")
if handle_verification_code(page, email_tab, controller, email, password, translator):
success = True success = True
return True, page # 返回浏览器实例 return True, page
else: else:
print("\n验证码处理失败") print(f"\n{Fore.RED}{translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}")
else: else:
print("\n第二次验证失败") print(f"\n{Fore.RED}{translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
else: else:
print("\n密码设置失败") print(f"\n{Fore.RED}{translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
else: else:
print("\n第一次验证失败") print(f"\n{Fore.RED}{translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}")
return False, None return False, None

View File

@@ -7,7 +7,7 @@ import requests
import random import random
import string import string
# 初始化 colorama # Initialize colorama
init() init()
class NewTempEmail: class NewTempEmail:
@@ -34,7 +34,7 @@ class NewTempEmail:
# Split text and remove empty lines # Split text and remove empty lines
domains = [line.strip() for line in response.text.split('\n') if line.strip()] domains = [line.strip() for line in response.text.split('\n') if line.strip()]
if self.translator: if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.blocked_domains_loaded', count=len(domains))}{Style.RESET_ALL}") print(f"{Fore.CYAN} {self.translator.get('email.blocked_domains_loaded', count=len(domains))}{Style.RESET_ALL}")
else: else:
print(f"{Fore.CYAN} 已加载 {len(domains)} 个被屏蔽的域名{Style.RESET_ALL}") print(f"{Fore.CYAN} 已加载 {len(domains)} 个被屏蔽的域名{Style.RESET_ALL}")
return domains return domains
@@ -66,153 +66,179 @@ class NewTempEmail:
return filtered_domains return filtered_domains
def _generate_credentials(self): def _generate_credentials(self):
"""生成随机用户名和密码""" """generate random username and password"""
username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10)) username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12)) password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12))
return username, password return username, password
def create_email(self): def create_email(self):
"""创建临时邮箱""" """create temporary email"""
try: max_retries = 3 # Maximum number of retries
if self.translator: attempt = 0 # Current attempt count
print(f"{Fore.CYAN} {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}")
else: while attempt < max_retries:
print(f"{Fore.CYAN} 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}") attempt += 1
# 获取可用域名列表
try: try:
domains_response = requests.get(f"{self.api_url}/domains", timeout=10)
if domains_response.status_code != 200:
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.status_code)}{Style.RESET_ALL}")
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.text)}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_available_domains') if self.translator else 'Failed to get available domains'}")
domains = domains_response.json()["hydra:member"]
print(f"{Fore.CYAN} {self.translator.get('email.available_domains_loaded', count=len(domains))}{Style.RESET_ALL}")
if not domains:
raise Exception(f"{self.translator.get('email.no_available_domains') if self.translator else '没有可用域名'}")
except Exception as e:
print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}")
raise
# 排除被屏蔽的域名
try:
filtered_domains = self.exclude_blocked_domains(domains)
if self.translator: if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}") print(f"{Fore.CYAN} {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}")
else: else:
print(f"{Fore.CYAN} 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}") print(f"{Fore.CYAN} 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}")
if not filtered_domains: # Get available domain list
try:
domains_response = requests.get(f"{self.api_url}/domains", timeout=10)
if domains_response.status_code != 200:
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.status_code)}{Style.RESET_ALL}")
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.text)}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_available_domains') if self.translator else 'Failed to get available domains'}")
domains = domains_response.json()["hydra:member"]
print(f"{Fore.CYAN} {self.translator.get('email.available_domains_loaded', count=len(domains))}{Style.RESET_ALL}")
if not domains:
raise Exception(f"{self.translator.get('email.no_available_domains') if self.translator else '没有可用域名'}")
except Exception as e:
print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}")
raise
# Exclude blocked domains
try:
filtered_domains = self.exclude_blocked_domains(domains)
if self.translator: if self.translator:
print(f"{Fore.RED} {self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}") print(f"{Fore.CYAN} {self.translator.get('email.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}")
else: else:
print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}") print(f"{Fore.CYAN} 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}")
# 切换到另一个服务 if not filtered_domains:
for service in self.services: if self.translator:
if service["api_url"] != self.api_url: print(f"{Fore.RED}{self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}")
self.selected_service = service else:
self.api_url = service["api_url"] print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}")
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}") # Switch to another service
else: for service in self.services:
print(f"{Fore.CYAN} 切换到 {service['name']} 服务{Style.RESET_ALL}") if service["api_url"] != self.api_url:
return self.create_email() # 递归调用 self.selected_service = service
self.api_url = service["api_url"]
raise Exception(f"{self.translator.get('email.no_available_domains_after_filtering') if self.translator else '过滤后没有可用域名'}") if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 切换到 {service['name']} 服务{Style.RESET_ALL}")
return self.create_email() # Recursively call
raise Exception(f"{self.translator.get('email.no_available_domains_after_filtering') if self.translator else '过滤后没有可用域名'}")
except Exception as e:
print(f"{Fore.RED}❌ 过滤域名时出错: {str(e)}{Style.RESET_ALL}")
raise
# Generate random username and password
try:
username, password = self._generate_credentials()
self.password = password
# Create email account
selected_domain = filtered_domains[0]['domain']
email = f"{username}@{selected_domain}"
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.trying_to_create_email', email=email)}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 尝试创建邮箱: {email}{Style.RESET_ALL}")
account_data = {
"address": email,
"password": password
}
except Exception as e:
print(f"{Fore.RED}❌ 生成凭据时出错: {str(e)}{Style.RESET_ALL}")
raise
# Create account
try:
create_response = requests.post(f"{self.api_url}/accounts", json=account_data, timeout=15)
if create_response.status_code != 201:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=create_response.status_code)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建账户失败: 状态码 {create_response.status_code}{Style.RESET_ALL}")
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=create_response.text)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 响应内容: {create_response.text}{Style.RESET_ALL}")
# If it's a domain problem, try the next available domain
if len(filtered_domains) > 1 and ("domain" in create_response.text.lower() or "address" in create_response.text.lower()):
print(f"{Fore.YELLOW}⚠️ 尝试使用下一个可用域名...{Style.RESET_ALL}")
# Add current domain to blocked list
if selected_domain not in self.blocked_domains:
self.blocked_domains.append(selected_domain)
# Recursively call yourself
return self.create_email()
raise Exception(f"{self.translator.get('email.failed_to_create_account') if self.translator else '创建账户失败'}")
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}")
raise
# Get access token
try:
token_data = {
"address": email,
"password": password
}
token_response = requests.post(f"{self.api_url}/token", json=token_data, timeout=10)
if token_response.status_code != 200:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_get_access_token', error=token_response.status_code)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 获取令牌失败: 状态码 {token_response.status_code}{Style.RESET_ALL}")
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_get_access_token', error=token_response.text)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 响应内容: {token_response.text}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_access_token') if self.translator else '获取访问令牌失败'}")
self.token = token_response.json()["token"]
self.email = email
except Exception as e:
print(f"{Fore.RED}❌ 获取令牌时出错: {str(e)}{Style.RESET_ALL}")
raise
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}")
return email
except Exception as e: except Exception as e:
print(f"{Fore.RED}❌ 过滤域名时出错: {str(e)}{Style.RESET_ALL}") if attempt < max_retries:
raise print(f"{Fore.YELLOW}⚠️ 尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}")
else:
# 生成随机用户名和密码 if self.translator:
try: print(f"{Fore.RED}{self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}")
username, password = self._generate_credentials() else:
self.password = password print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}")
return None
# 创建邮箱账户
selected_domain = filtered_domains[0]['domain']
email = f"{username}@{selected_domain}"
print(f"{Fore.CYAN} 尝试创建邮箱: {email}{Style.RESET_ALL}")
account_data = {
"address": email,
"password": password
}
except Exception as e:
print(f"{Fore.RED}❌ 生成凭据时出错: {str(e)}{Style.RESET_ALL}")
raise
# 创建账户
try:
create_response = requests.post(f"{self.api_url}/accounts", json=account_data, timeout=15)
if create_response.status_code != 201:
print(f"{Fore.RED}❌ 创建账户失败: 状态码 {create_response.status_code}{Style.RESET_ALL}")
print(f"{Fore.RED}❌ 响应内容: {create_response.text}{Style.RESET_ALL}")
# 如果是域名问题,尝试下一个域名
if len(filtered_domains) > 1 and ("domain" in create_response.text.lower() or "address" in create_response.text.lower()):
print(f"{Fore.YELLOW}⚠️ 尝试使用下一个可用域名...{Style.RESET_ALL}")
# 将当前域名添加到屏蔽列表
if selected_domain not in self.blocked_domains:
self.blocked_domains.append(selected_domain)
# 递归调用自己
return self.create_email()
raise Exception(f"{self.translator.get('email.failed_to_create_account') if self.translator else '创建账户失败'}")
except Exception as e:
print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}")
raise
# 获取访问令牌
try:
token_data = {
"address": email,
"password": password
}
token_response = requests.post(f"{self.api_url}/token", json=token_data, timeout=10)
if token_response.status_code != 200:
print(f"{Fore.RED}❌ 获取令牌失败: 状态码 {token_response.status_code}{Style.RESET_ALL}")
print(f"{Fore.RED}❌ 响应内容: {token_response.text}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_access_token') if self.translator else '获取访问令牌失败'}")
self.token = token_response.json()["token"]
self.email = email
except Exception as e:
print(f"{Fore.RED}❌ 获取令牌时出错: {str(e)}{Style.RESET_ALL}")
raise
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}")
return email
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}")
return None
def close(self): def close(self):
"""关闭浏览器""" """close browser"""
if self.page: if self.page:
self.page.quit() self.page.quit()
def refresh_inbox(self): def refresh_inbox(self):
"""刷新邮箱""" """refresh inbox"""
try: try:
if self.translator: if self.translator:
print(f"{Fore.CYAN}🔄 {self.translator.get('email.refreshing')}{Style.RESET_ALL}") print(f"{Fore.CYAN}🔄 {self.translator.get('email.refreshing')}{Style.RESET_ALL}")
else: else:
print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}") print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}")
# 使用 API 获取最新邮件 # Use API to get latest email
headers = {"Authorization": f"Bearer {self.token}"} headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers) response = requests.get(f"{self.api_url}/messages", headers=headers)
@@ -239,7 +265,7 @@ class NewTempEmail:
def check_for_cursor_email(self): def check_for_cursor_email(self):
"""检查是否有 Cursor 的验证邮件""" """检查是否有 Cursor 的验证邮件"""
try: try:
# 使用 API 获取邮件列表 # Use API to get email list
headers = {"Authorization": f"Bearer {self.token}"} headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers) response = requests.get(f"{self.api_url}/messages", headers=headers)
@@ -247,7 +273,7 @@ class NewTempEmail:
messages = response.json()["hydra:member"] messages = response.json()["hydra:member"]
for message in messages: for message in messages:
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
# 获取邮件内容 # Get email content
message_id = message["id"] message_id = message["id"]
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
if message_response.status_code == 200: if message_response.status_code == 200:
@@ -271,9 +297,9 @@ class NewTempEmail:
return False return False
def get_verification_code(self): def get_verification_code(self):
"""获取验证码""" """get verification code"""
try: try:
# 使用 API 获取邮件列表 # Use API to get email list
headers = {"Authorization": f"Bearer {self.token}"} headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers) response = requests.get(f"{self.api_url}/messages", headers=headers)
@@ -281,14 +307,14 @@ class NewTempEmail:
messages = response.json()["hydra:member"] messages = response.json()["hydra:member"]
for message in messages: for message in messages:
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]: if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
# 获取邮件内容 # Get email content
message_id = message["id"] message_id = message["id"]
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers) message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
if message_response.status_code == 200: if message_response.status_code == 200:
# 从邮件内容中提取验证码 # Extract verification code from email content
email_content = message_response.json()["text"] email_content = message_response.json()["text"]
# 查找6位数字验证码 # Find 6-digit verification code
import re import re
code_match = re.search(r'\b\d{6}\b', email_content) code_match = re.search(r'\b\d{6}\b', email_content)
@@ -324,7 +350,7 @@ def main(translator=None):
else: else:
print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}")
# 测试刷新功能 # Test refresh function
while True: while True:
if translator: if translator:
choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower() choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower()

View File

@@ -10,6 +10,10 @@ import re
import tempfile import tempfile
from colorama import Fore, Style, init from colorama import Fore, Style, init
from typing import Tuple from typing import Tuple
import configparser
from new_signup import get_user_documents_path
import traceback
from config import get_config
# Initialize colorama # Initialize colorama
init() init()
@@ -27,25 +31,105 @@ EMOJI = {
def get_cursor_paths(translator=None) -> Tuple[str, str]: def get_cursor_paths(translator=None) -> Tuple[str, str]:
""" Get Cursor related paths""" """ Get Cursor related paths"""
system = platform.system() system = platform.system()
# 讀取配置文件
config = configparser.ConfigParser()
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
if not os.path.exists(config_file):
raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件")
config.read(config_file, encoding='utf-8') # 指定編碼
# 根據系統獲取路徑
if system == "Darwin":
section = 'MacPaths'
elif system == "Windows":
section = 'WindowsPaths'
elif system == "Linux":
section = 'LinuxPaths'
else:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
if not config.has_section(section) or not config.has_option(section, 'cursor_path'):
raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑")
base_path = config.get(section, 'cursor_path')
if not os.path.exists(base_path):
raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}")
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out/main.js")
# 檢查文件是否存在
if not os.path.exists(pkg_path):
raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}")
if not os.path.exists(main_path):
raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}")
return (pkg_path, main_path)
def get_cursor_machine_id_path(translator=None) -> str:
"""
Get Cursor machineId file path based on operating system
Returns:
str: Path to machineId file
"""
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if os.path.exists(config_file):
config.read(config_file)
if sys.platform == "win32": # Windows
if not config.has_section('WindowsPaths'):
config.add_section('WindowsPaths')
config.set('WindowsPaths', 'machine_id_path',
os.path.join(os.getenv("APPDATA"), "Cursor", "machineId"))
return config.get('WindowsPaths', 'machine_id_path')
elif sys.platform == "linux": # Linux
if not config.has_section('LinuxPaths'):
config.add_section('LinuxPaths')
config.set('LinuxPaths', 'machine_id_path',
os.path.expanduser("~/.config/Cursor/machineId"))
return config.get('LinuxPaths', 'machine_id_path')
elif sys.platform == "darwin": # macOS
if not config.has_section('MacPaths'):
config.add_section('MacPaths')
config.set('MacPaths', 'machine_id_path',
os.path.expanduser("~/Library/Application Support/Cursor/machineId"))
return config.get('MacPaths', 'machine_id_path')
else:
raise OSError(f"Unsupported operating system: {sys.platform}")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
def get_workbench_cursor_path(translator=None) -> str:
"""Get Cursor workbench.desktop.main.js path"""
system = platform.system()
paths_map = { paths_map = {
"Darwin": { "Darwin": { # macOS
"base": "/Applications/Cursor.app/Contents/Resources/app", "base": "/Applications/Cursor.app/Contents/Resources/app",
"package": "package.json", "main": "out/vs/workbench/workbench.desktop.main.js"
"main": "out/main.js",
}, },
"Windows": { "Windows": {
"base": os.path.join( "base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app" "main": "out/vs/workbench/workbench.desktop.main.js"
),
"package": "package.json",
"main": "out/main.js",
}, },
"Linux": { "Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"package": "package.json", "main": "out/vs/workbench/workbench.desktop.main.js"
"main": "out/main.js", }
},
} }
if system not in paths_map: if system not in paths_map:
@@ -53,16 +137,18 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
if system == "Linux": if system == "Linux":
for base in paths_map["Linux"]["bases"]: for base in paths_map["Linux"]["bases"]:
pkg_path = os.path.join(base, paths_map["Linux"]["package"]) main_path = os.path.join(base, paths_map["Linux"]["main"])
if os.path.exists(pkg_path): if os.path.exists(main_path):
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"])) return main_path
raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径") raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径")
base_path = paths_map[system]["base"] base_path = paths_map[system]["base"]
return ( main_path = os.path.join(base_path, paths_map[system]["main"])
os.path.join(base_path, paths_map[system]["package"]),
os.path.join(base_path, paths_map[system]["main"]), if not os.path.exists(main_path):
) raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}")
return main_path
def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool: def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool:
"""Version number check""" """Version number check"""
@@ -95,11 +181,131 @@ def check_cursor_version(translator) -> bool:
"""Check Cursor version""" """Check Cursor version"""
try: try:
pkg_path, _ = get_cursor_paths(translator) pkg_path, _ = get_cursor_paths(translator)
with open(pkg_path, "r", encoding="utf-8") as f: print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}")
version = json.load(f)["version"]
return version_check(version, min_version="0.45.0", translator=translator) try:
with open(pkg_path, "r", encoding="utf-8") as f:
data = json.load(f)
except UnicodeDecodeError:
# 如果 UTF-8 讀取失敗,嘗試其他編碼
with open(pkg_path, "r", encoding="latin-1") as f:
data = json.load(f)
if not isinstance(data, dict):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
return False
if "version" not in data:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}")
return False
version = str(data["version"]).strip()
if not version:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}")
# 檢查版本格式
if not re.match(r"^\d+\.\d+\.\d+$", version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
return False
# 比較版本
try:
current = tuple(map(int, version.split(".")))
min_ver = (0, 45, 0) # 直接使用元組而不是字符串
if current >= min_ver:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
return False
except ValueError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}")
return False
except FileNotFoundError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}")
return False
except json.JSONDecodeError as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
return False
except Exception as e: except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}")
return False
def modify_workbench_js(file_path: str, translator=None) -> bool:
"""
Modify file content
"""
try:
# Save original file permissions
original_stat = os.stat(file_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
# Create temporary file
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file:
# Read original content
with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file:
content = main_file.read()
if sys.platform == "win32":
# Define replacement patterns
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "linux":
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "darwin":
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
CBadge_old_pattern = r'<div>Pro Trial'
CBadge_new_pattern = r'<div>Pro'
CToast_old_pattern = r'notifications-toasts'
CToast_new_pattern = r'notifications-toasts hidden'
# Replace content
content = content.replace(CButton_old_pattern, CButton_new_pattern)
content = content.replace(CBadge_old_pattern, CBadge_new_pattern)
content = content.replace(CToast_old_pattern, CToast_new_pattern)
# Write to temporary file
tmp_file.write(content)
tmp_path = tmp_file.name
# Backup original file
backup_path = file_path + ".backup"
if os.path.exists(backup_path):
os.remove(backup_path)
shutil.copy2(file_path, backup_path)
# Move temporary file to original position
if os.path.exists(file_path):
os.remove(file_path)
shutil.move(tmp_path, file_path)
# Restore original permissions
os.chmod(file_path, original_mode)
if os.name != "nt": # Not Windows
os.chown(file_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
try:
os.unlink(tmp_path)
except:
pass
return False return False
def modify_main_js(main_path: str, translator) -> bool: def modify_main_js(main_path: str, translator) -> bool:
@@ -195,34 +401,73 @@ class MachineIDResetter:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if not os.path.exists(config_file):
raise FileNotFoundError(f"Config file not found: {config_file}")
config.read(config_file, encoding='utf-8')
# Check operating system # Check operating system
if sys.platform == "win32": # Windows if sys.platform == "win32": # Windows
appdata = os.getenv("APPDATA") appdata = os.getenv("APPDATA")
if appdata is None: if appdata is None:
raise EnvironmentError("APPDATA Environment Variable Not Set") raise EnvironmentError("APPDATA Environment Variable Not Set")
self.db_path = os.path.join(
appdata, "Cursor", "User", "globalStorage", "storage.json" if not config.has_section('WindowsPaths'):
) config.add_section('WindowsPaths')
self.sqlite_path = os.path.join( config.set('WindowsPaths', 'storage_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "state.vscdb" appdata, "Cursor", "User", "globalStorage", "storage.json"
) ))
config.set('WindowsPaths', 'sqlite_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
))
self.db_path = config.get('WindowsPaths', 'storage_path')
self.sqlite_path = config.get('WindowsPaths', 'sqlite_path')
elif sys.platform == "darwin": # macOS elif sys.platform == "darwin": # macOS
self.db_path = os.path.abspath(os.path.expanduser( if not config.has_section('MacPaths'):
"~/Library/Application Support/Cursor/User/globalStorage/storage.json" config.add_section('MacPaths')
)) config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser(
self.sqlite_path = os.path.abspath(os.path.expanduser( "~/Library/Application Support/Cursor/User/globalStorage/storage.json"
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" )))
)) config.set('MacPaths', 'sqlite_path', os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('MacPaths', 'storage_path')
self.sqlite_path = config.get('MacPaths', 'sqlite_path')
elif sys.platform == "linux": # Linux elif sys.platform == "linux": # Linux
self.db_path = os.path.abspath(os.path.expanduser( if not config.has_section('LinuxPaths'):
"~/.config/Cursor/User/globalStorage/storage.json" config.add_section('LinuxPaths')
)) # 获取实际用户的主目录
self.sqlite_path = os.path.abspath(os.path.expanduser( sudo_user = os.environ.get('SUDO_USER')
"~/.config/Cursor/User/globalStorage/state.vscdb" actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
))
config.set('LinuxPaths', 'storage_path', os.path.abspath(os.path.join(
actual_home,
".config/Cursor/User/globalStorage/storage.json"
)))
config.set('LinuxPaths', 'sqlite_path', os.path.abspath(os.path.join(
actual_home,
".config/Cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('LinuxPaths', 'storage_path')
self.sqlite_path = config.get('LinuxPaths', 'sqlite_path')
else: else:
raise NotImplementedError(f"Not Supported OS: {sys.platform}") raise NotImplementedError(f"Not Supported OS: {sys.platform}")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
def generate_new_ids(self): def generate_new_ids(self):
"""Generate new machine ID""" """Generate new machine ID"""
# Generate new UUID # Generate new UUID
@@ -237,6 +482,8 @@ class MachineIDResetter:
# Generate new sqmId # Generate new sqmId
sqm_id = "{" + str(uuid.uuid4()).upper() + "}" sqm_id = "{" + str(uuid.uuid4()).upper() + "}"
self.update_machine_id_file(dev_device_id)
return { return {
"telemetry.devDeviceId": dev_device_id, "telemetry.devDeviceId": dev_device_id,
"telemetry.macMachineId": mac_machine_id, "telemetry.macMachineId": mac_machine_id,
@@ -309,12 +556,12 @@ class MachineIDResetter:
new_guid = str(uuid.uuid4()) new_guid = str(uuid.uuid4())
winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid) winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key) winreg.CloseKey(key)
print("Windows MachineGuid updated successfully") print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}")
except PermissionError: except PermissionError:
print("Permission denied: Run as administrator to update Windows MachineGuid") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
raise raise
except Exception as e: except Exception as e:
print(f"Failed to update Windows MachineGuid: {e}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
raise raise
def _update_macos_platform_uuid(self, new_ids): def _update_macos_platform_uuid(self, new_ids):
@@ -326,11 +573,11 @@ class MachineIDResetter:
cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"' cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"'
result = os.system(cmd) result = os.system(cmd)
if result == 0: if result == 0:
print("macOS Platform UUID updated successfully") print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}")
else: else:
raise Exception("Failed to execute plutil command") raise Exception(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.failed_to_execute_plutil_command')}{Style.RESET_ALL}")
except Exception as e: except Exception as e:
print(f"Failed to update macOS Platform UUID: {e}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_macos_platform_uuid_failed', error=str(e))}{Style.RESET_ALL}")
raise raise
def reset_machine_ids(self): def reset_machine_ids(self):
@@ -373,7 +620,16 @@ class MachineIDResetter:
# Update system IDs # Update system IDs
self.update_system_ids(new_ids) self.update_system_ids(new_ids)
### Remove In v1.7.02
# Modify workbench.desktop.main.js
# workbench_path = get_workbench_cursor_path(self.translator)
# modify_workbench_js(workbench_path, self.translator)
### Remove In v1.7.02
# Check Cursor version and perform corresponding actions # Check Cursor version and perform corresponding actions
greater_than_0_45 = check_cursor_version(self.translator) greater_than_0_45 = check_cursor_version(self.translator)
if greater_than_0_45: if greater_than_0_45:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}")
@@ -396,8 +652,48 @@ class MachineIDResetter:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}")
return False return False
def update_machine_id_file(self, machine_id: str) -> bool:
"""
Update machineId file with new machine_id
Args:
machine_id (str): New machine ID to write
Returns:
bool: True if successful, False otherwise
"""
try:
# Get the machineId file path
machine_id_path = get_cursor_machine_id_path()
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(machine_id_path), exist_ok=True)
# Create backup if file exists
if os.path.exists(machine_id_path):
backup_path = machine_id_path + ".backup"
try:
shutil.copy2(machine_id_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('reset.backup_created', path=backup_path) if self.translator else f'Backup created at: {backup_path}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_creation_failed', error=str(e)) if self.translator else f'Could not create backup: {str(e)}'}{Style.RESET_ALL}")
# Write new machine ID to file
with open(machine_id_path, "w", encoding="utf-8") as f:
f.write(machine_id)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.update_success') if self.translator else 'Successfully updated machineId file'}{Style.RESET_ALL}")
return True
except Exception as e:
error_msg = f"Failed to update machineId file: {str(e)}"
if self.translator:
error_msg = self.translator.get('reset.update_failed', error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {error_msg}{Style.RESET_ALL}")
return False
def run(translator=None): def run(translator=None):
"""Convenient function for directly calling the reset function""" config = get_config(translator)
if not config:
return False
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")

View File

@@ -63,8 +63,15 @@ detect_os() {
echo -e "${CYAN} Detected macOS Intel architecture${NC}" echo -e "${CYAN} Detected macOS Intel architecture${NC}"
fi fi
elif [[ "$(uname)" == "Linux" ]]; then elif [[ "$(uname)" == "Linux" ]]; then
OS="linux" # Detect Linux architecture
echo -e "${CYAN} Detected Linux system${NC}" ARCH=$(uname -m)
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
OS="linux_arm64"
echo -e "${CYAN} Detected Linux ARM64 architecture${NC}"
else
OS="linux_x64"
echo -e "${CYAN} Detected Linux x64 architecture${NC}"
fi
else else
# Assume Windows # Assume Windows
OS="windows" OS="windows"
@@ -123,6 +130,16 @@ install_cursor_free_vip() {
download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}" download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
echo -e "${CYAN} New download link: ${download_url}${NC}" echo -e "${CYAN} New download link: ${download_url}${NC}"
if ! curl --output /dev/null --silent --head --fail "$download_url"; then
echo -e "${RED}❌ New download link does not exist${NC}"
exit 1
fi
elif [[ "$OS" == "linux_x64" || "$OS" == "linux_arm64" ]]; then
OS="linux"
binary_name="CursorFreeVIP_${VERSION}_${OS}"
download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
echo -e "${CYAN} New download link: ${download_url}${NC}"
if ! curl --output /dev/null --silent --head --fail "$download_url"; then if ! curl --output /dev/null --silent --head --fail "$download_url"; then
echo -e "${RED}❌ New download link does not exist${NC}" echo -e "${RED}❌ New download link does not exist${NC}"
exit 1 exit 1

32
utils.py Normal file
View File

@@ -0,0 +1,32 @@
import os
import sys
import platform
def get_user_documents_path():
"""Get user documents path"""
if platform.system() == "Windows":
return os.path.expanduser("~\\Documents")
else:
return os.path.expanduser("~/Documents")
def get_default_chrome_path():
"""Get default Chrome path"""
if sys.platform == "win32":
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
elif sys.platform == "darwin":
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
else:
return "/usr/bin/google-chrome"
def get_linux_cursor_path():
"""Get Linux Cursor path"""
possible_paths = [
"/opt/Cursor/resources/app",
"/usr/share/cursor/resources/app",
"/opt/cursor-bin/resources/app",
"/usr/lib/cursor/resources/app",
os.path.expanduser("~/.local/share/cursor/resources/app")
]
# 返回第一个存在的路径,如果都不存在则返回第一个路径
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])