forked from mirrors/cursor-free-vip
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6f3fe814 | ||
|
|
54ecf2d752 | ||
|
|
1f1231d1a9 | ||
|
|
d10999a517 | ||
|
|
fb4be6334a | ||
|
|
786eba5371 | ||
|
|
41ddbf519e | ||
|
|
ffd48201fd | ||
|
|
cd4f36725c | ||
|
|
dccb524bd7 | ||
|
|
90e9a5b287 | ||
|
|
66a67fce8b | ||
|
|
0981f00b9c | ||
|
|
02851c9a09 | ||
|
|
6312d66813 | ||
|
|
1b1a21f3d7 | ||
|
|
c94bd605e5 | ||
|
|
9f6eee77e0 |
53
.github/workflows/build.yml
vendored
53
.github/workflows/build.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
version:
|
||||
description: 'Version number (e.g. 1.0.9)'
|
||||
required: true
|
||||
default: '1.5.03'
|
||||
default: '1.7.05'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
|
||||
build-linux:
|
||||
build-linux-x64:
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -120,20 +120,55 @@ jobs:
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Build Linux executable
|
||||
- name: Build Linux x64 executable
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: |
|
||||
pyinstaller build.spec
|
||||
mv "dist/CursorFreeVIP_${{ env.VERSION }}_linux" "dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64"
|
||||
echo "Contents of dist directory:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Upload Linux artifact
|
||||
- name: Upload Linux x64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux_x64
|
||||
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:
|
||||
needs: create-tag
|
||||
@@ -171,9 +206,8 @@ jobs:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
|
||||
|
||||
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
|
||||
|
||||
steps:
|
||||
@@ -201,7 +235,8 @@ jobs:
|
||||
files: |
|
||||
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 }}_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
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ build.mac.command
|
||||
build.py
|
||||
build.sh
|
||||
ENV/
|
||||
test.py
|
||||
|
||||
install.bat
|
||||
run.bat
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
||||
# Change Log
|
||||
|
||||
## 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 | 增加創建賬號最大重試次數
|
||||
|
||||
62
README.md
62
README.md
@@ -12,7 +12,7 @@
|
||||
[](https://github.com/yeongpin/cursor-free-vip/releases/latest)
|
||||
|
||||
</p>
|
||||
<h4>Support Latest 0.46.8 Version | 支持最新0.46.8本</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.
|
||||
|
||||
@@ -86,6 +86,62 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
|
||||
|
||||
## ❗ 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>請使用管理員身份運行腳本
|
||||
|
||||
* 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 | 常見問題
|
||||
|
||||
|如果遇到權限問題,請確保:|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 |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ class CursorRegistration:
|
||||
if usage_ele:
|
||||
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}")
|
||||
max_attempts = 30
|
||||
retry_interval = 2
|
||||
|
||||
@@ -68,7 +68,24 @@
|
||||
"version_less_than_0_45": "Cursor Version < 0.45.0, Skip Patching getMachineId",
|
||||
"detecting_version": "Detecting Cursor Version",
|
||||
"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": {
|
||||
"title": "Cursor Registration Tool",
|
||||
@@ -136,7 +153,14 @@
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"exit_signal": "Exit Signal",
|
||||
"email_address": "Email Address"
|
||||
"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}"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor Auth Manager",
|
||||
|
||||
@@ -68,7 +68,23 @@
|
||||
"version_less_than_0_45": "Cursor版本 < 0.45.0,跳过getMachineId修补",
|
||||
"detecting_version": "检测Cursor版本",
|
||||
"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": {
|
||||
"title": "Cursor 注册工具",
|
||||
@@ -136,7 +152,14 @@
|
||||
"first_name": "名字",
|
||||
"last_name": "姓氏",
|
||||
"exit_signal": "退出信号",
|
||||
"email_address": "邮箱地址"
|
||||
"email_address": "邮箱地址",
|
||||
"config_created": "配置已创建",
|
||||
"verification_failed": "验证失败",
|
||||
"verification_error": "验证错误: {error}",
|
||||
"config_option_added": "配置项已添加: {option}",
|
||||
"config_updated": "配置已更新",
|
||||
"password_submitted": "密码已提交",
|
||||
"total_usage": "总使用量: {usage}"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 认证管理器",
|
||||
|
||||
@@ -68,8 +68,25 @@
|
||||
"version_less_than_0_45": "Cursor版本 < 0.45.0,跳过getMachineId修补",
|
||||
"detecting_version": "檢測Cursor版本",
|
||||
"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": {
|
||||
"title": "Cursor 註冊工具",
|
||||
"start": "正在啟動註冊流程...",
|
||||
@@ -117,7 +134,14 @@
|
||||
"first_name": "名字",
|
||||
"last_name": "姓氏",
|
||||
"exit_signal": "退出信號",
|
||||
"email_address": "郵箱地址"
|
||||
"email_address": "郵箱地址",
|
||||
"config_created": "配置已創建",
|
||||
"verification_failed": "驗證失敗",
|
||||
"verification_error": "驗證錯誤: {error}",
|
||||
"config_option_added": "配置項已添加: {option}",
|
||||
"config_updated": "配置已更新",
|
||||
"password_submitted": "密碼已提交",
|
||||
"total_usage": "總使用量: {usage}"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 認證管理器",
|
||||
|
||||
8
main.py
8
main.py
@@ -285,6 +285,14 @@ def check_latest_version():
|
||||
|
||||
def main():
|
||||
print_logo()
|
||||
|
||||
# 初始化配置
|
||||
from new_signup import setup_config
|
||||
config = setup_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
|
||||
print_menu()
|
||||
|
||||
|
||||
488
new_signup.py
488
new_signup.py
@@ -4,6 +4,9 @@ import os
|
||||
import signal
|
||||
import random
|
||||
from colorama import Fore, Style
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# 在文件开头添加全局变量
|
||||
_translator = None
|
||||
@@ -34,22 +37,20 @@ def signal_handler(signum, frame):
|
||||
cleanup_chrome_processes(_translator)
|
||||
os._exit(0)
|
||||
|
||||
def simulate_human_input(page, url, translator=None):
|
||||
def simulate_human_input(page, url, config, translator=None):
|
||||
"""访问网址"""
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("正在访问网址...")
|
||||
|
||||
# 先访问空白页面
|
||||
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)
|
||||
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:
|
||||
if translator:
|
||||
@@ -61,25 +62,25 @@ def fill_signup_form(page, first_name, last_name, email, translator=None):
|
||||
first_name_input = page.ele("@name=first_name")
|
||||
if first_name_input:
|
||||
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")
|
||||
if last_name_input:
|
||||
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")
|
||||
if email_input:
|
||||
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")
|
||||
if submit_button:
|
||||
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.form_success')}{Style.RESET_ALL}")
|
||||
@@ -94,43 +95,241 @@ def fill_signup_form(page, first_name, last_name, email, translator=None):
|
||||
print(f"填写表单时出错: {e}")
|
||||
return False
|
||||
|
||||
def setup_driver(translator=None):
|
||||
"""设置浏览器驱动"""
|
||||
co = ChromiumOptions()
|
||||
|
||||
# 使用无痕模式
|
||||
co.set_argument("--incognito")
|
||||
def get_default_chrome_path():
|
||||
"""Get default Chrome path"""
|
||||
if sys.platform == "win32":
|
||||
paths = [
|
||||
os.path.join(os.environ.get('PROGRAMFILES', ''), 'Google/Chrome/Application/chrome.exe'),
|
||||
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')
|
||||
]
|
||||
elif sys.platform == "darwin":
|
||||
paths = [
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
]
|
||||
else: # Linux
|
||||
paths = [
|
||||
"/usr/bin/google-chrome",
|
||||
"/usr/bin/google-chrome-stable"
|
||||
]
|
||||
|
||||
# 设置随机端口
|
||||
co.set_argument("--no-sandbox")
|
||||
|
||||
# 设置随机端口
|
||||
co.auto_port()
|
||||
|
||||
# 使用有头模式(一定要设置为False,模擬人類操作)
|
||||
co.headless(False)
|
||||
|
||||
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:
|
||||
# 加载插件
|
||||
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)
|
||||
if not config.has_section('Timing'):
|
||||
return random.uniform(0.1, 0.8) # 默认值
|
||||
|
||||
if timing_type == 'random':
|
||||
min_time = float(config.get('Timing', 'min_random_time', fallback='0.1'))
|
||||
max_time = float(config.get('Timing', 'max_random_time', fallback='0.8'))
|
||||
return random.uniform(min_time, max_time)
|
||||
|
||||
time_value = config.get('Timing', timing_type, fallback='0.1-0.8')
|
||||
|
||||
# 检查是否为固定时间值
|
||||
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_config(translator=None):
|
||||
"""Setup configuration file and return config object"""
|
||||
try:
|
||||
# Set configuration file path
|
||||
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
|
||||
config_file = os.path.join(config_dir, "config.ini")
|
||||
|
||||
# Create config directory (if it doesn't exist)
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
# Read or create configuration file
|
||||
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' # 最大超时时间
|
||||
}
|
||||
}
|
||||
|
||||
# Add OS-specific path configurations
|
||||
if sys.platform == "win32":
|
||||
appdata = os.getenv("APPDATA")
|
||||
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(os.getenv("APPDATA"), "Cursor", "machineId"),
|
||||
'cursor_path': os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app")
|
||||
}
|
||||
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"
|
||||
}
|
||||
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': "/opt/Cursor/resources/app" # 默認路徑
|
||||
}
|
||||
|
||||
if os.path.exists(config_file):
|
||||
config.read(config_file)
|
||||
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, value)
|
||||
config_modified = True
|
||||
if translator:
|
||||
print(f"{Fore.YELLOW}ℹ️ {translator.get('register.config_option_added', option=f'{section}.{option}') if translator else 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') if translator else '配置文件已更新'}{Style.RESET_ALL}")
|
||||
else:
|
||||
# 创建新配置文件
|
||||
config = configparser.ConfigParser()
|
||||
for section, options in default_config.items():
|
||||
config.add_section(section)
|
||||
for option, value in options.items():
|
||||
config.set(section, option, 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') if translator else '已创建配置文件'}: {config_file}{Style.RESET_ALL}")
|
||||
|
||||
return config
|
||||
|
||||
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 page
|
||||
print(f"{Fore.RED}❌ {translator.get('register.config_setup_error', error=str(e)) if translator else f'配置设置出错: {str(e)}'}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
def handle_turnstile(page, translator=None):
|
||||
def setup_driver(translator=None):
|
||||
"""Setup browser driver"""
|
||||
try:
|
||||
# 获取配置
|
||||
config = setup_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:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.browser_setup_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"设置浏览器时出错: {e}")
|
||||
raise
|
||||
|
||||
def handle_turnstile(page, config, translator=None):
|
||||
"""处理 Turnstile 验证"""
|
||||
try:
|
||||
if translator:
|
||||
@@ -138,6 +337,16 @@ def handle_turnstile(page, translator=None):
|
||||
else:
|
||||
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
|
||||
retry_count = 0
|
||||
|
||||
@@ -151,7 +360,7 @@ def handle_turnstile(page, translator=None):
|
||||
try:
|
||||
# 尝试重置 turnstile
|
||||
page.run_js("try { turnstile.reset() } catch(e) { }")
|
||||
time.sleep(2)
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
# 定位验证框元素
|
||||
challenge_check = (
|
||||
@@ -168,12 +377,12 @@ def handle_turnstile(page, translator=None):
|
||||
else:
|
||||
print("检测到验证框...")
|
||||
|
||||
# 随机延时后点击验证
|
||||
time.sleep(random.uniform(1, 3))
|
||||
# from config
|
||||
time.sleep(random.uniform(min_time, max_time))
|
||||
challenge_check.click()
|
||||
time.sleep(2)
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
# 检查验证结果
|
||||
# check verification result
|
||||
if check_verification_success(page, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
@@ -195,7 +404,7 @@ def handle_turnstile(page, translator=None):
|
||||
print("验证通过!")
|
||||
return True
|
||||
|
||||
time.sleep(random.uniform(1, 2))
|
||||
time.sleep(random.uniform(min_time, max_time))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
@@ -240,43 +449,57 @@ def generate_password(length=12):
|
||||
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||||
return ''.join(random.choices(chars, k=length))
|
||||
|
||||
def fill_password(page, password, translator=None):
|
||||
"""填写密码"""
|
||||
def fill_password(page, password: str, config, translator=None) -> bool:
|
||||
"""
|
||||
填写密码表单
|
||||
"""
|
||||
try:
|
||||
if translator:
|
||||
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")
|
||||
if password_input:
|
||||
password_input.input(password)
|
||||
time.sleep(random.uniform(0.5, 1.0))
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}")
|
||||
|
||||
# 等待密码框出现并尝试多次
|
||||
max_retries = 5
|
||||
for i in range(max_retries):
|
||||
# 检查是否出现错误信息
|
||||
if page.ele("This email is not available."):
|
||||
print(f"{Fore.RED}❌ {translator.get('register.email_used') if translator else '注册失败:邮箱已被使用'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# 查找密码输入框
|
||||
password_input = page.ele("@name=password")
|
||||
if password_input:
|
||||
# 清除可能存在的旧值并输入新密码
|
||||
password_input.click()
|
||||
time.sleep(get_random_wait_time(config, 'input_wait'))
|
||||
password_input.input(password)
|
||||
time.sleep(get_random_wait_time(config, 'input_wait'))
|
||||
|
||||
# 查找并点击提交按钮
|
||||
submit_button = page.ele("@type=submit")
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}")
|
||||
time.sleep(get_random_wait_time(config, 'submit_wait'))
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_submit') if translator else '未找到提交按钮,重试中...'}{Style.RESET_ALL}")
|
||||
|
||||
submit_button = page.ele("@type=submit")
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(random.uniform(2.0, 3.0))
|
||||
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{translator.get('register.password_error')}: {e}")
|
||||
# 如果没找到密码框,等待后重试
|
||||
time.sleep(get_random_wait_time(config, 'failed_retry_time'))
|
||||
if i < max_retries - 1: # 不是最后一次尝试时才打印
|
||||
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_password', attempt=i+1) if translator else f'第 {i+1} 次尝试设置密码...'}{Style.RESET_ALL}")
|
||||
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_set_failed') if translator else '密码设置失败:超过重试次数'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def handle_verification_code(browser_tab, email_tab, controller, email, password, translator=None):
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def handle_verification_code(browser_tab, email_tab, controller, email, password, config, translator=None):
|
||||
"""处理验证码"""
|
||||
try:
|
||||
if translator:
|
||||
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: # 手动模式
|
||||
@@ -285,23 +508,21 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
# 在注册页面填写验证码
|
||||
for i, digit in enumerate(verification_code):
|
||||
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')}")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{translator.get('register.verification_success')}")
|
||||
else:
|
||||
print(f"{translator.get('register.verification_success')}")
|
||||
time.sleep(2)
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 访问设置页面
|
||||
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")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
return True, browser_tab
|
||||
|
||||
return False, None
|
||||
@@ -309,11 +530,11 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
# 自动获取验证码逻辑
|
||||
elif email_tab:
|
||||
print(f"{translator.get('register.waiting_for_verification_code')}")
|
||||
time.sleep(5) # 等待验证码邮件
|
||||
time.sleep(get_random_wait_time(config, 'email_check_initial_wait'))
|
||||
|
||||
# 使用已有的 email_tab 刷新邮箱
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'email_refresh_wait'))
|
||||
|
||||
# 检查邮箱是否有验证码邮件
|
||||
if email_tab.check_for_cursor_email():
|
||||
@@ -322,28 +543,23 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
# 在注册页面填写验证码
|
||||
for i, digit in enumerate(verification_code):
|
||||
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:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证码填写完成")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("最后一次验证通过!")
|
||||
time.sleep(2)
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 访问设置页面
|
||||
if translator:
|
||||
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")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
return True, browser_tab
|
||||
|
||||
else:
|
||||
@@ -356,69 +572,55 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
# 获取验证码,设置超时
|
||||
verification_code = None
|
||||
max_attempts = 20
|
||||
retry_interval = 10
|
||||
retry_interval = get_random_wait_time(config, 'retry_interval') # 使用 get_random_wait_time
|
||||
start_time = time.time()
|
||||
timeout = 160
|
||||
timeout = float(config.get('Timing', 'max_timeout', fallback='160')) # 這個可以保持不變因為是固定值
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("开始获取验证码...")
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
# 检查是否超时
|
||||
if time.time() - start_time > timeout:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_timeout')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("获取验证码超时...")
|
||||
break
|
||||
|
||||
verification_code = controller.get_verification_code()
|
||||
if verification_code:
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"成功获取验证码: {verification_code}")
|
||||
break
|
||||
|
||||
remaining_time = int(timeout - (time.time() - start_time))
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"第 {attempt + 1} 次尝试获取验证码,剩余时间: {remaining_time}秒...")
|
||||
|
||||
# 刷新邮箱
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(retry_interval)
|
||||
time.sleep(retry_interval) # 使用 get_random_wait_time
|
||||
|
||||
if verification_code:
|
||||
# 在注册页面填写验证码
|
||||
for i, digit in enumerate(verification_code):
|
||||
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:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证码填写完成")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("最后一次验证通过!")
|
||||
time.sleep(2)
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 直接访问设置页面
|
||||
if translator:
|
||||
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")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
|
||||
# 直接返回成功,让 cursor_register.py 处理账户信息获取
|
||||
return True, browser_tab
|
||||
@@ -426,8 +628,6 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
else:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("最后一次验证失败")
|
||||
return False, None
|
||||
|
||||
return False, None
|
||||
@@ -435,8 +635,6 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"处理验证码时出错: {e}")
|
||||
return False, None
|
||||
|
||||
def handle_sign_in(browser_tab, email, password, translator=None):
|
||||
@@ -499,26 +697,20 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
|
||||
page = None
|
||||
success = False
|
||||
try:
|
||||
page = setup_driver(translator)
|
||||
config, page = setup_driver(translator)
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("浏览器已启动")
|
||||
|
||||
# 访问注册页面
|
||||
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:
|
||||
print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("等待页面加载...")
|
||||
time.sleep(5)
|
||||
time.sleep(get_random_wait_time(config, 'page_load_wait'))
|
||||
|
||||
# 如果没有提供账号信息,则生成随机信息
|
||||
if not all([email, password, first_name, last_name]):
|
||||
@@ -535,44 +727,36 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=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:
|
||||
print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n表单已提交,开始验证...")
|
||||
|
||||
# 处理第一次 Turnstile 验证
|
||||
if handle_turnstile(page, translator):
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
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:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n等待第二次验证...")
|
||||
time.sleep(2)
|
||||
|
||||
# 处理第二次 Turnstile 验证
|
||||
if handle_turnstile(page, translator):
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n开始处理验证码...")
|
||||
if handle_verification_code(page, email_tab, controller, email, password, translator):
|
||||
if handle_verification_code(page, email_tab, controller, email, password, config, translator):
|
||||
success = True
|
||||
return True, page # 返回浏览器实例
|
||||
return True, page
|
||||
else:
|
||||
print("\n验证码处理失败")
|
||||
print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n第二次验证失败")
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n密码设置失败")
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n第一次验证失败")
|
||||
print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}")
|
||||
|
||||
return False, None
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import requests
|
||||
import random
|
||||
import string
|
||||
|
||||
# 初始化 colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
class NewTempEmail:
|
||||
@@ -34,7 +34,7 @@ class NewTempEmail:
|
||||
# Split text and remove empty lines
|
||||
domains = [line.strip() for line in response.text.split('\n') if line.strip()]
|
||||
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:
|
||||
print(f"{Fore.CYAN}ℹ️ 已加载 {len(domains)} 个被屏蔽的域名{Style.RESET_ALL}")
|
||||
return domains
|
||||
@@ -80,11 +80,11 @@ class NewTempEmail:
|
||||
attempt += 1
|
||||
try:
|
||||
if self.translator:
|
||||
print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.CYAN}ℹ️ 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}")
|
||||
|
||||
# 获取可用域名列表
|
||||
# Get available domain list
|
||||
try:
|
||||
domains_response = requests.get(f"{self.api_url}/domains", timeout=10)
|
||||
if domains_response.status_code != 200:
|
||||
@@ -93,7 +93,7 @@ class NewTempEmail:
|
||||
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}")
|
||||
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 '没有可用域名'}")
|
||||
@@ -101,11 +101,11 @@ class NewTempEmail:
|
||||
print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
# 排除被屏蔽的域名
|
||||
# Exclude blocked domains
|
||||
try:
|
||||
filtered_domains = self.exclude_blocked_domains(domains)
|
||||
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.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.CYAN}ℹ️ 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}")
|
||||
|
||||
@@ -115,33 +115,33 @@ class NewTempEmail:
|
||||
else:
|
||||
print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}")
|
||||
|
||||
# 切换到另一个服务
|
||||
# Switch to another service
|
||||
for service in self.services:
|
||||
if service["api_url"] != self.api_url:
|
||||
self.selected_service = service
|
||||
self.api_url = service["api_url"]
|
||||
if self.translator:
|
||||
print(f"{Fore.CYAN}ℹ️ {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}")
|
||||
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() # 递归调用
|
||||
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}")
|
||||
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}")
|
||||
|
||||
@@ -153,7 +153,7 @@ class NewTempEmail:
|
||||
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)
|
||||
|
||||
@@ -167,13 +167,13 @@ class NewTempEmail:
|
||||
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 '创建账户失败'}")
|
||||
@@ -184,7 +184,7 @@ class NewTempEmail:
|
||||
print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
# 获取访问令牌
|
||||
# Get access token
|
||||
try:
|
||||
token_data = {
|
||||
"address": email,
|
||||
@@ -238,7 +238,7 @@ class NewTempEmail:
|
||||
else:
|
||||
print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}")
|
||||
|
||||
# 使用 API 获取最新邮件
|
||||
# Use API to get latest email
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
@@ -265,7 +265,7 @@ class NewTempEmail:
|
||||
def check_for_cursor_email(self):
|
||||
"""检查是否有 Cursor 的验证邮件"""
|
||||
try:
|
||||
# 使用 API 获取邮件列表
|
||||
# Use API to get email list
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
@@ -273,7 +273,7 @@ class NewTempEmail:
|
||||
messages = response.json()["hydra:member"]
|
||||
for message in messages:
|
||||
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
|
||||
# 获取邮件内容
|
||||
# Get email content
|
||||
message_id = message["id"]
|
||||
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
|
||||
if message_response.status_code == 200:
|
||||
@@ -299,7 +299,7 @@ class NewTempEmail:
|
||||
def get_verification_code(self):
|
||||
"""get verification code"""
|
||||
try:
|
||||
# 使用 API 获取邮件列表
|
||||
# Use API to get email list
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
@@ -307,14 +307,14 @@ class NewTempEmail:
|
||||
messages = response.json()["hydra:member"]
|
||||
for message in messages:
|
||||
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
|
||||
# 获取邮件内容
|
||||
# Get email content
|
||||
message_id = message["id"]
|
||||
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
|
||||
|
||||
if message_response.status_code == 200:
|
||||
# 从邮件内容中提取验证码
|
||||
# Extract verification code from email content
|
||||
email_content = message_response.json()["text"]
|
||||
# 查找6位数字验证码
|
||||
# Find 6-digit verification code
|
||||
import re
|
||||
code_match = re.search(r'\b\d{6}\b', email_content)
|
||||
|
||||
@@ -350,7 +350,7 @@ def main(translator=None):
|
||||
else:
|
||||
print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}")
|
||||
|
||||
# 测试刷新功能
|
||||
# Test refresh function
|
||||
while True:
|
||||
if translator:
|
||||
choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower()
|
||||
|
||||
@@ -10,6 +10,9 @@ import re
|
||||
import tempfile
|
||||
from colorama import Fore, Style, init
|
||||
from typing import Tuple
|
||||
import configparser
|
||||
from new_signup import get_user_documents_path
|
||||
import traceback
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
@@ -27,25 +30,105 @@ EMOJI = {
|
||||
def get_cursor_paths(translator=None) -> Tuple[str, str]:
|
||||
""" Get Cursor related paths"""
|
||||
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)
|
||||
|
||||
# 根據系統獲取路徑
|
||||
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 = {
|
||||
"Darwin": {
|
||||
"Darwin": { # macOS
|
||||
"base": "/Applications/Cursor.app/Contents/Resources/app",
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
"main": "out/vs/workbench/workbench.desktop.main.js"
|
||||
},
|
||||
"Windows": {
|
||||
"base": os.path.join(
|
||||
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"
|
||||
),
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
"base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
|
||||
"main": "out/vs/workbench/workbench.desktop.main.js"
|
||||
},
|
||||
"Linux": {
|
||||
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
"main": "out/vs/workbench/workbench.desktop.main.js"
|
||||
}
|
||||
}
|
||||
|
||||
if system not in paths_map:
|
||||
@@ -53,16 +136,18 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
|
||||
|
||||
if system == "Linux":
|
||||
for base in paths_map["Linux"]["bases"]:
|
||||
pkg_path = os.path.join(base, paths_map["Linux"]["package"])
|
||||
if os.path.exists(pkg_path):
|
||||
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"]))
|
||||
main_path = os.path.join(base, paths_map["Linux"]["main"])
|
||||
if os.path.exists(main_path):
|
||||
return main_path
|
||||
raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径")
|
||||
|
||||
base_path = paths_map[system]["base"]
|
||||
return (
|
||||
os.path.join(base_path, paths_map[system]["package"]),
|
||||
os.path.join(base_path, paths_map[system]["main"]),
|
||||
)
|
||||
main_path = 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:
|
||||
"""Version number check"""
|
||||
@@ -95,11 +180,131 @@ def check_cursor_version(translator) -> bool:
|
||||
"""Check Cursor version"""
|
||||
try:
|
||||
pkg_path, _ = get_cursor_paths(translator)
|
||||
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||
version = json.load(f)["version"]
|
||||
return version_check(version, min_version="0.45.0", translator=translator)
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}")
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
def modify_main_js(main_path: str, translator) -> bool:
|
||||
@@ -195,34 +400,73 @@ class MachineIDResetter:
|
||||
def __init__(self, translator=None):
|
||||
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)
|
||||
|
||||
# Check operating system
|
||||
if sys.platform == "win32": # Windows
|
||||
appdata = os.getenv("APPDATA")
|
||||
if appdata is None:
|
||||
raise EnvironmentError("APPDATA Environment Variable Not Set")
|
||||
self.db_path = os.path.join(
|
||||
appdata, "Cursor", "User", "globalStorage", "storage.json"
|
||||
)
|
||||
self.sqlite_path = os.path.join(
|
||||
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
|
||||
)
|
||||
|
||||
if not config.has_section('WindowsPaths'):
|
||||
config.add_section('WindowsPaths')
|
||||
config.set('WindowsPaths', 'storage_path', os.path.join(
|
||||
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
|
||||
self.db_path = os.path.abspath(os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/storage.json"
|
||||
))
|
||||
self.sqlite_path = os.path.abspath(os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||
))
|
||||
if not config.has_section('MacPaths'):
|
||||
config.add_section('MacPaths')
|
||||
config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/storage.json"
|
||||
)))
|
||||
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
|
||||
self.db_path = os.path.abspath(os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/storage.json"
|
||||
))
|
||||
self.sqlite_path = os.path.abspath(os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/state.vscdb"
|
||||
))
|
||||
if not config.has_section('LinuxPaths'):
|
||||
config.add_section('LinuxPaths')
|
||||
# 获取实际用户的主目录
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
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:
|
||||
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):
|
||||
"""Generate new machine ID"""
|
||||
# Generate new UUID
|
||||
@@ -237,6 +481,8 @@ class MachineIDResetter:
|
||||
# Generate new sqmId
|
||||
sqm_id = "{" + str(uuid.uuid4()).upper() + "}"
|
||||
|
||||
self.update_machine_id_file(dev_device_id)
|
||||
|
||||
return {
|
||||
"telemetry.devDeviceId": dev_device_id,
|
||||
"telemetry.macMachineId": mac_machine_id,
|
||||
@@ -309,12 +555,12 @@ class MachineIDResetter:
|
||||
new_guid = str(uuid.uuid4())
|
||||
winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid)
|
||||
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:
|
||||
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
|
||||
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
|
||||
|
||||
def _update_macos_platform_uuid(self, new_ids):
|
||||
@@ -326,11 +572,11 @@ class MachineIDResetter:
|
||||
cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"'
|
||||
result = os.system(cmd)
|
||||
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:
|
||||
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:
|
||||
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
|
||||
|
||||
def reset_machine_ids(self):
|
||||
@@ -373,7 +619,16 @@ class MachineIDResetter:
|
||||
# Update system 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
|
||||
|
||||
greater_than_0_45 = check_cursor_version(self.translator)
|
||||
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}")
|
||||
@@ -396,6 +651,44 @@ class MachineIDResetter:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}")
|
||||
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):
|
||||
"""Convenient function for directly calling the reset function"""
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
@@ -63,8 +63,15 @@ detect_os() {
|
||||
echo -e "${CYAN}ℹ️ Detected macOS Intel architecture${NC}"
|
||||
fi
|
||||
elif [[ "$(uname)" == "Linux" ]]; then
|
||||
OS="linux"
|
||||
echo -e "${CYAN}ℹ️ Detected Linux system${NC}"
|
||||
# Detect Linux architecture
|
||||
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
|
||||
# Assume Windows
|
||||
OS="windows"
|
||||
@@ -127,9 +134,19 @@ install_cursor_free_vip() {
|
||||
echo -e "${RED}❌ New download link does not exist${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
} 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
|
||||
echo -e "${RED}❌ New download link does not exist${NC}"
|
||||
exit 1
|
||||
fi
|
||||
} else {
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
# Download file
|
||||
|
||||
Reference in New Issue
Block a user