Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02851c9a09 | ||
|
|
6312d66813 | ||
|
|
1b1a21f3d7 | ||
|
|
c94bd605e5 | ||
|
|
9f6eee77e0 | ||
|
|
2bdcc2f633 | ||
|
|
4aabe2e403 | ||
|
|
005aa2cd95 | ||
|
|
14f6dfc29d | ||
|
|
2e9bd269ad | ||
|
|
f9b7e23253 | ||
|
|
0d979f7543 | ||
|
|
cce3025f7f | ||
|
|
fe3e27561b | ||
|
|
bf2bea71eb | ||
|
|
77a61647dd | ||
|
|
813dd4431e | ||
|
|
d7116b8cf3 | ||
|
|
f5a7acc4e3 | ||
|
|
479933844a | ||
|
|
93046d7f03 | ||
|
|
e7ca31b710 | ||
|
|
87b99b0d16 | ||
|
|
e983b6f560 | ||
|
|
21ca7ab24f | ||
|
|
e7468644a4 | ||
|
|
2bca7d7d14 | ||
|
|
040c5f5836 | ||
|
|
4a86bbeeb4 | ||
|
|
68b7888a83 | ||
|
|
78dee025a7 | ||
|
|
17c2b4b243 | ||
|
|
2acb271f19 | ||
|
|
b688b67c26 | ||
|
|
3543615a69 | ||
|
|
24b0a5c09e | ||
|
|
5bbba05f69 | ||
|
|
72c95e4b4f | ||
|
|
8afd5df4ea | ||
|
|
a7a97b5621 | ||
|
|
e2e2ebc12e | ||
|
|
d131bccac0 | ||
|
|
d852bcff50 | ||
|
|
4c91525082 | ||
|
|
b565232ba6 | ||
|
|
5725a2b2f7 | ||
|
|
6ec6f8bdbf | ||
|
|
5c1bcff55a | ||
|
|
025c4e2ef3 | ||
|
|
6ac1294bee | ||
|
|
fb443592d3 | ||
|
|
c30a62b072 | ||
|
|
8bc509cccf | ||
|
|
9f814708d1 | ||
|
|
1889f9827a | ||
|
|
57d2d40e67 | ||
|
|
04fa6ee935 | ||
|
|
ed1e0f787e | ||
|
|
240716e45f | ||
|
|
35bbe6c93c | ||
|
|
fed50a31cc | ||
|
|
57ea4dd25a | ||
|
|
72eadfaf74 | ||
|
|
9521c0ce75 | ||
|
|
637f923c16 | ||
|
|
1cc93ffc22 | ||
|
|
887239d80c | ||
|
|
5d944fa3b1 | ||
|
|
654157b371 | ||
|
|
f4314cc6da | ||
|
|
1f29b2dbbe | ||
|
|
9c2b3f2fc8 | ||
|
|
8e20a0ed3d | ||
|
|
4122701468 | ||
|
|
1ee9813155 | ||
|
|
a9e4b3a5c6 | ||
|
|
a83851d441 | ||
|
|
1e290d0417 | ||
|
|
53ab15604e | ||
|
|
638916e5ef | ||
|
|
9500ce1249 | ||
|
|
721c13cb2f | ||
|
|
a04eed2c6b | ||
|
|
8107bede63 | ||
|
|
4166bc5135 | ||
|
|
69994d2486 | ||
|
|
ffcb8ec140 | ||
|
|
b61b61c607 | ||
|
|
7326d0eeb0 | ||
|
|
371f00645d | ||
|
|
a873291481 | ||
|
|
7710ea4bb3 | ||
|
|
13483eed77 | ||
|
|
eb69f933af | ||
|
|
54cd8cf323 | ||
|
|
4e0289c86c | ||
|
|
af4a1c4065 | ||
|
|
e5a4134a41 | ||
|
|
731db36121 | ||
|
|
f139fa189d | ||
|
|
a7e927a557 | ||
|
|
ffd7b839e7 |
209
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
name: Build Executables
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version number (e.g. 1.0.9)'
|
||||
required: true
|
||||
default: '1.5.03'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
create-tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # 获取所有标签
|
||||
|
||||
- name: Delete existing tag if exists
|
||||
run: |
|
||||
if git ls-remote --tags origin | grep -q "refs/tags/v${{ github.event.inputs.version }}"; then
|
||||
git push origin --delete "v${{ github.event.inputs.version }}" || true
|
||||
git tag -d "v${{ github.event.inputs.version }}" || true
|
||||
fi
|
||||
|
||||
- name: Create Tag
|
||||
run: |
|
||||
git tag "v${{ github.event.inputs.version }}"
|
||||
git push origin "v${{ github.event.inputs.version }}"
|
||||
|
||||
build-windows:
|
||||
needs: create-tag
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Build EXE
|
||||
run: |
|
||||
pyinstaller build.spec
|
||||
|
||||
- name: Upload Windows artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_windows.exe
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_windows.exe
|
||||
|
||||
build-macos-arm64:
|
||||
needs: create-tag
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Build MacOS ARM executable
|
||||
run: |
|
||||
pyinstaller build.spec
|
||||
mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64"
|
||||
|
||||
- name: Upload MacOS ARM artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
|
||||
build-linux:
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Build Linux executable
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: |
|
||||
pyinstaller build.spec
|
||||
echo "Contents of dist directory:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Upload Linux artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
|
||||
|
||||
build-macos-intel:
|
||||
needs: create-tag
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
arch -x86_64 pip3 install --upgrade pip
|
||||
arch -x86_64 pip3 install pyinstaller
|
||||
arch -x86_64 pip3 install -r requirements.txt
|
||||
|
||||
- name: Build MacOS Intel executable
|
||||
env:
|
||||
TARGET_ARCH: 'x86_64'
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: |
|
||||
arch -x86_64 python3 -m PyInstaller build.spec
|
||||
mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel"
|
||||
|
||||
- name: Upload MacOS Intel artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
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]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Get version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Prepare release files
|
||||
run: |
|
||||
cd artifacts
|
||||
echo "Contents of artifacts directory:"
|
||||
ls -la
|
||||
echo "Contents of subdirectories:"
|
||||
ls -la */
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ env.VERSION }}
|
||||
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 }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
51
.gitignore
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
__pycache__
|
||||
server/
|
||||
venv/
|
||||
check_license.py
|
||||
cursor_modifier.py
|
||||
reset_machine.py
|
||||
Run_Venv.bat
|
||||
token_monitor.py
|
||||
get_mac.py
|
||||
.gitignore
|
||||
build.bat
|
||||
build.mac.command
|
||||
build.py
|
||||
build.sh
|
||||
ENV/
|
||||
test.py
|
||||
|
||||
install.bat
|
||||
run.bat
|
||||
|
||||
temp_account_info.txt
|
||||
|
||||
.env copy
|
||||
|
||||
# PyInstaller
|
||||
build/
|
||||
dist/
|
||||
*.spec2
|
||||
|
||||
credentials.txt
|
||||
cursor_accounts.txt
|
||||
recaptcha.py
|
||||
install_requirements.bat
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Project specific
|
||||
*.log
|
||||
*.db
|
||||
*.sqlite3
|
||||
|
||||
# Mac
|
||||
run_venv.mac.command
|
||||
245
CHANGELOG.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
1. Add: Check Latest Version | 增加檢查最新版本
|
||||
2. Add: Update Command | 增加更新命令
|
||||
|
||||
## v1.4.08
|
||||
1. Add: Print Some Account Info | 增加打印一些賬號信息
|
||||
|
||||
## v1.4.07
|
||||
1. Add Removed break statements after each operation | 修改結束event後的break暫停應用
|
||||
2. Added print_menu() calls to show the menu again | 添加print_menu()調用以再次顯示菜單
|
||||
3. Updated error handling to show menu instead of exiting | 更新錯誤處理以顯示菜單而不是退出
|
||||
|
||||
## v1.4.06
|
||||
|
||||
1. Add: Blocked Domains Loaded | 增加被屏蔽的域名加載
|
||||
2. Fix: Cleanup Error | 修復清理進程時出錯
|
||||
3. Fix: Blocked Domains Loaded Error | 修復被屏蔽的域名加載錯誤
|
||||
4. Fix: Available Domains Loaded Error | 修復可用域名加載錯誤
|
||||
5. Fix: Domains Filtered Error | 修復過濾後剩餘域名錯誤
|
||||
6. Fix: Domains Excluded Error | 修復排除域名錯誤
|
||||
|
||||
|
||||
## v1.4.05
|
||||
|
||||
1. Fix: macOS Language Detection | 修復macOS語言檢測
|
||||
|
||||
|
||||
## v1.4.04
|
||||
|
||||
1. Change Some Language Info to English | 更改一些語言信息為英文
|
||||
2. Add Auto Detect System Language | 增加自動檢測系統語言
|
||||
3. Fixed Some Issues | 修復一些問題
|
||||
|
||||
|
||||
## v1.4.03
|
||||
|
||||
1. Switch to API-based Registration System | 改用API註冊系統替代瀏覽器操作
|
||||
2. Add Support for Latest Cursor Version | 增加支持最新版本Cursor
|
||||
3. Enhance Translation System | 優化多語言翻譯系統
|
||||
4. Add Database Connection Status Messages | 增加數據庫連接狀態提示
|
||||
5. Improve Error Handling for Database Operations | 改進數據庫操作的錯誤處理
|
||||
6. Add New API Integration | 新增API集成
|
||||
7. Optimize Performance and Stability | 優化性能和穩定性
|
||||
|
||||
## v1.4.01
|
||||
|
||||
1. Add Disable Cursor Auto Upgrade | 增加禁用Cursor自動升級
|
||||
|
||||
## v1.3.02
|
||||
|
||||
1. Add Buy Me a Coffee | 增加請我喝杯咖啡
|
||||
2. Add PayPal | 增加PayPal
|
||||
3. Very Small Fix | 非常小的修復
|
||||
4. Fix main.py option number | 修復main.py選項數量
|
||||
|
||||
## v1.3.01
|
||||
|
||||
1. Add Manual Email Input | 增加手動輸入郵箱地址
|
||||
2. Add Manual Code Input | 增加手動輸入驗證碼
|
||||
3. Fix Cursor Options | 修復Cursor選項
|
||||
|
||||
|
||||
## v1.2.02
|
||||
|
||||
1. Add PBlock | 增加PBlock
|
||||
2. Remove uBlock0.chromium | 移除uBlock0.chromium
|
||||
3. Optimize the logic of the script | 優化腳本邏輯
|
||||
4. Optimize Size | 優化大小
|
||||
|
||||
|
||||
## v1.2.01
|
||||
|
||||
1. Fix Cursor Cloudflare Human Verification Problem | 修復Cursor Cloudflare人機驗證問題
|
||||
2. Change to automatic registration account | 全面改為自動註冊賬號
|
||||
3. Change Mail Site | 改變郵箱網站
|
||||
4. Fix Cursor Cloudflare Problem | 修復Cursor Cloudflare問題
|
||||
|
||||
|
||||
## v1.1.01
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/cloudflare_2025-02-12_13-43-21.png" alt="free" width="400"/><br>
|
||||
</p>
|
||||
|
||||
1. Hot Fix Cursor Cloudflare Problem | 修復Cursor Cloudflare問題
|
||||
2. Fix Cursor Cloudflare Human Verification Problem | 修復Cursor Cloudflare人機驗證問題
|
||||
3. Remake signup logic | 重做註冊邏輯
|
||||
|
||||
|
||||
## v1.0.10
|
||||
|
||||
1. Hot Fix Mac Chrome Problem | 修復Mac Chrome問題
|
||||
2. Fix Linux Start Donet Problem | 修復Linux啟動開發者問題
|
||||
|
||||
|
||||
## v1.0.9
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/cloudflare_2025-02-12_13-43-21.png" alt="free" width="400"/><br>
|
||||
</p>
|
||||
|
||||
1. Fixed New 0.45.x Version Reset Machine | 修復新0.45版本重置機器
|
||||
2. Fix Locale Language | 修復多語言
|
||||
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
|
||||
4. Add Remake main.js | 重做main.js
|
||||
|
||||
|
||||
## v1.0.8
|
||||
|
||||
1. Fix New 0.45 Version Reset Machine | 修復新0.45版本重置機器
|
||||
2. Fix Locale Language | 修復多語言
|
||||
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
|
||||
|
||||
|
||||
## v1.0.7 - HotFix
|
||||
|
||||
1. Fix Reset Machine | 修復重置機器
|
||||
2. Fix Locale Language | 修復多語言
|
||||
|
||||
|
||||
## v1.0.7
|
||||
|
||||
1. Add Locale Language Support | 增加多語言支持
|
||||
<p align="center">
|
||||
<img src="./images/locale_2025-01-15_13-40-08.png" alt="locale" width="400"/><br>
|
||||
</p>
|
||||
|
||||
|
||||
## v1.0.6
|
||||
|
||||
1. Add Quit Cursor Option | 增加退出Cursor選項
|
||||
2. Add Recaptcha Path Patch | 增加Recaptcha路徑修復
|
||||
3. Fix Admin Permission | 修復管理員權限問題
|
||||
4. Remove all need admin permission | 移除所有需要管理員權限
|
||||
|
||||
|
||||
## v1.0.5 - HotFix
|
||||
|
||||
1. Fix: Mac Browser Control | 修復Mac瀏覽器控制問題
|
||||
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
|
||||
3. Add Linux Support | 增加Linux支持
|
||||
<p align="center">
|
||||
<img src="./images/fix_2025-01-14_21-30-43.png" alt="fix" width="400"/><br>
|
||||
</p>
|
||||
|
||||
|
||||
## v1.0.5
|
||||
|
||||
1. Remove MachineID | 移除機器碼ID
|
||||
2. Change to automatic registration account | 全面改為自動註冊賬號
|
||||
3. Use your own exclusive new account | 使用自己獨享的新賬號
|
||||
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-14_14-40-37.png" alt="Why" width="400"/><br>
|
||||
</p>
|
||||
|
||||
|
||||
## v1.0.4
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
2. Fix Cloud Lame | 修復雲端慢速模式
|
||||
|
||||
|
||||
## v1.0.3
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
2. Add Manual Reset Machine | 增加手動重置機器
|
||||
3. Add CDN Cloud Control WatchDog | 增加CDN雲端控制WatchDog
|
||||
4. Add Mac OS Support | 增加Mac OS支持
|
||||
5. 759 ++ People use , but star only a few | 759 ++人使用,但只有幾個人點贊
|
||||
<p align="center">
|
||||
<img src="./images/what_2025-01-13_13-32-54.png" alt="Why" width="400"/><br>
|
||||
</p>
|
||||
|
||||
|
||||
## v1.0.2
|
||||
|
||||
1. Fix: Some known issues | 修復了一些已知問題
|
||||
2. Add cloud control device code | 增加雲端控制設備碼
|
||||
3. Cloud reset device code | 雲端重置設備碼
|
||||
4. Remove official WatchDog monitoring | 移除官方WatchDog監控
|
||||
5. Remove Proxy official prompt | 移除Proxy 官方提示
|
||||
6. Fix: Too Many Computer | 修復Too Many Computer 問題
|
||||
7. Fix Billing Issue | 修復計費問題
|
||||
8. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
9. Fix cursor-slow mode | 修復cursor-slow模式
|
||||
|
||||
|
||||
## v1.0.1
|
||||
|
||||
1. Fix: Reset machine ID | 修復了重置機器ID的問題
|
||||
2. Fix: Bypass membership check | 修復了 繞過會員檢查的問題
|
||||
3. Fix: Auto upgrade to "pro" membership | 修復了 自動升級為pro會員的問題
|
||||
4. Fix: Real-time send Token request | 修復了 實時發送Token請求的問題
|
||||
5. Fix: Reset Cursor's configuration | 修復了 重置Cursor的配置的問題
|
||||
|
||||
|
||||
|
||||
## v1.0
|
||||
1. Preview Image | 預覽圖<br>
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_00-50-40.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_00-51-07.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下MachineID 聯繫作者
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_16-24-03.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
5
PBlock/background.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// Import the contents of your other files
|
||||
importScripts('default_filters.js', 'block.js');
|
||||
|
||||
// Your service worker initialization code can go here
|
||||
console.log('Service worker initialized');
|
||||
129
PBlock/block.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* All the actual functionality of the extension; loads as part of the background page.
|
||||
*
|
||||
* Active ingredient is enable(), which sets up the webRequest callbacks.
|
||||
*
|
||||
* */
|
||||
|
||||
let blockingEnabled = false;
|
||||
let allFilters = null;
|
||||
let webRTCPrivacy = null;
|
||||
|
||||
function setFilters(newFilters) {
|
||||
allFilters = newFilters;
|
||||
chrome.storage.local.set({"filters": newFilters});
|
||||
if (blockingEnabled) {
|
||||
refreshFilters();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert URL patterns to declarativeNetRequest rule format
|
||||
function createRules(filters) {
|
||||
return filters.map((filter, index) => ({
|
||||
id: index + 1,
|
||||
priority: 1,
|
||||
action: {
|
||||
type: "block"
|
||||
},
|
||||
condition: {
|
||||
urlFilter: filter.replace("*://", "*"),
|
||||
resourceTypes: [
|
||||
"main_frame", "sub_frame", "stylesheet", "script", "image",
|
||||
"font", "object", "xmlhttprequest", "ping", "media", "websocket"
|
||||
]
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function enable(icon = true) {
|
||||
if (blockingEnabled) return;
|
||||
|
||||
if (allFilters && allFilters.length > 0) {
|
||||
const rules = createRules(allFilters);
|
||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
||||
removeRuleIds: rules.map(rule => rule.id),
|
||||
addRules: rules
|
||||
});
|
||||
}
|
||||
|
||||
blockingEnabled = true;
|
||||
if (icon) {
|
||||
chrome.action.setIcon({
|
||||
path: {
|
||||
"16": "enabled16.png",
|
||||
"48": "enabled48.png"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function disable(icon = true) {
|
||||
if (!blockingEnabled) return;
|
||||
|
||||
const rules = await chrome.declarativeNetRequest.getDynamicRules();
|
||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
||||
removeRuleIds: rules.map(rule => rule.id),
|
||||
addRules: []
|
||||
});
|
||||
|
||||
blockingEnabled = false;
|
||||
if (icon) {
|
||||
chrome.action.setIcon({
|
||||
path: {
|
||||
"16": "disabled.png",
|
||||
"32": "disabled.png"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshFilters() {
|
||||
await disable(false);
|
||||
await enable(true);
|
||||
}
|
||||
|
||||
async function toggleEnabled() {
|
||||
if (blockingEnabled) {
|
||||
await disable();
|
||||
} else {
|
||||
await enable();
|
||||
}
|
||||
}
|
||||
|
||||
function setWebRTCPrivacy(flag, store = true) {
|
||||
webRTCPrivacy = flag;
|
||||
const privacySetting = flag ? "default_public_interface_only" : "default";
|
||||
chrome.privacy.network.webRTCIPHandlingPolicy.set({value: privacySetting});
|
||||
if (store) {
|
||||
chrome.storage.local.set({"webrtc_privacy": flag});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization
|
||||
chrome.storage.local.get("filters",
|
||||
function(result) {
|
||||
if (result["filters"] == undefined) {
|
||||
console.log("Initializing filters to defaults.");
|
||||
setFilters(defaultFilters);
|
||||
} else {
|
||||
setFilters(result["filters"]);
|
||||
allFilters = result["filters"];
|
||||
}
|
||||
|
||||
// toggle blocking on-off via the extension icon
|
||||
chrome.action.onClicked.addListener(toggleEnabled);
|
||||
// initialize blocking
|
||||
enable();
|
||||
}
|
||||
);
|
||||
|
||||
chrome.storage.local.get("webrtc_privacy",
|
||||
function(result) {
|
||||
if (result["webrtc_privacy"] == undefined) {
|
||||
console.log("Initializing WebRTC privacy to default.");
|
||||
setWebRTCPrivacy(false, true);
|
||||
} else {
|
||||
setWebRTCPrivacy(result["webrtc_privacy"], false);
|
||||
}
|
||||
}
|
||||
);
|
||||
14
PBlock/default_filters.js
Normal file
@@ -0,0 +1,14 @@
|
||||
defaultFilters = [
|
||||
// personally, I can't stand the like box
|
||||
//"http://www.facebook.com/plugins/likebox.php?*",
|
||||
"*://*.doubleclick.net/*",
|
||||
"*://partner.googleadservices.com/*",
|
||||
"*://*.googlesyndication.com/*",
|
||||
"*://*.google-analytics.com/*",
|
||||
"*://creative.ak.fbcdn.net/*",
|
||||
"*://*.adbrite.com/*",
|
||||
"*://*.exponential.com/*",
|
||||
"*://*.quantserve.com/*",
|
||||
"*://*.scorecardresearch.com/*",
|
||||
"*://*.zedo.com/*",
|
||||
]
|
||||
BIN
PBlock/enabled16.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
PBlock/enabled48.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
29
PBlock/manifest.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "PBlock",
|
||||
"version": "0.1",
|
||||
"manifest_version": 3,
|
||||
"author": "yeongpin",
|
||||
"url": "https://github.com/yeongpin/PBlock",
|
||||
"description": "Just a simple blocker.",
|
||||
"action": {
|
||||
"default_icon": "enabled48.png"
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"icons": {
|
||||
"16": "enabled16.png",
|
||||
"48": "enabled48.png"
|
||||
},
|
||||
"permissions": [
|
||||
"declarativeNetRequest",
|
||||
"storage",
|
||||
"privacy"
|
||||
],
|
||||
"host_permissions": [
|
||||
"http://*/*",
|
||||
"https://*/*",
|
||||
"ws://*/*",
|
||||
"wss://*/*"
|
||||
]
|
||||
}
|
||||
5
PBlock/rules.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "ruleset_1",
|
||||
"version": "1.0",
|
||||
"rules": []
|
||||
}
|
||||
170
README.md
@@ -1,7 +1,7 @@
|
||||
# ➤ Cursor Free VIP
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200"/>
|
||||
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200" style="border-radius: 6px;"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -9,118 +9,34 @@
|
||||
[](https://github.com/yeongpin/cursor-free-vip/releases/latest)
|
||||
[](https://creativecommons.org/licenses/by-nc-nd/4.0/)
|
||||
[](https://github.com/yeongpin/cursor-free-vip/stargazers)
|
||||
[](https://github.com/yeongpin/cursor-free-vip/releases/latest)
|
||||
|
||||
</p>
|
||||
<h4>Support Latest 0.46.8 Version | 支持最新0.46.8本</h4>
|
||||
|
||||
This is a tool to automatically register (except for Google verification code), 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.
|
||||
|
||||
這是一個自動化工具,自動註冊(除了Google驗證碼),支持 Windows 和 macOS 系統,完成Auth驗證,重置Cursor的配置。
|
||||
這是一個自動化工具,自動註冊 ,支持 Windows 和 macOS 系統,完成Auth驗證,重置Cursor的配置。
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-14_14-40-37.png" alt="new" width="400"/><br>
|
||||
<img src="./images/new_2025-02-27_10-42-44.png" alt="new" width="400" style="border-radius: 6px;"/><br>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<p align="center">
|
||||
<img src="./images/free_2025-01-14_14-59-15.png" alt="free" width="400"/><br>
|
||||
</p>
|
||||
##### If you dont have google chrome , you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
|
||||
|
||||
## ⚠️ Google Recaptcha need to be manually verified, don't be lazy, move your fingers, verify it, otherwise it will keep prompting you to verify ⚠️
|
||||
### If you dont have google chrome , you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
|
||||
|
||||
## ⚠️ 郵箱驗證 需要手動驗證,不要那麼懶,動一動手指,驗證一下,不然會一直提示你驗證 ⚠️
|
||||
### 如果沒有Google Chrome,可以從[這裡](https://www.google.com/intl/en_pk/chrome/)下載
|
||||
##### 如果沒有Google Chrome,可以從[這裡](https://www.google.com/intl/en_pk/chrome/)下載
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
## 🔄 更新日志
|
||||
<details open>
|
||||
<summary>v1.0.5 - HotFix</summary>
|
||||
|
||||
1. Fix: Mac Browser Control | 修復Mac瀏覽器控制問題
|
||||
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
|
||||
3. Add Linux Support | 增加Linux支持
|
||||
<p align="center">
|
||||
<img src="./images/fix_2025-01-14_21-30-43.png" alt="fix" width="400"/><br>
|
||||
</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>v1.0.5</summary>
|
||||
|
||||
1. Remove MachineID | 移除機器碼ID
|
||||
2. Change to automatic registration account | 全面改為自動註冊賬號
|
||||
3. Use your own exclusive new account | 使用自己獨享的新賬號
|
||||
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-14_14-40-37.png" alt="Why" width="400"/><br>
|
||||
</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>v1.0.4</summary>
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
2. Fix Cloud Lame | 修復雲端慢速模式
|
||||
</details>
|
||||
<details>
|
||||
<summary>v1.0.3</summary>
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
2. Add Manual Reset Machine | 增加手動重置機器
|
||||
3. Add CDN Cloud Control WatchDog | 增加CDN雲端控制WatchDog
|
||||
4. Add Mac OS Support | 增加Mac OS支持
|
||||
5. 759 ++ People use , but star only a few | 759 ++人使用,但只有幾個人點贊
|
||||
<p align="center">
|
||||
<img src="./images/what_2025-01-13_13-32-54.png" alt="Why" width="400"/><br>
|
||||
</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>v1.0.2</summary>
|
||||
|
||||
1. Fix: Some known issues | 修復了一些已知問題
|
||||
2. Add cloud control device code | 增加雲端控制設備碼
|
||||
3. Cloud reset device code | 雲端重置設備碼
|
||||
4. Remove official WatchDog monitoring | 移除官方WatchDog監控
|
||||
5. Remove Proxy official prompt | 移除Proxy 官方提示
|
||||
6. Fix: Too Many Computer | 修復Too Many Computer 問題
|
||||
7. Fix Billing Issue | 修復計費問題
|
||||
8. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
9. Fix cursor-slow mode | 修復cursor-slow模式
|
||||
</details>
|
||||
<details>
|
||||
<summary>v1.0.1</summary>
|
||||
|
||||
1. Fix: Reset machine ID | 修復了重置機器ID的問題
|
||||
2. Fix: Bypass membership check | 修復了 繞過會員檢查的問題
|
||||
3. Fix: Auto upgrade to "pro" membership | 修復了 自動升級為pro會員的問題
|
||||
4. Fix: Real-time send Token request | 修復了 實時發送Token請求的問題
|
||||
5. Fix: Reset Cursor's configuration | 修復了 重置Cursor的配置的問題
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.0</summary>
|
||||
1. Preview Image | 預覽圖<br>
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_00-50-40.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_00-51-07.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下MachineID 聯繫作者
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_16-24-03.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
</details>
|
||||
## 🔄 Change Log | 更新日志
|
||||
[Watch Change Log | 查看更新日志](CHANGELOG.md)
|
||||
|
||||
## ✨ Features | 功能特點
|
||||
|
||||
* Automatically register Cursor membership<br>自動註冊Cursor會員<br>
|
||||
|
||||
* Except for Google verification code<br>除了Google驗證碼<br>
|
||||
|
||||
* Support Windows and macOS systems<br>支持 Windows 和 macOS 系統<br>
|
||||
|
||||
* Complete Auth verification<br>完成Auth驗證<br>
|
||||
@@ -137,9 +53,7 @@ This is a tool to automatically register (except for Google verification code),
|
||||
|Linux|ARM64|✅|Linux|ARM64|✅|
|
||||
|
||||
## 👀 How to use | 如何使用
|
||||
|⚠️Must logout your account before running the script⚠️|⚠️必須先登出你的帳戶再運行腳本⚠️ |
|
||||
|:---:|:---:|
|
||||
<br>
|
||||
|
||||
<details open>
|
||||
<summary><b>⭐ Auto Run Script | 腳本自動化運行</b></summary>
|
||||
|
||||
@@ -172,9 +86,24 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
|
||||
|
||||
## ❗ Note | 注意事項
|
||||
|
||||
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>
|
||||
📝 Config | 文件配置
|
||||
`Win / Macos / Linux Path | 路徑 [Documents/.cursor-free-vip/config.ini]`
|
||||
|
||||
* Do not close this script when using Cursor <br>使用Cursor時請勿關閉此腳本<br>
|
||||
```
|
||||
[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
|
||||
```
|
||||
|
||||
* Use administrator to run the script <br>請使用管理員身份運行腳本
|
||||
|
||||
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>
|
||||
|
||||
* This tool is only for learning and research purposes <br>此工具僅供學習和研究使用<br>
|
||||
|
||||
@@ -184,9 +113,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 |
|
||||
|
||||
|
||||
|
||||
@@ -195,11 +124,46 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
|
||||
歡迎提交 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 | 免責聲明
|
||||
|
||||
本工具僅供學習和研究使用,使用本工具所產生的任何後果由使用者自行承擔。 <br>
|
||||
|
||||
源代碼靈感來之 | Original code inspiration from [Here](https://github.com/hmhm2022/gpt-cursor-auto)
|
||||
|
||||
This tool is only for learning and research purposes, and any consequences arising from the use of this tool are borne by the user.
|
||||
|
||||
## 💰 Buy Me a Coffee | 請我喝杯咖啡
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./images/provi-code.jpg" alt="buy_me_a_coffee" width="280"/><br>
|
||||
</td>
|
||||
<td>
|
||||
<img src="./images/paypal.png" alt="buy_me_a_coffee" width="280"/><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
## ⭐ Star History | 星星數
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://star-history.com/#yeongpin/cursor-free-vip&Date)
|
||||
|
||||
</div>
|
||||
|
||||
## 📝 License | 授權
|
||||
|
||||
本項目採用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 授權。
|
||||
Please refer to the [LICENSE](LICENSE.md) file for details.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
3
block_domain.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
oakon.com
|
||||
famamail.com
|
||||
2925.com
|
||||
45
browser.py
@@ -2,6 +2,7 @@ from DrissionPage import ChromiumOptions, ChromiumPage
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
|
||||
|
||||
class BrowserManager:
|
||||
@@ -26,27 +27,36 @@ class BrowserManager:
|
||||
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)
|
||||
|
||||
extension_recaptcha_path = self.get_extension_recaptcha()
|
||||
co.add_extension(extension_recaptcha_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/130.0.6723.92 Safari/537.36"
|
||||
"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.set_argument("--hide-crash-restore-bubble")
|
||||
|
||||
# 随机端口
|
||||
co.auto_port()
|
||||
|
||||
# Mac 系统特殊处理
|
||||
if sys.platform == "darwin":
|
||||
co.set_argument("--no-sandbox")
|
||||
# 系统特定设置
|
||||
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
|
||||
|
||||
@@ -66,23 +76,10 @@ class BrowserManager:
|
||||
def get_extension_block(self):
|
||||
"""获取插件路径"""
|
||||
root_dir = os.getcwd()
|
||||
extension_path = os.path.join(root_dir, "uBlock0.chromium")
|
||||
extension_path = os.path.join(root_dir, "PBlock")
|
||||
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
extension_path = os.path.join(sys._MEIPASS, "uBlock0.chromium")
|
||||
|
||||
if not os.path.exists(extension_path):
|
||||
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||
|
||||
return extension_path
|
||||
|
||||
def get_extension_recaptcha(self):
|
||||
"""获取插件路径"""
|
||||
root_dir = os.getcwd()
|
||||
extension_path = os.path.join(root_dir, "recaptchaPatch")
|
||||
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
extension_path = os.path.join(sys._MEIPASS, "recaptchaPatch")
|
||||
extension_path = os.path.join(sys._MEIPASS, "PBlock")
|
||||
|
||||
if not os.path.exists(extension_path):
|
||||
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||
|
||||
30
build.bat
@@ -2,28 +2,28 @@
|
||||
chcp 65001 > nul
|
||||
cls
|
||||
|
||||
:: 檢查是否以管理員權限運行
|
||||
:: Check if running with administrator privileges
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
:: 如果是管理員權限,只創建虛擬環境後就降權運行
|
||||
:: If running with administrator privileges, create virtual environment and then run with normal user privileges
|
||||
if not exist venv (
|
||||
echo ℹ️ 正在創建虛擬環境...
|
||||
python -m venv venv
|
||||
)
|
||||
|
||||
:: 降權運行剩餘的步驟
|
||||
:: Run remaining steps with normal user privileges
|
||||
echo ℹ️ 以普通用戶權限繼續...
|
||||
powershell -Command "Start-Process -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0 run' -Verb RunAs:NO"
|
||||
exit /b
|
||||
) else (
|
||||
:: 檢查是否是第二階段運行
|
||||
:: Check if running in second stage
|
||||
if "%1"=="run" (
|
||||
goto RUN_BUILD
|
||||
) else (
|
||||
:: 如果是普通權限且需要創建虛擬環境,請求管理員權限
|
||||
:: If running with normal privileges and creating virtual environment is required, request administrator privileges
|
||||
if not exist venv (
|
||||
echo ⚠️ 需要管理員權限來創建虛擬環境
|
||||
echo ℹ️ 正在請求管理員權限...
|
||||
echo ⚠️ Requires administrator privileges to create virtual environment
|
||||
echo ℹ️ Requesting administrator privileges...
|
||||
powershell -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0'"
|
||||
exit /b
|
||||
) else (
|
||||
@@ -33,30 +33,30 @@ if %errorLevel% == 0 (
|
||||
)
|
||||
|
||||
:RUN_BUILD
|
||||
echo ℹ️ 啟動虛擬環境...
|
||||
echo ℹ️ Starting virtual environment...
|
||||
call venv\Scripts\activate.bat
|
||||
if errorlevel 1 (
|
||||
echo ❌ 啟動虛擬環境失敗
|
||||
echo ❌ Failed to start virtual environment
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 檢查並安裝缺失的依賴
|
||||
echo ℹ️ 檢查依賴...
|
||||
:: Check and install missing dependencies
|
||||
echo ℹ️ Checking dependencies...
|
||||
for /f "tokens=1" %%i in (requirements.txt) do (
|
||||
pip show %%i >nul 2>&1 || (
|
||||
echo ℹ️ 安裝 %%i...
|
||||
echo ℹ️ Installing %%i...
|
||||
pip install %%i
|
||||
)
|
||||
)
|
||||
|
||||
echo ℹ️ 開始構建...
|
||||
echo ℹ️ Starting build...
|
||||
python build.py
|
||||
if errorlevel 1 (
|
||||
echo ❌ 構建失敗
|
||||
echo ❌ Build failed
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ✅ 完成!
|
||||
echo ✅ Completed!
|
||||
pause
|
||||
26
build.py
@@ -8,7 +8,7 @@ import shutil
|
||||
from logo import print_logo
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 忽略特定警告
|
||||
# Ignore specific warnings
|
||||
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
||||
|
||||
class LoadingAnimation:
|
||||
@@ -50,21 +50,21 @@ def simulate_progress(message, duration=1.0, steps=20):
|
||||
progress_bar(i, steps, prefix="Progress:", length=40)
|
||||
|
||||
def build():
|
||||
# 清理屏幕
|
||||
# Clean screen
|
||||
os.system("cls" if platform.system().lower() == "windows" else "clear")
|
||||
|
||||
# 顯示 logo
|
||||
# Display logo
|
||||
print_logo()
|
||||
|
||||
# 清理 PyInstaller 緩存
|
||||
print("\033[93m🧹 清理構建緩存...\033[0m")
|
||||
# Clean PyInstaller cache
|
||||
print("\033[93m🧹 Cleaning build cache...\033[0m")
|
||||
if os.path.exists('build'):
|
||||
shutil.rmtree('build')
|
||||
|
||||
# 重新加載環境變量以確保獲取最新版本
|
||||
# Reload environment variables to ensure getting the latest version
|
||||
load_dotenv(override=True)
|
||||
version = os.getenv('VERSION', '1.0.0')
|
||||
print(f"\033[93m📦 正在構建版本: v{version}\033[0m")
|
||||
print(f"\033[93m📦 Building version: v{version}\033[0m")
|
||||
|
||||
try:
|
||||
simulate_progress("Preparing build environment...", 0.5)
|
||||
@@ -72,7 +72,7 @@ def build():
|
||||
loading = LoadingAnimation()
|
||||
loading.start("Building in progress")
|
||||
|
||||
# 根据系统类型设置输出名称
|
||||
# Set output name based on system type
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
os_type = "windows"
|
||||
@@ -86,7 +86,7 @@ def build():
|
||||
|
||||
output_name = f"CursorFreeVIP_{version}_{os_type}"
|
||||
|
||||
# 构建命令
|
||||
# Build command
|
||||
build_command = f'pyinstaller --clean --noconfirm build.spec'
|
||||
output_path = os.path.join('dist', f'{output_name}{ext}')
|
||||
|
||||
@@ -95,16 +95,16 @@ def build():
|
||||
loading.stop()
|
||||
|
||||
if os.path.exists(output_path):
|
||||
print(f"\n\033[92m✅ 構建完成!")
|
||||
print(f"📦 可執行文件位於: {output_path}\033[0m")
|
||||
print(f"\n\033[92m✅ Build completed!")
|
||||
print(f"📦 Executable file located: {output_path}\033[0m")
|
||||
else:
|
||||
print("\n\033[91m❌ 構建失敗:未找到輸出文件\033[0m")
|
||||
print("\n\033[91m❌ Build failed: Output file not found\033[0m")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if loading:
|
||||
loading.stop()
|
||||
print(f"\n\033[91m❌ 構建過程出錯: {str(e)}\033[0m")
|
||||
print(f"\n\033[91m❌ Build process error: {str(e)}\033[0m")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
20
build.sh
@@ -8,7 +8,7 @@ NC='\033[0m' # No Color
|
||||
|
||||
# 检查并安装必要的依赖
|
||||
check_dependencies() {
|
||||
echo -e "${YELLOW}检查系统依赖...${NC}"
|
||||
echo -e "${YELLOW}Checking system dependencies...${NC}"
|
||||
|
||||
# 检查是否为 Ubuntu/Debian
|
||||
if [ -f /etc/debian_version ]; then
|
||||
@@ -16,42 +16,42 @@ check_dependencies() {
|
||||
PACKAGES="python3 python3-pip python3-venv"
|
||||
for pkg in $PACKAGES; do
|
||||
if ! dpkg -l | grep -q "^ii $pkg "; then
|
||||
echo -e "${YELLOW}安装 $pkg...${NC}"
|
||||
echo -e "${YELLOW}Installing $pkg...${NC}"
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y $pkg
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${RED}不支持的系统,请手动安装 python3, pip3 和 python3-venv${NC}"
|
||||
echo -e "${RED}Unsupported system, please install python3, pip3 and python3-venv manually${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建并激活虚拟环境
|
||||
setup_venv() {
|
||||
echo -e "${GREEN}正在创建虚拟环境...${NC}"
|
||||
echo -e "${GREEN}Creating virtual environment...${NC}"
|
||||
python3 -m venv venv
|
||||
|
||||
echo -e "${GREEN}启动虚拟环境...${NC}"
|
||||
echo -e "${GREEN}Starting virtual environment...${NC}"
|
||||
. ./venv/bin/activate || source ./venv/bin/activate
|
||||
}
|
||||
|
||||
# 安装依赖
|
||||
install_dependencies() {
|
||||
echo -e "${GREEN}安装依赖...${NC}"
|
||||
echo -e "${GREEN}Installing dependencies...${NC}"
|
||||
python3 -m pip install --upgrade pip
|
||||
pip3 install -r requirements.txt
|
||||
}
|
||||
|
||||
# 构建程序
|
||||
build_program() {
|
||||
echo -e "${GREEN}开始构建...${NC}"
|
||||
echo -e "${GREEN}Starting build...${NC}"
|
||||
python3 build.py
|
||||
}
|
||||
|
||||
# 清理
|
||||
cleanup() {
|
||||
echo -e "${GREEN}清理虚拟环境...${NC}"
|
||||
echo -e "${GREEN}Cleaning virtual environment...${NC}"
|
||||
deactivate 2>/dev/null || true
|
||||
rm -rf venv
|
||||
}
|
||||
@@ -73,8 +73,8 @@ main() {
|
||||
# 清理
|
||||
cleanup
|
||||
|
||||
echo -e "${GREEN}完成!${NC}"
|
||||
echo "按任意键退出..."
|
||||
echo -e "${GREEN}Completed!${NC}"
|
||||
echo "Press any key to exit..."
|
||||
# 使用兼容的方式读取输入
|
||||
if [ "$(uname)" = "Linux" ]; then
|
||||
read dummy
|
||||
|
||||
@@ -24,13 +24,14 @@ a = Analysis(
|
||||
binaries=[],
|
||||
datas=[
|
||||
('turnstilePatch', 'turnstilePatch'),
|
||||
('recaptchaPatch', 'recaptchaPatch'),
|
||||
('uBlock0.chromium', 'uBlock0.chromium'),
|
||||
('PBlock', 'PBlock'),
|
||||
('locales', 'locales'),
|
||||
('cursor_auth.py', '.'),
|
||||
('reset_machine_manual.py', '.'),
|
||||
('cursor_register.py', '.'),
|
||||
('browser.py', '.'),
|
||||
('control.py', '.')
|
||||
('control.py', '.'),
|
||||
('.env', '.')
|
||||
],
|
||||
hiddenimports=[
|
||||
'cursor_auth',
|
||||
|
||||
273
control.py
@@ -1,11 +1,25 @@
|
||||
import time
|
||||
from colorama import Fore, Style
|
||||
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):
|
||||
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 # 注册标签页
|
||||
@@ -25,232 +39,21 @@ class BrowserControl:
|
||||
# 保存新标签页
|
||||
self.signup_tab = new_browser
|
||||
|
||||
print(f"{Fore.GREEN}Create New Tab Success | 成功创建新窗口{Style.RESET_ALL}")
|
||||
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}Create New Tab Failed | 创建新窗口时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
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}Switch Tab Success | 成功切换窗口{Style.RESET_ALL}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Switch Tab Failed | 切换窗口时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def get_current_tab(self):
|
||||
"""获取当前标签页"""
|
||||
return self.browser
|
||||
|
||||
def generate_new_email(self):
|
||||
"""点击新的按钮生成新邮箱"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Click Generate New Email | 点击生成新邮箱...{Style.RESET_ALL}")
|
||||
new_button = self.browser.ele('xpath://button[contains(@class, "egenbut")]')
|
||||
if new_button:
|
||||
new_button.click()
|
||||
time.sleep(1) # 等待生成
|
||||
print(f"{Fore.GREEN}Generate New Email | 成功生成新邮箱{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}No Generate Button Found | 未找到生成按钮{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Generate New Email Failed | 生成新邮箱时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def select_email_domain(self, domain_index=None):
|
||||
"""选择邮箱域名,如果不指定index则随机选择"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Select Email Domain | 选择邮箱域名...{Style.RESET_ALL}")
|
||||
# 找到下拉框
|
||||
select_element = self.browser.ele('xpath://select[@id="seldom"]')
|
||||
if select_element:
|
||||
# 获取所有选项,包括两个 optgroup 下的所有 option
|
||||
all_options = []
|
||||
|
||||
# 获取 "新的" 组下的选项
|
||||
new_options = self.browser.eles('xpath://select[@id="seldom"]/optgroup[@label="-- 新的 --"]/option')
|
||||
all_options.extend(new_options)
|
||||
|
||||
# 获取 "其他" 组下的选项
|
||||
other_options = self.browser.eles('xpath://select[@id="seldom"]/optgroup[@label="-- 其他 --"]/option')
|
||||
all_options.extend(other_options)
|
||||
|
||||
if all_options:
|
||||
# 如果没有指定索引,随机选择一个
|
||||
if domain_index is None:
|
||||
domain_index = random.randint(0, len(all_options) - 1)
|
||||
|
||||
if domain_index < len(all_options):
|
||||
# 获取选中选项的文本
|
||||
selected_domain = all_options[domain_index].text
|
||||
print(f"{Fore.CYAN}Select Email Domain | 选择域名: {selected_domain}{Style.RESET_ALL}")
|
||||
|
||||
# 点击选择
|
||||
all_options[domain_index].click()
|
||||
time.sleep(1)
|
||||
print(f"{Fore.GREEN}Select Email Domain | 成功选择邮箱域名{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
print(f"{Fore.RED}No Available Domain Options | 未找到可用的域名选项,总共有 {len(all_options)} 个选项{Style.RESET_ALL}")
|
||||
return False
|
||||
else:
|
||||
print(f"{Fore.RED}No Domain Select Box Found | 未找到域名选择框{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Select Email Domain Failed | 选择邮箱域名时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def wait_for_page_load(self, seconds=2):
|
||||
"""等待页面加载"""
|
||||
time.sleep(seconds)
|
||||
|
||||
def navigate_to(self, url):
|
||||
"""导航到指定URL"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}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}Visit {url} Failed | 访问 {url} 时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def copy_and_get_email(self):
|
||||
"""获取邮箱地址"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Get Email Info | 获取邮箱信息...{Style.RESET_ALL}")
|
||||
|
||||
# 等待元素加载
|
||||
time.sleep(1)
|
||||
|
||||
# 获取邮箱名称
|
||||
try:
|
||||
email_div = self.browser.ele('xpath://div[@class="segen"]//div[contains(@style, "color: #e5e5e5")]')
|
||||
if email_div:
|
||||
email_name = email_div.text.split()[0]
|
||||
print(f"{Fore.CYAN}Get Email Name | 找到邮箱名称: {email_name}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}Get Email Name Failed | 无法找到邮箱名称元素{Style.RESET_ALL}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Get Email Name Failed | 获取邮箱名称时出错: {str(e)}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
# 直接使用上一步选择的域名
|
||||
try:
|
||||
domain = self.browser.ele('xpath://select[@id="seldom"]').value
|
||||
if not domain: # 如果获取不到value,尝试获取选中的选项文本
|
||||
selected_option = self.browser.ele('xpath://select[@id="seldom"]/option[1]')
|
||||
domain = selected_option.text if selected_option else "@yopmail.com" # 使用默认域名作为后备
|
||||
except:
|
||||
domain = "@yopmail.com" # 如果出错,使用默认域名
|
||||
|
||||
# 组合完整邮箱地址
|
||||
full_email = f"{email_name}{domain}"
|
||||
print(f"{Fore.GREEN}Get Email Address | 完整邮箱地址: {full_email}{Style.RESET_ALL}")
|
||||
return full_email
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Get Email Address Failed | 获取邮箱地址时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def view_mailbox(self):
|
||||
"""点击查看邮箱按钮"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Enter Mailbox | 正在进入邮箱...{Style.RESET_ALL}")
|
||||
view_button = self.browser.ele('xpath://button[contains(@class, "egenbut") and contains(.//span, "查看邮箱")]')
|
||||
if view_button:
|
||||
view_button.click()
|
||||
time.sleep(2) # 等待页面加载
|
||||
print(f"{Fore.GREEN}Successfully Entered Mailbox | 成功进入邮箱{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}No View Mailbox Button Found | 未找到查看邮箱按钮{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Enter Mailbox Failed | 进入邮箱时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def refresh_mailbox(self):
|
||||
"""刷新邮箱获取最新信息"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Refresh Mailbox | 正在刷新邮箱...{Style.RESET_ALL}")
|
||||
refresh_button = self.browser.ele('xpath://button[@id="refresh"]')
|
||||
if refresh_button:
|
||||
refresh_button.click()
|
||||
time.sleep(2) # 等待刷新完成
|
||||
print(f"{Fore.GREEN}Mailbox Refreshed Successfully | 邮箱刷新成功{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}No Refresh Button Found | 未找到刷新按钮{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Refresh Mailbox Failed | 刷新邮箱时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def check_and_click_recaptcha(self):
|
||||
"""检查并点击验证码复选框"""
|
||||
try:
|
||||
# 使用环境变量或配置文件中预设的坐标
|
||||
click_x = int(os.getenv('RECAPTCHA_X', '100')) # 默认值100
|
||||
click_y = int(os.getenv('RECAPTCHA_Y', '100')) # 默认值100
|
||||
|
||||
print(f"{Fore.CYAN}使用预设坐标点击: x={click_x}, y={click_y}{Style.RESET_ALL}")
|
||||
|
||||
# 直接点击预设坐标
|
||||
self.browser.page.mouse.click(click_x, click_y)
|
||||
print(f"{Fore.GREEN}Clicked reCAPTCHA Position | 已点击 reCAPTCHA 位置{Style.RESET_ALL}")
|
||||
time.sleep(1)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}Click reCAPTCHA Failed | 点击 reCAPTCHA 失败: {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}Found Verification Code | 找到验证码: {verification_code}{Style.RESET_ALL}")
|
||||
return verification_code
|
||||
|
||||
print(f"{Fore.YELLOW}No Valid Verification Code Found | 未找到有效验证码{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Get Verification Code 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}Verification Code Format Error | 验证码格式不正确{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.verification_code_format_error')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.CYAN}Fill Verification Code | 正在填写验证码...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.fill_verification_code')}...{Style.RESET_ALL}")
|
||||
|
||||
# 记住当前标签页(邮箱页面)
|
||||
email_tab = self.browser
|
||||
@@ -264,10 +67,10 @@ class BrowserControl:
|
||||
self.browser.actions.input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
print(f"{Fore.GREEN}Verification Code Filled | 验证码填写完成{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.verification_code_filled')}{Style.RESET_ALL}")
|
||||
|
||||
# 等待页面加载和登录完成
|
||||
print(f"{Fore.CYAN}Wait for Login | 等待登录完成...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.wait_for_login')}...{Style.RESET_ALL}")
|
||||
time.sleep(5)
|
||||
|
||||
# 先访问登录页面确保登录状态
|
||||
@@ -278,7 +81,7 @@ class BrowserControl:
|
||||
# 获取cookies(第一次尝试)
|
||||
token = self.get_cursor_session_token()
|
||||
if not token:
|
||||
print(f"{Fore.YELLOW}Get Token Failed | 首次获取token失败,等待后重试...{Style.RESET_ALL}")
|
||||
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()
|
||||
|
||||
@@ -287,7 +90,7 @@ class BrowserControl:
|
||||
|
||||
# 获取到token后再访问设置页面
|
||||
settings_url = "https://www.cursor.com/settings"
|
||||
print(f"{Fore.CYAN}Get Account Info | 正在访问设置页面获取账户信息...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_account_info')}...{Style.RESET_ALL}")
|
||||
self.browser.get(settings_url)
|
||||
time.sleep(2)
|
||||
|
||||
@@ -302,9 +105,9 @@ class BrowserControl:
|
||||
if usage_ele:
|
||||
usage_info = usage_ele.text
|
||||
total_usage = usage_info.split("/")[-1].strip()
|
||||
print(f"{Fore.GREEN}Account Usage Limit | 账户可用额度上限: {total_usage}{Style.RESET_ALL}")
|
||||
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}Get Account Usage Failed | 获取账户额度信息失败: {str(e)}{Style.RESET_ALL}")
|
||||
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)
|
||||
@@ -312,7 +115,7 @@ class BrowserControl:
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Fill Verification Code Failed | 填写验证码时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
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):
|
||||
@@ -324,19 +127,19 @@ class BrowserControl:
|
||||
# 查找验证框
|
||||
verify_checkbox = self.browser.ele('xpath://label[contains(@class, "cb-lb")]//input[@type="checkbox"]')
|
||||
if verify_checkbox:
|
||||
print(f"{Fore.CYAN}Find Turnstile Verification Box | 找到 Turnstile 验证框,尝试点击...{Style.RESET_ALL}")
|
||||
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}Clicked Turnstile Verification Box | 已点击 Turnstile 验证框{Style.RESET_ALL}")
|
||||
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}Check and Click Turnstile Failed | 未找到 Turnstile 验证框或点击失败: {str(e)}{Style.RESET_ALL}")
|
||||
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}Get Cursor Session Token | 开始获取cookie...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_cursor_session_token')}...{Style.RESET_ALL}")
|
||||
attempts = 0
|
||||
|
||||
while attempts < max_attempts:
|
||||
@@ -348,21 +151,21 @@ class BrowserControl:
|
||||
for cookie in all_cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
token = cookie["value"].split("%3A%3A")[1]
|
||||
print(f"{Fore.GREEN}成功获取CursorSessionToken: {token}{Style.RESET_ALL}")
|
||||
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} Try | 第 {attempts} 次尝试未获取到CursorSessionToken,{retry_interval}秒后重试...{Style.RESET_ALL}")
|
||||
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}Reach Max Attempts ({max_attempts}) | 已达到最大尝试次数({max_attempts}),获取CursorSessionToken失败{Style.RESET_ALL}")
|
||||
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}获取cookie失败: {str(e)}{Style.RESET_ALL}")
|
||||
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}Will Retry in {retry_interval} seconds | 将在 {retry_interval} 秒后重试...{Style.RESET_ALL}")
|
||||
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
|
||||
@@ -373,6 +176,6 @@ class BrowserControl:
|
||||
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}Token Saved to cursor_tokens.txt | Token已保存到 cursor_tokens.txt{Style.RESET_ALL}")
|
||||
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}Save Token Failed | 保存Token时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.save_token_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
@@ -14,20 +14,46 @@ EMOJI = {
|
||||
'ERROR': '❌',
|
||||
'WARN': '⚠️',
|
||||
'INFO': 'ℹ️',
|
||||
'KEY': '🔑'
|
||||
'FILE': '📄',
|
||||
'KEY': '🔐'
|
||||
}
|
||||
|
||||
class CursorAuth:
|
||||
def __init__(self):
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
# 判断操作系统
|
||||
if os.name == "nt": # Windows
|
||||
if sys.platform == "win32": # Windows
|
||||
self.db_path = os.path.join(
|
||||
os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb"
|
||||
)
|
||||
else: # macOS
|
||||
elif sys.platform == 'linux':
|
||||
self.db_path = os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/state.vscdb"
|
||||
)
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
self.db_path = os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||
)
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform')}{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
# 检查数据库文件是否存在
|
||||
if not os.path.exists(self.db_path):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path)}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
# 检查文件权限
|
||||
if not os.access(self.db_path, os.R_OK | os.W_OK):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error')}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
try:
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
|
||||
except sqlite3.Error as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
def update_auth(self, email=None, access_token=None, refresh_token=None):
|
||||
conn = None
|
||||
@@ -54,7 +80,7 @@ class CursorAuth:
|
||||
|
||||
# 重新连接数据库
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
print(f"{EMOJI['INFO']} {Fore.GREEN}Successfully connected to database | 成功连接到数据库{Style.RESET_ALL}")
|
||||
print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 增加超时和其他优化设置
|
||||
@@ -64,13 +90,16 @@ class CursorAuth:
|
||||
|
||||
# 设置要更新的键值对
|
||||
updates = []
|
||||
|
||||
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
|
||||
|
||||
if email is not None:
|
||||
updates.append(("cursorAuth/cachedEmail", email))
|
||||
if access_token is not None:
|
||||
updates.append(("cursorAuth/accessToken", access_token))
|
||||
if refresh_token is not None:
|
||||
updates.append(("cursorAuth/refreshToken", refresh_token))
|
||||
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
|
||||
|
||||
|
||||
# 使用事务来确保数据完整性
|
||||
cursor.execute("BEGIN TRANSACTION")
|
||||
@@ -88,10 +117,10 @@ class CursorAuth:
|
||||
UPDATE ItemTable SET value = ?
|
||||
WHERE key = ?
|
||||
""", (value, key))
|
||||
print(f"{EMOJI['INFO']} {Fore.CYAN}Updating {key.split('/')[-1]}...{Style.RESET_ALL}")
|
||||
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair')} {key.split('/')[-1]}...{Style.RESET_ALL}")
|
||||
|
||||
cursor.execute("COMMIT")
|
||||
print(f"{EMOJI['SUCCESS']} {Fore.GREEN}Database updated successfully | 数据库更新成功{Style.RESET_ALL}")
|
||||
print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully')}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
@@ -99,14 +128,12 @@ class CursorAuth:
|
||||
raise e
|
||||
|
||||
except sqlite3.Error as e:
|
||||
print(f"\n{EMOJI['ERROR']} {Fore.RED}Database error | 数据库错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"\n{EMOJI['ERROR']} {Fore.RED}An error occurred | 发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
print(f"{EMOJI['DB']} {Fore.CYAN}Database connection closed | 数据库连接已关闭{Style.RESET_ALL}")
|
||||
|
||||
|
||||
print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}")
|
||||
|
||||
@@ -10,10 +10,10 @@ from reset_machine_manual import MachineIDResetter
|
||||
os.environ["PYTHONVERBOSE"] = "0"
|
||||
os.environ["PYINSTALLER_VERBOSE"] = "0"
|
||||
|
||||
# 初始化colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# 定义emoji和颜色常量
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
'START': '🚀',
|
||||
'FORM': '📝',
|
||||
@@ -24,14 +24,16 @@ EMOJI = {
|
||||
'ERROR': '❌',
|
||||
'WAIT': '⏳',
|
||||
'SUCCESS': '✅',
|
||||
'MAIL': '<EFBFBD><EFBFBD>',
|
||||
'MAIL': '📧',
|
||||
'KEY': '🔐',
|
||||
'UPDATE': '🔄'
|
||||
'UPDATE': '🔄',
|
||||
'INFO': 'ℹ️'
|
||||
}
|
||||
|
||||
class CursorRegistration:
|
||||
def __init__(self):
|
||||
# 设置为显示模式
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
# Set to display mode
|
||||
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||
self.browser_manager = BrowserManager()
|
||||
self.browser = None
|
||||
@@ -45,268 +47,109 @@ class CursorRegistration:
|
||||
|
||||
# 账号信息
|
||||
self.password = self._generate_password()
|
||||
self.first_name = self._generate_name()
|
||||
self.last_name = self._generate_name()
|
||||
# Generate first name and last name separately
|
||||
first_name = random.choice([
|
||||
"James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
|
||||
"Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
|
||||
"Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
|
||||
])
|
||||
self.last_name = random.choice([
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
|
||||
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
|
||||
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
|
||||
])
|
||||
|
||||
# Modify first letter of first name
|
||||
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
self.first_name = new_first_letter + first_name[1:]
|
||||
|
||||
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
|
||||
|
||||
def _generate_password(self, length=12):
|
||||
"""Generate Random Password"""
|
||||
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||||
return ''.join(random.choices(chars, k=length))
|
||||
|
||||
def _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):
|
||||
"""Setup Temporary Email"""
|
||||
"""Setup Email"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}Staring Browser | 正在启动浏览器...{Style.RESET_ALL}")
|
||||
self.browser = self.browser_manager.init_browser()
|
||||
self.controller = BrowserControl(self.browser)
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.browser_start')}...{Style.RESET_ALL}")
|
||||
|
||||
# 打开邮箱生成器页面(第一个标签页)
|
||||
self.controller.navigate_to(self.mail_url)
|
||||
self.email_tab = self.browser # 保存邮箱标签页
|
||||
self.controller.email_tab = self.email_tab # 同时保存到controller
|
||||
# Create a temporary email using new_tempemail, passing translator
|
||||
from new_tempemail import NewTempEmail
|
||||
self.temp_email = NewTempEmail(self.translator) # Pass translator
|
||||
|
||||
# 生成新邮箱
|
||||
self.controller.generate_new_email()
|
||||
# Create a temporary email
|
||||
email_address = self.temp_email.create_email()
|
||||
if not email_address:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_create_failed')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# 选择随机域名
|
||||
self.controller.select_email_domain()
|
||||
# Save email address
|
||||
self.email_address = email_address
|
||||
self.email_tab = self.temp_email # Pass NewTempEmail instance
|
||||
|
||||
# 获取邮箱地址
|
||||
self.email_address = self.controller.copy_and_get_email()
|
||||
if self.email_address:
|
||||
print(f"{Fore.CYAN}Get Email Address | 获取到的邮箱地址: {self.email_address}{Style.RESET_ALL}")
|
||||
|
||||
# 进入邮箱
|
||||
if self.controller.view_mailbox():
|
||||
return True
|
||||
|
||||
return False
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}Error Occured | 发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def register_cursor(self):
|
||||
"""注册 Cursor 账号"""
|
||||
signup_browser_manager = None
|
||||
"""注册 Cursor"""
|
||||
browser_tab = None
|
||||
try:
|
||||
print(f"\n{Fore.CYAN}{EMOJI['START']}Start Register | 开始 Cursor 注册流程{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
|
||||
|
||||
# 创建新的浏览器实例用于注册
|
||||
from browser import BrowserManager
|
||||
signup_browser_manager = BrowserManager(noheader=True)
|
||||
self.signup_tab = signup_browser_manager.init_browser()
|
||||
# Directly use new_signup.py to sign up
|
||||
from new_signup import main as new_signup_main
|
||||
|
||||
# 访问注册页面
|
||||
self.signup_tab.get(self.sign_up_url)
|
||||
time.sleep(2)
|
||||
|
||||
# 填写注册表单
|
||||
if self.signup_tab.ele("@name=first_name"):
|
||||
print(f"{Fore.YELLOW}{EMOJI['FORM']}Fill Form | 填写注册信息...{Style.RESET_ALL}")
|
||||
|
||||
self.signup_tab.ele("@name=first_name").input(self.first_name)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
self.signup_tab.ele("@name=last_name").input(self.last_name)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
self.signup_tab.ele("@name=email").input(self.email_address)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
self.signup_tab.ele("@type=submit").click()
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Basic Info Submitted | 基本信息提交完成{Style.RESET_ALL}")
|
||||
|
||||
# 处理 Turnstile 验证
|
||||
self._handle_turnstile()
|
||||
|
||||
# 设置密码
|
||||
if self.signup_tab.ele("@name=password"):
|
||||
print(f"{Fore.YELLOW}{EMOJI['PASSWORD']} Set Password | 设置密码...{Style.RESET_ALL}")
|
||||
self.signup_tab.ele("@name=password").input(self.password)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
self.signup_tab.ele("@type=submit").click()
|
||||
# Execute the new registration process, passing translator
|
||||
result, browser_tab = new_signup_main(
|
||||
email=self.email_address,
|
||||
password=self.password,
|
||||
first_name=self.first_name,
|
||||
last_name=self.last_name,
|
||||
email_tab=self.email_tab,
|
||||
controller=self.controller,
|
||||
translator=self.translator
|
||||
)
|
||||
|
||||
self._handle_turnstile()
|
||||
|
||||
# 等待并获取验证码
|
||||
time.sleep(5) # 等待验证码邮件
|
||||
|
||||
self.browser.refresh()
|
||||
if result:
|
||||
# Use the returned browser instance to get account information
|
||||
self.signup_tab = browser_tab # Save browser instance
|
||||
success = self._get_account_info()
|
||||
|
||||
# Close browser after getting information
|
||||
if browser_tab:
|
||||
try:
|
||||
browser_tab.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
return success
|
||||
|
||||
# 获取验证码,设置60秒超时
|
||||
verification_code = None
|
||||
max_attempts = 10 # 增加到10次尝试
|
||||
retry_interval = 5 # 每5秒重试一次
|
||||
start_time = time.time()
|
||||
timeout = 60 # 60秒超时
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} Start Getting Verification Code | 开始获取验证码,将在60秒内尝试...{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
# 检查是否超时
|
||||
if time.time() - start_time > timeout:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Get Verification Code Timeout | 获取验证码超时{Style.RESET_ALL}")
|
||||
break
|
||||
|
||||
verification_code = self.controller.get_verification_code()
|
||||
if verification_code:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Get Verification Code Success | 成功获取验证码: {verification_code}{Style.RESET_ALL}")
|
||||
break
|
||||
|
||||
remaining_time = int(timeout - (time.time() - start_time))
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} Try | 第 {attempt + 1} Get Verification Code | 次尝试未获取到验证码,Time Remaining | 剩余时间: {remaining_time}秒{Style.RESET_ALL}")
|
||||
|
||||
# 刷新邮箱
|
||||
self.browser.refresh()
|
||||
time.sleep(retry_interval)
|
||||
|
||||
if verification_code:
|
||||
# 在注册页面填写验证码
|
||||
for i, digit in enumerate(verification_code):
|
||||
self.signup_tab.ele(f"@data-index={i}").input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Verification Code Filled | 验证码填写完成{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
|
||||
self._handle_turnstile()
|
||||
|
||||
# 检查当前URL
|
||||
current_url = self.signup_tab.url
|
||||
if "authenticator.cursor.sh" in current_url:
|
||||
print(f"{Fore.CYAN}{EMOJI['VERIFY']} Detect Login Page | 检测到登录页面,开始登录...{Style.RESET_ALL}")
|
||||
|
||||
# 填写邮箱
|
||||
email_input = self.signup_tab.ele('@name=email')
|
||||
if email_input:
|
||||
email_input.input(self.email_address)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# 点击提交
|
||||
submit_button = self.signup_tab.ele('@type=submit')
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理 Turnstile 验证
|
||||
self._handle_turnstile()
|
||||
|
||||
# 填写密码
|
||||
password_input = self.signup_tab.ele('@name=password')
|
||||
if password_input:
|
||||
password_input.input(self.password)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# 点击提交
|
||||
submit_button = self.signup_tab.ele('@type=submit')
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理 Turnstile 验证
|
||||
self._handle_turnstile()
|
||||
|
||||
# 等待跳转到设置页面
|
||||
max_wait = 30
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < max_wait:
|
||||
if "cursor.com/settings" in self.signup_tab.url:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Login Success and Jump to Settings Page | 成功登录并跳转到设置页面{Style.RESET_ALL}")
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
# 获取账户信息
|
||||
result = self._get_account_info()
|
||||
|
||||
# 关闭注册窗口
|
||||
if signup_browser_manager:
|
||||
signup_browser_manager.quit()
|
||||
|
||||
return result
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Get Verification Code Timeout | 未能在60秒内获取到验证码{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Register Process 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
|
||||
finally:
|
||||
# 确保在任何情况下都关闭注册窗口
|
||||
if signup_browser_manager:
|
||||
signup_browser_manager.quit()
|
||||
|
||||
def _handle_turnstile(self):
|
||||
"""处理 Turnstile 验证"""
|
||||
print(f"{Fore.YELLOW}{EMOJI['VERIFY']} Handle Turnstile | 处理 Turnstile 验证...{Style.RESET_ALL}")
|
||||
|
||||
# 设置最大等待时间(秒)
|
||||
max_wait_time = 10 # 增加等待时间
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 检查是否超时
|
||||
if time.time() - start_time > max_wait_time:
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} Not Detect Turnstile | 未检测到 Turnstile 验证,继续下一步...{Style.RESET_ALL}")
|
||||
time.sleep(2) # 添加短暂延迟
|
||||
break
|
||||
|
||||
# 检查是否存在验证框
|
||||
# Ensure browser is closed in any case
|
||||
if browser_tab:
|
||||
try:
|
||||
challengeCheck = (
|
||||
self.signup_tab.ele("@id=cf-turnstile", timeout=1)
|
||||
.child()
|
||||
.shadow_root.ele("tag:iframe")
|
||||
.ele("tag:body")
|
||||
.sr("tag:input")
|
||||
)
|
||||
|
||||
if challengeCheck:
|
||||
challengeCheck.click()
|
||||
time.sleep(3) # 增加点击后的等待时间
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Turnstile Passed | 验证通过{Style.RESET_ALL}")
|
||||
time.sleep(2) # 确保验证完全完成
|
||||
break
|
||||
browser_tab.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 检查是否已经通过验证(检查下一步的元素是否存在)
|
||||
try:
|
||||
if (self.signup_tab.ele("@name=password", timeout=0.5) or
|
||||
self.signup_tab.ele("@name=email", timeout=0.5) or
|
||||
self.signup_tab.ele("@data-index=0", timeout=0.5)):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Verification Passed | 验证已通过{Style.RESET_ALL}")
|
||||
time.sleep(2) # 添加短暂延迟
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
# 等待短暂时间后继续检查
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Turnstile Error | Turnstile 处理出错: {str(e)}{Style.RESET_ALL}")
|
||||
time.sleep(2) # 出错时等待更长时间
|
||||
break
|
||||
|
||||
# 最后再等待一下,确保页面加载完成
|
||||
time.sleep(2)
|
||||
|
||||
def _get_account_info(self):
|
||||
"""获取账户信息和 Token"""
|
||||
"""Get Account Information and Token"""
|
||||
try:
|
||||
# 访问设置页面
|
||||
self.signup_tab.get(self.settings_url)
|
||||
time.sleep(2)
|
||||
|
||||
# 获取账户额度信息
|
||||
usage_selector = (
|
||||
"css:div.col-span-2 > div > div > div > div > "
|
||||
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
|
||||
@@ -317,8 +160,8 @@ class CursorRegistration:
|
||||
if usage_ele:
|
||||
total_usage = usage_ele.text.split("/")[-1].strip()
|
||||
|
||||
# 获取 Token
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} Get Cursor Session Token | 开始获取 Cursor Session Token...{Style.RESET_ALL}")
|
||||
print(f"Total Usage: {total_usage}\n")
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
|
||||
max_attempts = 30
|
||||
retry_interval = 2
|
||||
attempts = 0
|
||||
@@ -329,48 +172,47 @@ class CursorRegistration:
|
||||
for cookie in cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
token = cookie["value"].split("%3A%3A")[1]
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Get Token Success | Token 获取成功{Style.RESET_ALL}")
|
||||
# 保存账户信息
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}")
|
||||
self._save_account_info(token, total_usage)
|
||||
return True
|
||||
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(
|
||||
f"{Fore.YELLOW}{EMOJI['WAIT']} Try | 第 {attempts} times to get Token | 次尝试未获取到 Token,{retry_interval}秒后重试...{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Reach Max Attempts ({max_attempts}) | 已达到最大尝试次数({max_attempts}),获取 Token 失败{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Get Token Failed | 获取 Token 失败: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} Will Retry in {retry_interval} seconds | 将在 {retry_interval} 秒后重试...{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Get Account Info Failed | 获取账户信息失败: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _save_account_info(self, token, total_usage):
|
||||
"""保存账户信息到文件"""
|
||||
"""Save Account Information to File"""
|
||||
try:
|
||||
# 先更新认证信息
|
||||
print(f"{Fore.CYAN}{EMOJI['KEY']} 正在更新 Cursor 认证信息...{Style.RESET_ALL}")
|
||||
if update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Cursor Auth Info Updated | 认证信息更新成功{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
|
||||
if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Cursor Auth Info Update Failed | 认证信息更新失败{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}")
|
||||
|
||||
# 重置机器ID
|
||||
print(f"{Fore.CYAN}{EMOJI['UPDATE']} Reset Machine ID | 正在重置机器ID...{Style.RESET_ALL}")
|
||||
MachineIDResetter().reset_machine_ids()
|
||||
print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
|
||||
resetter = MachineIDResetter(self.translator) # 创建实例时传入translator
|
||||
if not resetter.reset_machine_ids(): # 直接调用reset_machine_ids方法
|
||||
raise Exception("Failed to reset machine ID")
|
||||
|
||||
# 保存账户信息到文件
|
||||
# Save account information to file
|
||||
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n{'='*50}\n")
|
||||
f.write(f"Email: {self.email_address}\n")
|
||||
@@ -379,37 +221,46 @@ class CursorRegistration:
|
||||
f.write(f"Usage Limit: {total_usage}\n")
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account Info Saved to cursor_accounts.txt | 账户信息已保存到 cursor_accounts.txt{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Save Account Info Failed | 保存账户信息失败: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
"""启动注册流程"""
|
||||
"""Start Registration Process"""
|
||||
try:
|
||||
if self.setup_email():
|
||||
if self.register_cursor():
|
||||
print(f"\n{Fore.GREEN}{EMOJI['DONE']} Cursor Registration Completed | 注册完成!{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}")
|
||||
return True
|
||||
return False
|
||||
finally:
|
||||
if self.browser_manager:
|
||||
self.browser_manager.quit()
|
||||
# Close email tab
|
||||
if hasattr(self, 'temp_email'):
|
||||
try:
|
||||
self.temp_email.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
|
||||
"""Update Cursor Auth Info"""
|
||||
auth_manager = CursorAuth(translator=self.translator)
|
||||
return auth_manager.update_auth(email, access_token, refresh_token)
|
||||
|
||||
def main(translator=None):
|
||||
"""Main function to be called from main.py"""
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
def update_cursor_auth(email=None, access_token=None, refresh_token=None):
|
||||
"""
|
||||
更新Cursor的认证信息的便捷函数
|
||||
"""
|
||||
auth_manager = CursorAuth()
|
||||
return auth_manager.update_auth(email, access_token, refresh_token)
|
||||
|
||||
def main():
|
||||
registration = CursorRegistration()
|
||||
registration = CursorRegistration(translator)
|
||||
registration.start()
|
||||
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
from main import translator as main_translator
|
||||
main(main_translator)
|
||||
273
cursor_register_manual.py
Normal file
@@ -0,0 +1,273 @@
|
||||
import os
|
||||
from colorama import Fore, Style, init
|
||||
import time
|
||||
import random
|
||||
from browser import BrowserManager
|
||||
from control import BrowserControl
|
||||
from cursor_auth import CursorAuth
|
||||
from reset_machine_manual import MachineIDResetter
|
||||
|
||||
os.environ["PYTHONVERBOSE"] = "0"
|
||||
os.environ["PYINSTALLER_VERBOSE"] = "0"
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
'START': '🚀',
|
||||
'FORM': '📝',
|
||||
'VERIFY': '🔄',
|
||||
'PASSWORD': '🔑',
|
||||
'CODE': '📱',
|
||||
'DONE': '✨',
|
||||
'ERROR': '❌',
|
||||
'WAIT': '⏳',
|
||||
'SUCCESS': '✅',
|
||||
'MAIL': '📧',
|
||||
'KEY': '🔐',
|
||||
'UPDATE': '🔄',
|
||||
'INFO': 'ℹ️'
|
||||
}
|
||||
|
||||
class CursorRegistration:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
# Set to display mode
|
||||
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||
self.browser_manager = BrowserManager()
|
||||
self.browser = None
|
||||
self.controller = None
|
||||
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
|
||||
self.settings_url = "https://www.cursor.com/settings"
|
||||
self.email_address = None
|
||||
self.signup_tab = None
|
||||
self.email_tab = None
|
||||
|
||||
# Generate account information
|
||||
self.password = self._generate_password()
|
||||
# Generate first name and last name separately
|
||||
first_name = random.choice([
|
||||
"James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
|
||||
"Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
|
||||
"Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
|
||||
])
|
||||
self.last_name = random.choice([
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
|
||||
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
|
||||
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
|
||||
])
|
||||
|
||||
# Modify first letter of first name
|
||||
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
self.first_name = new_first_letter + first_name[1:]
|
||||
|
||||
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
|
||||
|
||||
def _generate_password(self, length=12):
|
||||
"""Generate Random Password"""
|
||||
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||||
return ''.join(random.choices(chars, k=length))
|
||||
|
||||
def setup_email(self):
|
||||
"""Setup Email"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}")
|
||||
self.email_address = input().strip()
|
||||
|
||||
if '@' not in self.email_address:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}\n{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def get_verification_code(self):
|
||||
"""Manually Get Verification Code"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}")
|
||||
code = input().strip()
|
||||
|
||||
if not code.isdigit() or len(code) != 6:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
return code
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def register_cursor(self):
|
||||
"""Register Cursor"""
|
||||
browser_tab = None
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
|
||||
|
||||
# Use new_signup.py directly for registration
|
||||
from new_signup import main as new_signup_main
|
||||
|
||||
# Execute new registration process, passing translator
|
||||
result, browser_tab = new_signup_main(
|
||||
email=self.email_address,
|
||||
password=self.password,
|
||||
first_name=self.first_name,
|
||||
last_name=self.last_name,
|
||||
email_tab=None, # No email tab needed
|
||||
controller=self, # Pass self instead of self.controller
|
||||
translator=self.translator
|
||||
)
|
||||
|
||||
if result:
|
||||
# Use the returned browser instance to get account information
|
||||
self.signup_tab = browser_tab # Save browser instance
|
||||
success = self._get_account_info()
|
||||
|
||||
# Close browser after getting information
|
||||
if browser_tab:
|
||||
try:
|
||||
browser_tab.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
return success
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
finally:
|
||||
# Ensure browser is closed in any case
|
||||
if browser_tab:
|
||||
try:
|
||||
browser_tab.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
def _get_account_info(self):
|
||||
"""Get Account Information and Token"""
|
||||
try:
|
||||
self.signup_tab.get(self.settings_url)
|
||||
time.sleep(2)
|
||||
|
||||
usage_selector = (
|
||||
"css:div.col-span-2 > div > div > div > div > "
|
||||
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
|
||||
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
|
||||
)
|
||||
usage_ele = self.signup_tab.ele(usage_selector)
|
||||
total_usage = "未知"
|
||||
if usage_ele:
|
||||
total_usage = usage_ele.text.split("/")[-1].strip()
|
||||
|
||||
print(f"Total Usage: {total_usage}\n")
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
|
||||
max_attempts = 30
|
||||
retry_interval = 2
|
||||
attempts = 0
|
||||
|
||||
while attempts < max_attempts:
|
||||
try:
|
||||
cookies = self.signup_tab.cookies()
|
||||
for cookie in cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
token = cookie["value"].split("%3A%3A")[1]
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}")
|
||||
self._save_account_info(token, total_usage)
|
||||
return True
|
||||
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _save_account_info(self, token, total_usage):
|
||||
"""Save Account Information to File"""
|
||||
try:
|
||||
# Update authentication information first
|
||||
print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
|
||||
if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}")
|
||||
|
||||
# Reset machine ID
|
||||
print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
|
||||
resetter = MachineIDResetter(self.translator) # Create instance with translator
|
||||
if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly
|
||||
raise Exception("Failed to reset machine ID")
|
||||
|
||||
# Save account information to file
|
||||
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n{'='*50}\n")
|
||||
f.write(f"Email: {self.email_address}\n")
|
||||
f.write(f"Password: {self.password}\n")
|
||||
f.write(f"Token: {token}\n")
|
||||
f.write(f"Usage Limit: {total_usage}\n")
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
"""Start Registration Process"""
|
||||
try:
|
||||
if self.setup_email():
|
||||
if self.register_cursor():
|
||||
print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}")
|
||||
return True
|
||||
return False
|
||||
finally:
|
||||
# Close email tab
|
||||
if hasattr(self, 'temp_email'):
|
||||
try:
|
||||
self.temp_email.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
|
||||
"""Convenient function to update Cursor authentication information"""
|
||||
auth_manager = CursorAuth(translator=self.translator)
|
||||
return auth_manager.update_auth(email, access_token, refresh_token)
|
||||
|
||||
def main(translator=None):
|
||||
"""Main function to be called from main.py"""
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
registration = CursorRegistration(translator)
|
||||
registration.start()
|
||||
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
from main import translator as main_translator
|
||||
main(main_translator)
|
||||
135
disable_auto_update.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import shutil
|
||||
from colorama import Fore, Style, init
|
||||
import subprocess
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
"PROCESS": "🔄",
|
||||
"SUCCESS": "✅",
|
||||
"ERROR": "❌",
|
||||
"INFO": "ℹ️",
|
||||
"FOLDER": "📁",
|
||||
"FILE": "📄",
|
||||
"STOP": "🛑",
|
||||
"CHECK": "✔️"
|
||||
}
|
||||
|
||||
class AutoUpdateDisabler:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
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"),
|
||||
"Linux": os.path.expanduser("~/.config/cursor-updater")
|
||||
}
|
||||
|
||||
def _kill_cursor_processes(self):
|
||||
"""End all Cursor processes"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('update.killing_processes') if self.translator else '正在结束 Cursor 进程...'}{Style.RESET_ALL}")
|
||||
|
||||
if self.system == "Windows":
|
||||
subprocess.run(['taskkill', '/F', '/IM', 'Cursor.exe', '/T'], capture_output=True)
|
||||
else:
|
||||
subprocess.run(['pkill', '-f', 'Cursor'], capture_output=True)
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.processes_killed') if self.translator else 'Cursor 进程已结束'}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.kill_process_failed', error=str(e)) if self.translator else f'结束进程失败: {e}'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _remove_updater_directory(self):
|
||||
"""Delete updater directory"""
|
||||
try:
|
||||
updater_path = self.updater_paths.get(self.system)
|
||||
if not updater_path:
|
||||
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['FOLDER']} {self.translator.get('update.removing_directory') if self.translator else '正在删除更新程序目录...'}{Style.RESET_ALL}")
|
||||
|
||||
if os.path.exists(updater_path):
|
||||
if os.path.isdir(updater_path):
|
||||
shutil.rmtree(updater_path)
|
||||
else:
|
||||
os.remove(updater_path)
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.directory_removed') if self.translator else '更新程序目录已删除'}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _create_blocking_file(self):
|
||||
"""Create blocking file"""
|
||||
try:
|
||||
updater_path = self.updater_paths.get(self.system)
|
||||
if not updater_path:
|
||||
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
|
||||
|
||||
# Create empty file
|
||||
open(updater_path, 'w').close()
|
||||
|
||||
# Set read-only attribute
|
||||
if self.system == "Windows":
|
||||
os.system(f'attrib +r "{updater_path}"')
|
||||
else:
|
||||
os.chmod(updater_path, 0o444) # Set to read-only
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.create_block_file_failed', error=str(e)) if self.translator else f'创建阻止文件失败: {e}'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def disable_auto_update(self):
|
||||
"""Disable auto update"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('update.start_disable') if self.translator else '开始禁用自动更新...'}{Style.RESET_ALL}")
|
||||
|
||||
# 1. End processes
|
||||
if not self._kill_cursor_processes():
|
||||
return False
|
||||
|
||||
# 2. Delete directory
|
||||
if not self._remove_updater_directory():
|
||||
return False
|
||||
|
||||
# 3. Create blocking file
|
||||
if not self._create_blocking_file():
|
||||
return False
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.disable_failed', error=str(e)) if self.translator else f'禁用自动更新失败: {e}'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def run(translator=None):
|
||||
"""Convenient function for directly calling the disable function"""
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('update.title') if translator else 'Disable Cursor Auto Update'}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
disabler = AutoUpdateDisabler(translator)
|
||||
disabler.disable_auto_update()
|
||||
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
input(f"{EMOJI['INFO']} {translator.get('update.press_enter') if translator else 'Press Enter to Continue...'}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
from main import translator as main_translator
|
||||
run(main_translator)
|
||||
BIN
images/cloudflare_2025-02-12_13-43-21.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
images/locale_2025-01-15_13-40-08.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
images/new107_2025-01-15_13-53-56.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
images/new_2025-02-27_10-42-44.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
images/pass_2025-02-08_21-48-36.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
images/paypal.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
images/pronew_2025-02-13_15-01-32.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
images/provi-code.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
256
locales/en.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"menu": {
|
||||
"title": "Available Options",
|
||||
"exit": "Exit Program",
|
||||
"reset": "Reset Machine ID",
|
||||
"register": "Register New Cursor Account",
|
||||
"register_manual": "Register Cursor with Custom Email",
|
||||
"quit": "Close Cursor Application",
|
||||
"select_language": "Change Language",
|
||||
"input_choice": "Please enter your choice ({choices})",
|
||||
"invalid_choice": "Invalid selection. Please enter a number from {choices}",
|
||||
"program_terminated": "Program was terminated by user",
|
||||
"error_occurred": "An error occurred: {error}. Please try again",
|
||||
"press_enter": "Press Enter to Exit",
|
||||
"disable_auto_update": "Disable Cursor Auto-Update"
|
||||
},
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "Start Quitting Cursor",
|
||||
"no_process": "No Running Cursor Process",
|
||||
"terminating": "Terminating Process {pid}",
|
||||
"waiting": "Waiting for Process to Exit",
|
||||
"success": "All Cursor Processes Closed",
|
||||
"timeout": "Process Timeout: {pids}",
|
||||
"error": "Error Occurred: {error}"
|
||||
},
|
||||
"reset": {
|
||||
"title": "Cursor Machine ID Reset Tool",
|
||||
"checking": "Checking Config File",
|
||||
"not_found": "Config File Not Found",
|
||||
"no_permission": "Cannot Read or Write Config File, Please Check File Permissions",
|
||||
"reading": "Reading Current Config",
|
||||
"creating_backup": "Creating Config Backup",
|
||||
"backup_exists": "Backup File Already Exists, Skipping Backup Step",
|
||||
"generating": "Generating New Machine ID",
|
||||
"saving_json": "Saving New Config to JSON",
|
||||
"success": "Machine ID Reset Successfully",
|
||||
"new_id": "New Machine ID",
|
||||
"permission_error": "Permission Error: {error}",
|
||||
"run_as_admin": "Please Try Running This Program as Administrator",
|
||||
"process_error": "Reset Process Error: {error}",
|
||||
"updating_sqlite": "Updating SQLite Database",
|
||||
"updating_pair": "Updating Key-Value Pair",
|
||||
"sqlite_success": "SQLite Database Updated Successfully",
|
||||
"sqlite_error": "SQLite Database Update Failed: {error}",
|
||||
"press_enter": "Press Enter to Exit",
|
||||
"unsupported_os": "Unsupported OS: {os}",
|
||||
"linux_path_not_found": "Linux Path Not Found",
|
||||
"updating_system_ids": "Updating System IDs",
|
||||
"system_ids_updated": "System IDs Updated Successfully",
|
||||
"system_ids_update_failed": "System IDs Update Failed: {error}",
|
||||
"windows_guid_updated": "Windows GUID Updated Successfully",
|
||||
"windows_permission_denied": "Windows Permission Denied",
|
||||
"windows_guid_update_failed": "Windows GUID Update Failed",
|
||||
"macos_uuid_updated": "macOS UUID Updated Successfully",
|
||||
"plutil_command_failed": "plutil Command Failed",
|
||||
"start_patching": "Starting Patching getMachineId",
|
||||
"macos_uuid_update_failed": "macOS UUID Update Failed",
|
||||
"current_version": "Current Cursor Version: {version}",
|
||||
"patch_completed": "Patching getMachineId Completed",
|
||||
"patch_failed": "Patching getMachineId Failed: {error}",
|
||||
"version_check_passed": "Cursor Version Check Passed",
|
||||
"file_modified": "File Modified",
|
||||
"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"
|
||||
},
|
||||
"register": {
|
||||
"title": "Cursor Registration Tool",
|
||||
"start": "Starting registration process...",
|
||||
"handling_turnstile": "Processing security verification...",
|
||||
"retry_verification": "Retrying verification...",
|
||||
"detect_turnstile": "Checking security verification...",
|
||||
"verification_success": "Security verification successful",
|
||||
"starting_browser": "Opening browser...",
|
||||
"form_success": "Form submitted successfully",
|
||||
"browser_started": "Browser opened successfully",
|
||||
"waiting_for_second_verification": "Waiting for email verification...",
|
||||
"waiting_for_verification_code": "Waiting for verification code...",
|
||||
"password_success": "Password set successfully",
|
||||
"password_error": "Could not set password: {error}. Please try again",
|
||||
"waiting_for_page_load": "Loading page...",
|
||||
"first_verification_passed": "Initial verification successful",
|
||||
"mailbox": "Successfully accessed email inbox",
|
||||
"register_start": "Start Register",
|
||||
"form_submitted": "Form Submitted, Start Verification...",
|
||||
"filling_form": "Fill Form",
|
||||
"visiting_url": "Visiting URL",
|
||||
"basic_info": "Basic Info Submitted",
|
||||
"handle_turnstile": "Handle Turnstile",
|
||||
"no_turnstile": "Not Detect Turnstile",
|
||||
"turnstile_passed": "Turnstile Passed",
|
||||
"verification_start": "Start Getting Verification Code",
|
||||
"verification_timeout": "Get Verification Code Timeout",
|
||||
"verification_not_found": "No Verification Code Found",
|
||||
"try_get_code": "Try | {attempt} Get Verification Code | Time Remaining: {time}s",
|
||||
"get_account": "Getting Account Info",
|
||||
"get_token": "Get Cursor Session Token",
|
||||
"token_success": "Get Token Success",
|
||||
"token_attempt": "Try | {attempt} times to get Token | Will retry in {time}s",
|
||||
"token_max_attempts": "Reach Max Attempts ({max}) | Failed to get Token",
|
||||
"token_failed": "Get Token Failed: {error}",
|
||||
"account_error": "Get Account Info Failed: {error}",
|
||||
"press_enter": "Press Enter to Exit",
|
||||
"browser_start": "Starting Browser",
|
||||
"open_mailbox": "Opening Mailbox Page",
|
||||
"email_error": "Failed to Get Email Address",
|
||||
"setup_error": "Email Setup Error: {error}",
|
||||
"start_getting_verification_code": "Start Getting Verification Code, Will Try in 60s",
|
||||
"get_verification_code_timeout": "Get Verification Code Timeout",
|
||||
"get_verification_code_success": "Get Verification Code Success",
|
||||
"try_get_verification_code": "Try | {attempt} Get Verification Code | Time Remaining: {remaining_time}s",
|
||||
"verification_code_filled": "Verification Code Filled",
|
||||
"login_success_and_jump_to_settings_page": "Login Success and Jump to Settings Page",
|
||||
"detect_login_page": "Detect Login Page, Start Login...",
|
||||
"cursor_registration_completed": "Cursor Registration Completed!",
|
||||
"set_password": "Set Password",
|
||||
"basic_info_submitted": "Basic Info Submitted",
|
||||
"cursor_auth_info_updated": "Cursor Auth Info Updated",
|
||||
"cursor_auth_info_update_failed": "Cursor Auth Info Update Failed",
|
||||
"reset_machine_id": "Reset Machine ID",
|
||||
"account_info_saved": "Account Info Saved",
|
||||
"save_account_info_failed": "Save Account Info Failed",
|
||||
"get_email_address": "Get Email Address",
|
||||
"update_cursor_auth_info": "Update Cursor Auth Info",
|
||||
"register_process_error": "Register Process Error: {error}",
|
||||
"setting_password": "Setting Password",
|
||||
"manual_code_input": "Manual Code 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"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor Auth Manager",
|
||||
"checking_auth": "Checking Auth File",
|
||||
"auth_not_found": "Auth File Not Found",
|
||||
"auth_file_error": "Auth File Error: {error}",
|
||||
"reading_auth": "Reading Auth File",
|
||||
"updating_auth": "Updating Auth Info",
|
||||
"auth_updated": "Auth Info Updated Successfully",
|
||||
"auth_update_failed": "Auth Info Update Failed: {error}",
|
||||
"auth_file_created": "Auth File Created",
|
||||
"auth_file_create_failed": "Auth File Create Failed: {error}",
|
||||
"press_enter": "Press Enter to Exit",
|
||||
"reset_machine_id": "Reset Machine ID",
|
||||
"database_connection_closed": "Database Connection Closed",
|
||||
"database_updated_successfully": "Database Updated Successfully",
|
||||
"connected_to_database": "Connected to Database",
|
||||
"updating_pair": "Updating Key-Value Pair",
|
||||
"db_not_found": "Database file not found at: {path}",
|
||||
"db_permission_error": "Cannot access database file. Please check permissions",
|
||||
"db_connection_error": "Failed to connect to database: {error}"
|
||||
},
|
||||
"control": {
|
||||
"generate_email": "Generating New Email",
|
||||
"blocked_domain": "Blocked Domain",
|
||||
"select_domain": "Selecting Random Domain",
|
||||
"copy_email": "Copying Email Address",
|
||||
"enter_mailbox": "Entering Mailbox",
|
||||
"refresh_mailbox": "Refreshing Mailbox",
|
||||
"check_verification": "Checking Verification Code",
|
||||
"verification_found": "Verification Code Found",
|
||||
"verification_not_found": "No Verification Code Found",
|
||||
"browser_error": "Browser Control Error: {error}",
|
||||
"navigation_error": "Navigation Error: {error}",
|
||||
"email_copy_error": "Email Copy Error: {error}",
|
||||
"mailbox_error": "Mailbox Error: {error}",
|
||||
"token_saved_to_file": "Token Saved to cursor_tokens.txt",
|
||||
"navigate_to": "Navigating to {url}",
|
||||
"generate_email_success": "Generate Email Success",
|
||||
"select_email_domain": "Select Email Domain",
|
||||
"select_email_domain_success": "Select Email Domain Success",
|
||||
"get_email_name": "Get Email Name",
|
||||
"get_email_name_success": "Get Email Name Success",
|
||||
"get_email_address": "Get Email Address",
|
||||
"get_email_address_success": "Get Email Address Success",
|
||||
"enter_mailbox_success": "Enter Mailbox Success",
|
||||
"found_verification_code": "Found Verification Code",
|
||||
"get_cursor_session_token": "Get Cursor Session Token",
|
||||
"get_cursor_session_token_success": "Get Cursor Session Token Success",
|
||||
"get_cursor_session_token_failed": "Get Cursor Session Token Failed",
|
||||
"save_token_failed": "Save Token Failed",
|
||||
"database_updated_successfully": "Database Updated Successfully",
|
||||
"database_connection_closed": "Database Connection Closed",
|
||||
"no_valid_verification_code": "No Valid Verification Code"
|
||||
},
|
||||
"email": {
|
||||
"starting_browser": "Starting Browser",
|
||||
"visiting_site": "Visiting mail.tm",
|
||||
"create_success": "Email Created Successfully",
|
||||
"create_failed": "Failed to Create Email",
|
||||
"create_error": "Email Creation Error: {error}",
|
||||
"refreshing": "Refreshing Email",
|
||||
"refresh_success": "Email Refreshed Successfully",
|
||||
"refresh_error": "Email Refresh Error: {error}",
|
||||
"refresh_button_not_found": "Refresh Button Not Found",
|
||||
"verification_found": "Verification Found",
|
||||
"verification_not_found": "Verification Not Found",
|
||||
"verification_error": "Verification Error: {error}",
|
||||
"verification_code_found": "Verification Code Found",
|
||||
"verification_code_not_found": "Verification Code Not Found",
|
||||
"verification_code_error": "Verification Code Error: {error}",
|
||||
"address": "Email Address",
|
||||
"all_domains_blocked": "All Domains Blocked, Switching Service",
|
||||
"no_available_domains_after_filtering": "No Available Domains After Filtering",
|
||||
"switching_service": "Switching to {service} Service",
|
||||
"domains_list_error": "Failed to Get Domains List: {error}",
|
||||
"failed_to_get_available_domains": "Failed to Get Available Domains",
|
||||
"domains_excluded": "Domains Excluded: {domains}",
|
||||
"failed_to_create_account": "Failed to Create Account",
|
||||
"account_creation_error": "Account Creation Error: {error}",
|
||||
"blocked_domains": "Blocked Domains: {domains}",
|
||||
"blocked_domains_loaded": "Blocked Domains Loaded: {count}",
|
||||
"blocked_domains_loaded_error": "Blocked Domains Loaded Error: {error}",
|
||||
"blocked_domains_loaded_success": "Blocked Domains Loaded Successfully",
|
||||
"blocked_domains_loaded_timeout": "Blocked Domains Loaded Timeout: {timeout}s",
|
||||
"blocked_domains_loaded_timeout_error": "Blocked Domains Loaded Timeout Error: {error}",
|
||||
"available_domains_loaded": "Available Domains Loaded: {count}",
|
||||
"domains_filtered": "Domains Filtered: {count}",
|
||||
"trying_to_create_email": "Trying to create email: {email}"
|
||||
},
|
||||
"update": {
|
||||
"title": "Disable Cursor Auto Update",
|
||||
"disable_success": "Auto Update Disabled Successfully",
|
||||
"disable_failed": "Disable Auto Update Failed: {error}",
|
||||
"press_enter": "Press Enter to Exit",
|
||||
"start_disable": "Start Disabling Auto Update",
|
||||
"killing_processes": "Killing Processes",
|
||||
"processes_killed": "Processes Killed",
|
||||
"removing_directory": "Removing Directory",
|
||||
"directory_removed": "Directory Removed",
|
||||
"creating_block_file": "Creating Block File",
|
||||
"block_file_created": "Block File Created"
|
||||
},
|
||||
"updater": {
|
||||
"checking": "Checking for updates...",
|
||||
"new_version_available": "New version available! (Current: {current}, Latest: {latest})",
|
||||
"updating": "Updating to the latest version. The program will restart automatically.",
|
||||
"up_to_date": "You are using the latest version.",
|
||||
"check_failed": "Failed to check for updates: {error}",
|
||||
"continue_anyway": "Continuing with current version..."
|
||||
}
|
||||
}
|
||||
253
locales/zh_cn.json
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"menu": {
|
||||
"title": "可用选项",
|
||||
"exit": "退出程序",
|
||||
"reset": "重置机器标识",
|
||||
"register": "注册新 Cursor 账号",
|
||||
"register_manual": "使用自定义邮箱注册",
|
||||
"quit": "关闭 Cursor 应用",
|
||||
"select_language": "更改语言",
|
||||
"input_choice": "请输入您的选择 ({choices})",
|
||||
"invalid_choice": "选择无效,请输入 {choices} 范围内的数字",
|
||||
"program_terminated": "程序已被用户终止",
|
||||
"error_occurred": "发生错误:{error},请重试",
|
||||
"press_enter": "按回车键退出",
|
||||
"disable_auto_update": "禁用 Cursor 自动更新"
|
||||
},
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "开始退出 Cursor",
|
||||
"no_process": "未发现运行中的 Cursor 进程",
|
||||
"terminating": "正在终止进程 {pid}",
|
||||
"waiting": "等待进程退出",
|
||||
"success": "所有 Cursor 进程已正常关闭",
|
||||
"timeout": "以下进程未能在规定时间内关闭: {pids}",
|
||||
"error": "关闭 Cursor 进程时发生错误: {error}"
|
||||
},
|
||||
"reset": {
|
||||
"title": "Cursor 机器标识重置工具",
|
||||
"checking": "检查配置文件",
|
||||
"not_found": "配置文件未找到",
|
||||
"no_permission": "无法读取或写入配置文件,请检查文件权限",
|
||||
"reading": "读取当前配置",
|
||||
"creating_backup": "创建配置备份",
|
||||
"backup_exists": "备份文件已存在,跳过备份步骤",
|
||||
"generating": "生成新机器标识",
|
||||
"saving_json": "保存新配置到JSON",
|
||||
"success": "机器标识重置成功",
|
||||
"new_id": "新机器标识",
|
||||
"permission_error": "权限错误: {error}",
|
||||
"run_as_admin": "请尝试以管理员身份运行此程序",
|
||||
"process_error": "重置进程错误: {error}",
|
||||
"updating_sqlite": "更新SQLite数据库",
|
||||
"updating_pair": "更新键值对",
|
||||
"sqlite_success": "SQLite数据库更新成功",
|
||||
"sqlite_error": "SQLite数据库更新失败: {error}",
|
||||
"press_enter": "按回车键退出",
|
||||
"updating_system_ids": "更新系统ID",
|
||||
"system_ids_updated": "系统ID更新成功",
|
||||
"system_ids_update_failed": "系统ID更新失败: {error}",
|
||||
"unsupported_os": "不支持的操作系统: {os}",
|
||||
"linux_path_not_found": "Linux路径未找到",
|
||||
"windows_guid_updated": "Windows GUID更新成功",
|
||||
"windows_permission_denied": "Windows权限拒绝",
|
||||
"windows_guid_update_failed": "Windows GUID更新失败",
|
||||
"macos_uuid_updated": "macOS UUID更新成功",
|
||||
"plutil_command_failed": "plutil命令失败",
|
||||
"macos_uuid_update_failed": "macOS UUID更新失败",
|
||||
"start_patching": "开始修补getMachineId",
|
||||
"current_version": "当前Cursor版本: {version}",
|
||||
"patch_completed": "getMachineId修补完成",
|
||||
"patch_failed": "getMachineId修补失败: {error}",
|
||||
"version_check_passed": "Cursor版本检查通过",
|
||||
"file_modified": "文件已修改",
|
||||
"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"
|
||||
},
|
||||
"register": {
|
||||
"title": "Cursor 注册工具",
|
||||
"start": "正在启动注册流程...",
|
||||
"browser_started": "浏览器已成功打开",
|
||||
"password_success": "密码设置成功",
|
||||
"password_error": "无法设置密码:{error},请重试",
|
||||
"waiting_for_page_load": "页面加载中...",
|
||||
"mailbox": "已成功访问邮箱",
|
||||
"waiting_for_second_verification": "等待邮箱验证...",
|
||||
"waiting_for_verification_code": "等待验证码...",
|
||||
"first_verification_passed": "初始验证通过",
|
||||
"register_start": "开始注册流程",
|
||||
"form_submitted": "表单已提交,开始验证...",
|
||||
"filling_form": "填写注册信息",
|
||||
"visiting_url": "访问URL",
|
||||
"basic_info": "基本信息提交完成",
|
||||
"handling_turnstile": "正在处理安全验证...",
|
||||
"retry_verification": "正在重试验证...",
|
||||
"detect_turnstile": "正在检查安全验证...",
|
||||
"verification_success": "安全验证通过",
|
||||
"starting_browser": "正在打开浏览器...",
|
||||
"form_success": "表单提交成功",
|
||||
"handle_turnstile": "处理 Turnstile 验证",
|
||||
"no_turnstile": "未检测到 Turnstile 验证",
|
||||
"turnstile_passed": "验证通过",
|
||||
"verification_start": "开始获取验证码",
|
||||
"verification_timeout": "获取验证码超时",
|
||||
"verification_not_found": "未找到验证码",
|
||||
"try_get_code": "第 {attempt} 次尝试获取验证码 | 剩余时间: {time}秒",
|
||||
"get_account": "获取账户信息",
|
||||
"get_token": "获取 Cursor Session Token",
|
||||
"token_success": "Token 获取成功",
|
||||
"token_attempt": "第 {attempt} 次尝试未获取到 Token,{time}秒后重试",
|
||||
"token_max_attempts": "已达到最大尝试次数({max}),获取 Token 失败",
|
||||
"token_failed": "获取 Token 失败: {error}",
|
||||
"account_error": "获取账户信息失败: {error}",
|
||||
"press_enter": "按回车键退出",
|
||||
"browser_start": "正在启动浏览器",
|
||||
"open_mailbox": "正在打开邮箱页面",
|
||||
"email_error": "获取邮箱地址失败",
|
||||
"setup_error": "邮箱设置出错: {error}",
|
||||
"start_getting_verification_code": "开始获取验证码,将在60秒内尝试...",
|
||||
"get_verification_code_timeout": "获取验证码超时",
|
||||
"get_verification_code_success": "成功获取验证码",
|
||||
"try_get_verification_code": "第 {attempt} 次尝试未获取到验证码,剩余时间: {remaining_time}秒",
|
||||
"verification_code_filled": "验证码填写完成",
|
||||
"login_success_and_jump_to_settings_page": "成功登录并跳转到设置页面",
|
||||
"detect_login_page": "检测到登录页面,开始登录...",
|
||||
"cursor_registration_completed": "注册完成!",
|
||||
"set_password": "设置密码",
|
||||
"basic_info_submitted": "基本信息提交完成",
|
||||
"cursor_auth_info_updated": "Cursor 认证信息更新成功",
|
||||
"cursor_auth_info_update_failed": "Cursor 认证信息更新失败",
|
||||
"reset_machine_id": "重置机器ID",
|
||||
"account_info_saved": "账户信息已保存",
|
||||
"save_account_info_failed": "保存账户信息失败",
|
||||
"get_email_address": "获取邮箱地址",
|
||||
"register_process_error": "注册流程错误: {error}",
|
||||
"update_cursor_auth_info": "更新Cursor认证信息",
|
||||
"setting_password": "设置密码",
|
||||
"manual_code_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": "配置已更新"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 认证管理器",
|
||||
"checking_auth": "检查认证文件",
|
||||
"auth_not_found": "未找到认证文件",
|
||||
"auth_file_error": "认证文件错误: {error}",
|
||||
"reading_auth": "读取认证文件",
|
||||
"updating_auth": "更新认证信息",
|
||||
"auth_updated": "认证信息更新成功",
|
||||
"auth_update_failed": "认证信息更新失败: {error}",
|
||||
"auth_file_created": "认证文件已创建",
|
||||
"auth_file_create_failed": "认证文件创建失败: {error}",
|
||||
"press_enter": "按回车键退出",
|
||||
"connected_to_database": "已连接到数据库",
|
||||
"database_updated_successfully": "数据库更新成功",
|
||||
"database_connection_closed": "数据库连接已关闭",
|
||||
"updating_pair": "更新键值对",
|
||||
"db_not_found": "未找到数据库文件:{path}",
|
||||
"db_permission_error": "无法访问数据库文件,请检查权限",
|
||||
"db_connection_error": "连接数据库失败:{error}"
|
||||
},
|
||||
"control": {
|
||||
"generate_email": "生成新邮箱",
|
||||
"select_domain": "选择随机域名",
|
||||
"copy_email": "复制邮箱地址",
|
||||
"enter_mailbox": "进入邮箱",
|
||||
"blocked_domain": "被屏蔽的域名",
|
||||
"refresh_mailbox": "刷新邮箱",
|
||||
"check_verification": "检查验证码",
|
||||
"verification_found": "找到验证码",
|
||||
"verification_not_found": "未找到验证码",
|
||||
"browser_error": "浏览器控制错误: {error}",
|
||||
"navigation_error": "导航错误: {error}",
|
||||
"email_copy_error": "邮箱复制错误: {error}",
|
||||
"mailbox_error": "邮箱错误: {error}",
|
||||
"token_saved_to_file": "Token已保存到 cursor_tokens.txt",
|
||||
"navigate_to": "导航到 {url}",
|
||||
"generate_email_success": "生成邮箱成功",
|
||||
"select_email_domain": "选择邮箱域名",
|
||||
"select_email_domain_success": "选择邮箱域名成功",
|
||||
"get_email_name": "获取邮箱名称",
|
||||
"get_email_name_success": "获取邮箱名称成功",
|
||||
"get_email_address": "获取邮箱地址",
|
||||
"get_email_address_success": "获取邮箱地址成功",
|
||||
"enter_mailbox_success": "进入邮箱成功",
|
||||
"found_verification_code": "找到验证码",
|
||||
"get_cursor_session_token": "获取Cursor Session Token",
|
||||
"get_cursor_session_token_success": "获取Cursor Session Token成功",
|
||||
"get_cursor_session_token_failed": "获取Cursor Session Token失败",
|
||||
"save_token_failed": "保存Token失败",
|
||||
"no_valid_verification_code": "没有有效的验证码"
|
||||
},
|
||||
"email": {
|
||||
"starting_browser": "启动浏览器",
|
||||
"visiting_site": "访问 mail.tm",
|
||||
"create_success": "邮箱创建成功",
|
||||
"create_failed": "邮箱创建失败",
|
||||
"create_error": "邮箱创建错误: {error}",
|
||||
"refreshing": "刷新邮箱",
|
||||
"refresh_success": "邮箱刷新成功",
|
||||
"refresh_error": "邮箱刷新错误: {error}",
|
||||
"refresh_button_not_found": "未找到刷新按钮",
|
||||
"verification_found": "找到验证码",
|
||||
"verification_not_found": "未找到验证码",
|
||||
"verification_error": "验证错误: {error}",
|
||||
"verification_code_found": "找到验证码",
|
||||
"verification_code_not_found": "未找到验证码",
|
||||
"verification_code_error": "验证码错误: {error}",
|
||||
"address": "邮箱地址",
|
||||
"all_domains_blocked": "所有域名都被屏蔽了,切换服务",
|
||||
"no_available_domains_after_filtering": "过滤后没有可用域名",
|
||||
"switching_service": "切换到 {service} 服务",
|
||||
"domains_list_error": "获取域名列表失败: {error}",
|
||||
"failed_to_get_available_domains": "获取可用域名失败",
|
||||
"blocked_domains_loaded": "加载了 {count} 个被屏蔽的域名",
|
||||
"domains_excluded": "排除了 {domains} 个被屏蔽的域名",
|
||||
"failed_to_create_account": "创建账户失败",
|
||||
"account_creation_error": "账户创建错误: {error}",
|
||||
"blocked_domains": "被屏蔽的域名: {domains}",
|
||||
"blocked_domains_loaded_error": "加载被屏蔽的域名失败: {error}",
|
||||
"blocked_domains_loaded_success": "加载被屏蔽的域名成功",
|
||||
"blocked_domains_loaded_timeout": "加载被屏蔽的域名超时: {timeout}秒",
|
||||
"blocked_domains_loaded_timeout_error": "加载被屏蔽的域名超时错误: {error}",
|
||||
"available_domains_loaded": "获取到 {count} 个可用域名",
|
||||
"domains_filtered": "过滤后剩餘 {count} 個可用域名",
|
||||
"trying_to_create_email": "尝试创建邮箱: {email}"
|
||||
},
|
||||
"update": {
|
||||
"title": "禁用 Cursor 自动更新",
|
||||
"disable_success": "自动更新禁用成功",
|
||||
"disable_failed": "禁用自动更新失败: {error}",
|
||||
"press_enter": "按回车键退出",
|
||||
"start_disable": "开始禁用自动更新",
|
||||
"killing_processes": "杀死进程",
|
||||
"processes_killed": "进程已杀死",
|
||||
"removing_directory": "删除目录",
|
||||
"directory_removed": "目录已删除",
|
||||
"creating_block_file": "创建阻止文件",
|
||||
"block_file_created": "阻止文件已创建"
|
||||
},
|
||||
"updater": {
|
||||
"checking": "检查更新...",
|
||||
"new_version_available": "有新版本可用! (当前版本: {current}, 最新版本: {latest})",
|
||||
"updating": "正在更新到最新版本。程序将自动重启。",
|
||||
"up_to_date": "您使用的是最新版本。",
|
||||
"check_failed": "检查更新失败: {error}",
|
||||
"continue_anyway": "继续使用当前版本..."
|
||||
}
|
||||
}
|
||||
234
locales/zh_tw.json
Normal file
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"menu": {
|
||||
"title": "可用選項",
|
||||
"exit": "退出程式",
|
||||
"reset": "重置機器識別碼",
|
||||
"register": "註冊新 Cursor 帳號",
|
||||
"register_manual": "使用自訂郵箱註冊",
|
||||
"quit": "關閉 Cursor 應用程式",
|
||||
"select_language": "變更語言",
|
||||
"input_choice": "請輸入您的選擇 ({choices})",
|
||||
"invalid_choice": "選擇無效,請輸入 {choices} 範圍內的數字",
|
||||
"program_terminated": "程式已被使用者終止",
|
||||
"error_occurred": "發生錯誤:{error},請重試",
|
||||
"press_enter": "按返回鍵退出",
|
||||
"disable_auto_update": "停用 Cursor 自動更新"
|
||||
},
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "開始退出 Cursor",
|
||||
"no_process": "未發現運行中的 Cursor 進程",
|
||||
"terminating": "正在終止進程 {pid}",
|
||||
"waiting": "等待進程退出",
|
||||
"success": "所有 Cursor 進程已正常關閉",
|
||||
"timeout": "以下進程未能在規定時間內關閉: {pids}",
|
||||
"error": "關閉 Cursor 進程時發生錯誤: {error}"
|
||||
},
|
||||
"reset": {
|
||||
"title": "Cursor 機器標識重置工具",
|
||||
"checking": "檢查配置文件",
|
||||
"not_found": "配置文件未找到",
|
||||
"no_permission": "無法讀取或寫入配置文件,請檢查文件權限",
|
||||
"reading": "讀取當前配置",
|
||||
"creating_backup": "創建配置備份",
|
||||
"backup_exists": "備份文件已存在,跳過備份步驟",
|
||||
"generating": "生成新機器標識",
|
||||
"saving_json": "保存新配置到JSON",
|
||||
"success": "機器標識重置成功",
|
||||
"new_id": "新機器標識",
|
||||
"permission_error": "權限錯誤: {error}",
|
||||
"run_as_admin": "請嘗試以管理員身份運行此程序",
|
||||
"process_error": "重置進程錯誤: {error}",
|
||||
"updating_sqlite": "更新SQLite數據庫",
|
||||
"updating_pair": "更新鍵值對",
|
||||
"sqlite_success": "SQLite數據庫更新成功",
|
||||
"sqlite_error": "SQLite數據庫更新失敗: {error}",
|
||||
"press_enter": "按回車鍵退出",
|
||||
"updating_system_ids": "更新系統ID",
|
||||
"system_ids_updated": "系統ID更新成功",
|
||||
"system_ids_update_failed": "系統ID更新失敗: {error}",
|
||||
"unsupported_os": "不支持的操作系統: {os}",
|
||||
"linux_path_not_found": "Linux路徑未找到",
|
||||
"windows_guid_updated": "Windows GUID更新成功",
|
||||
"windows_permission_denied": "Windows權限拒絕",
|
||||
"windows_guid_update_failed": "Windows GUID更新失敗",
|
||||
"macos_uuid_updated": "macOS UUID更新成功",
|
||||
"plutil_command_failed": "plutil命令失敗",
|
||||
"macos_uuid_update_failed": "macOS UUID更新失敗",
|
||||
"start_patching": "開始修補getMachineId",
|
||||
"current_version": "當前Cursor版本: {version}",
|
||||
"patch_completed": "getMachineId修補完成",
|
||||
"patch_failed": "getMachineId修補失敗: {error}",
|
||||
"version_check_passed": "Cursor版本檢查通過",
|
||||
"file_modified": "文件已修改",
|
||||
"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"
|
||||
},
|
||||
"register": {
|
||||
"title": "Cursor 註冊工具",
|
||||
"start": "正在啟動註冊流程...",
|
||||
"handling_turnstile": "正在處理安全驗證...",
|
||||
"retry_verification": "正在重試驗證...",
|
||||
"detect_turnstile": "正在檢查安全驗證...",
|
||||
"verification_success": "安全驗證通過",
|
||||
"starting_browser": "正在開啟瀏覽器...",
|
||||
"form_success": "表單提交成功",
|
||||
"browser_started": "瀏覽器已成功開啟",
|
||||
"waiting_for_second_verification": "等待郵箱驗證...",
|
||||
"waiting_for_verification_code": "等待驗證碼...",
|
||||
"password_success": "密碼設定成功",
|
||||
"password_error": "無法設定密碼:{error},請重試",
|
||||
"waiting_for_page_load": "頁面載入中...",
|
||||
"first_verification_passed": "初始驗證通過",
|
||||
"mailbox": "已成功存取郵箱",
|
||||
"visiting_url": "訪問URL",
|
||||
"register_start": "開始註冊流程",
|
||||
"form_submitted": "表單已提交,開始驗證...",
|
||||
"filling_form": "填寫註冊信息",
|
||||
"basic_info": "基本信息提交完成",
|
||||
"handle_turnstile": "處理 Turnstile 驗證",
|
||||
"no_turnstile": "未檢測到 Turnstile 驗證",
|
||||
"turnstile_passed": "驗證通過",
|
||||
"verification_start": "開始獲取驗證碼",
|
||||
"verification_code_filled": "驗證碼填寫完成",
|
||||
"login_success_and_jump_to_settings_page": "成功登錄並跳轉到設置頁面",
|
||||
"detect_login_page": "檢測到登錄頁面,開始登錄...",
|
||||
"cursor_registration_completed": "註冊完成!",
|
||||
"set_password": "設置密碼",
|
||||
"basic_info_submitted": "基本信息提交完成",
|
||||
"cursor_auth_info_updated": "Cursor 認證信息更新成功",
|
||||
"cursor_auth_info_update_failed": "Cursor 認證信息更新失敗",
|
||||
"reset_machine_id": "重置機器ID",
|
||||
"account_info_saved": "賬戶信息已保存",
|
||||
"save_account_info_failed": "保存賬戶信息失敗",
|
||||
"get_email_address": "獲取郵箱地址",
|
||||
"register_process_error": "註冊流程錯誤: {error}",
|
||||
"update_cursor_auth_info": "更新Cursor認證信息",
|
||||
"setting_password": "設置密碼",
|
||||
"manual_code_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": "配置已更新"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 認證管理器",
|
||||
"checking_auth": "檢查認證文件",
|
||||
"auth_not_found": "未找到認證文件",
|
||||
"auth_file_error": "認證文件錯誤: {error}",
|
||||
"reading_auth": "讀取認證文件",
|
||||
"updating_auth": "更新認證信息",
|
||||
"auth_updated": "認證信息更新成功",
|
||||
"auth_update_failed": "認證信息更新失敗: {error}",
|
||||
"auth_file_created": "認證文件已創建",
|
||||
"auth_file_create_failed": "認證文件創建失敗: {error}",
|
||||
"press_enter": "按回車鍵退出",
|
||||
"connected_to_database": "已連接到數據庫",
|
||||
"database_updated_successfully": "數據庫更新成功",
|
||||
"database_connection_closed": "數據庫連接已關閉",
|
||||
"updating_pair": "更新鍵值對",
|
||||
"db_not_found": "未找到數據庫文件:{path}",
|
||||
"db_permission_error": "無法訪問數據庫文件,請檢查權限",
|
||||
"db_connection_error": "連接數據庫失敗:{error}"
|
||||
},
|
||||
"control": {
|
||||
"generate_email": "生成新郵箱",
|
||||
"select_domain": "選擇隨機域名",
|
||||
"copy_email": "複製郵箱地址",
|
||||
"enter_mailbox": "進入郵箱",
|
||||
"refresh_mailbox": "刷新郵箱",
|
||||
"check_verification": "檢查驗證碼",
|
||||
"verification_found": "找到驗證碼",
|
||||
"verification_not_found": "未找到驗證碼",
|
||||
"browser_error": "瀏覽器控制錯誤: {error}",
|
||||
"navigation_error": "導航錯誤: {error}",
|
||||
"email_copy_error": "郵箱複製錯誤: {error}",
|
||||
"mailbox_error": "郵箱錯誤: {error}",
|
||||
"token_saved_to_file": "Token已保存到 cursor_tokens.txt",
|
||||
"navigate_to": "導航到 {url}",
|
||||
"generate_email_success": "生成郵箱成功",
|
||||
"select_email_domain": "選擇郵箱域名",
|
||||
"select_email_domain_success": "選擇郵箱域名成功",
|
||||
"get_email_name": "獲取郵箱名稱",
|
||||
"get_email_name_success": "獲取郵箱名稱成功",
|
||||
"get_email_address": "獲取郵箱地址",
|
||||
"get_email_address_success": "獲取郵箱地址成功",
|
||||
"enter_mailbox_success": "進入郵箱成功",
|
||||
"found_verification_code": "找到驗證碼",
|
||||
"get_cursor_session_token": "獲取Cursor Session Token",
|
||||
"get_cursor_session_token_success": "獲取Cursor Session Token成功",
|
||||
"get_cursor_session_token_failed": "獲取Cursor Session Token失敗",
|
||||
"save_token_failed": "保存Token失敗",
|
||||
"blocked_domain": "被屏蔽的域名",
|
||||
"no_valid_verification_code": "沒有有效的驗證碼"
|
||||
},
|
||||
"email": {
|
||||
"starting_browser": "啟動瀏覽器",
|
||||
"visiting_site": "訪問 mail.tm",
|
||||
"create_success": "郵箱創建成功",
|
||||
"create_failed": "郵箱創建失敗",
|
||||
"create_error": "郵箱創建錯誤: {error}",
|
||||
"refreshing": "刷新郵箱",
|
||||
"refresh_success": "郵箱刷新成功",
|
||||
"refresh_error": "郵箱刷新錯誤: {error}",
|
||||
"refresh_button_not_found": "未找到刷新按鈕",
|
||||
"verification_found": "找到驗證碼",
|
||||
"verification_not_found": "未找到驗證碼",
|
||||
"verification_error": "驗證錯誤: {error}",
|
||||
"verification_code_found": "找到驗證碼",
|
||||
"verification_code_not_found": "未找到驗證碼",
|
||||
"verification_code_error": "驗證碼錯誤: {error}",
|
||||
"address": "郵箱地址",
|
||||
"all_domains_blocked": "所有域名都被屏蔽了,切換服務",
|
||||
"no_available_domains_after_filtering": "過濾後沒有可用域名",
|
||||
"switching_service": "切換到 {service} 服務",
|
||||
"domains_list_error": "獲取域名列表失敗: {error}",
|
||||
"failed_to_get_available_domains": "獲取可用域名失敗",
|
||||
"domains_excluded": "排除的域名: {domains}",
|
||||
"failed_to_create_account": "創建帳戶失敗",
|
||||
"account_creation_error": "帳戶創建錯誤: {error}",
|
||||
"blocked_domains": "被屏蔽的域名: {domains}",
|
||||
"blocked_domains_loaded": "加載被屏蔽的域名: {domains}",
|
||||
"blocked_domains_loaded_error": "加載被屏蔽的域名失敗: {error}",
|
||||
"blocked_domains_loaded_success": "加載被屏蔽的域名成功",
|
||||
"blocked_domains_loaded_timeout": "加載被屏蔽的域名超時: {timeout}秒",
|
||||
"blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}",
|
||||
"available_domains_loaded": "獲取到 {count} 個可用域名",
|
||||
"domains_filtered": "過濾後剩餘 {count} 個可用域名",
|
||||
"trying_to_create_email": "嘗試創建郵箱: {email}"
|
||||
},
|
||||
"update": {
|
||||
"title": "禁用 Cursor 自动更新",
|
||||
"disable_success": "自動更新禁用成功",
|
||||
"disable_failed": "禁用自動更新失敗: {error}",
|
||||
"press_enter": "按回車鍵退出",
|
||||
"start_disable": "開始禁用自動更新",
|
||||
"killing_processes": "殺死進程",
|
||||
"processes_killed": "進程已殺死",
|
||||
"removing_directory": "刪除目錄",
|
||||
"directory_removed": "目錄已刪除",
|
||||
"creating_block_file": "創建阻止文件",
|
||||
"block_file_created": "阻止文件已創建"
|
||||
},
|
||||
"updater": {
|
||||
"checking": "檢查更新...",
|
||||
"new_version_available": "有新版本可用! (當前版本: {current}, 最新版本: {latest})",
|
||||
"updating": "正在更新到最新版本。程序將自動重啟。",
|
||||
"up_to_date": "您使用的是最新版本。",
|
||||
"check_failed": "檢查更新失敗: {error}",
|
||||
"continue_anyway": "繼續使用當前版本..."
|
||||
}
|
||||
}
|
||||
24
logo.py
@@ -1,5 +1,18 @@
|
||||
from colorama import Fore, Style, init
|
||||
# 初始化 colorama
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Get the current script directory
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Build the full path to the .env file
|
||||
env_path = os.path.join(current_dir, '.env')
|
||||
|
||||
# Load environment variables, specifying the .env file path
|
||||
load_dotenv(env_path)
|
||||
# Get the version number, using the default value if not found
|
||||
version = os.getenv('VERSION', '1.0.0')
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
CURSOR_LOGO = f"""
|
||||
@@ -11,15 +24,18 @@ CURSOR_LOGO = f"""
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
|
||||
{Fore.YELLOW}
|
||||
Pro Version Activator
|
||||
Pro Version Activator v{version}
|
||||
{Fore.GREEN}
|
||||
Author: Pin Studios | yeongpin
|
||||
Author: Pin Studios | yeongpin
|
||||
|
||||
Github: https://github.com/yeongpin/cursor-free-vip
|
||||
{Fore.RED}
|
||||
Press 5 to change language | 按下 5 键切换语言
|
||||
{Style.RESET_ALL}
|
||||
"""
|
||||
|
||||
def print_logo():
|
||||
print(CURSOR_LOGO)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_logo()
|
||||
315
main.py
@@ -1,7 +1,20 @@
|
||||
# main.py
|
||||
# This script allows the user to choose which script to run.
|
||||
from logo import print_logo
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from logo import print_logo, version
|
||||
from colorama import Fore, Style, init
|
||||
import locale
|
||||
import platform
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
# 只在 Windows 系统上导入 windll
|
||||
if platform.system() == 'Windows':
|
||||
import ctypes
|
||||
# 只在 Windows 上导入 windll
|
||||
from ctypes import windll
|
||||
|
||||
# 初始化colorama
|
||||
init()
|
||||
@@ -16,58 +29,308 @@ EMOJI = {
|
||||
"RESET": "🔄",
|
||||
"MENU": "📋",
|
||||
"ARROW": "➜",
|
||||
"LANG": "🌐",
|
||||
"UPDATE": "🔄"
|
||||
}
|
||||
|
||||
class Translator:
|
||||
def __init__(self):
|
||||
self.translations = {}
|
||||
self.current_language = self.detect_system_language() # 使用正确的方法名
|
||||
self.fallback_language = 'en' # Fallback language if translation is missing
|
||||
self.load_translations()
|
||||
|
||||
def detect_system_language(self):
|
||||
"""Detect system language and return corresponding language code"""
|
||||
try:
|
||||
system = platform.system()
|
||||
|
||||
if system == 'Windows':
|
||||
return self._detect_windows_language()
|
||||
else:
|
||||
return self._detect_unix_language()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Failed to detect system language: {e}{Style.RESET_ALL}")
|
||||
return 'en'
|
||||
|
||||
def _detect_windows_language(self):
|
||||
"""Detect language on Windows systems"""
|
||||
try:
|
||||
# 确保我们在 Windows 上
|
||||
if platform.system() != 'Windows':
|
||||
return 'en'
|
||||
|
||||
# 获取键盘布局
|
||||
user32 = ctypes.windll.user32
|
||||
hwnd = user32.GetForegroundWindow()
|
||||
threadid = user32.GetWindowThreadProcessId(hwnd, 0)
|
||||
layout_id = user32.GetKeyboardLayout(threadid) & 0xFFFF
|
||||
|
||||
# Map language ID to our language codes
|
||||
language_map = {
|
||||
0x0409: 'en', # English
|
||||
0x0404: 'zh_tw', # Traditional Chinese
|
||||
0x0804: 'zh_cn', # Simplified Chinese
|
||||
}
|
||||
|
||||
return language_map.get(layout_id, 'en')
|
||||
except:
|
||||
return self._detect_unix_language()
|
||||
|
||||
def _detect_unix_language(self):
|
||||
"""Detect language on Unix-like systems (Linux, macOS)"""
|
||||
try:
|
||||
# Get the system locale
|
||||
system_locale = locale.getdefaultlocale()[0]
|
||||
if not system_locale:
|
||||
return 'en'
|
||||
|
||||
system_locale = system_locale.lower()
|
||||
|
||||
# Map locale to our language codes
|
||||
if system_locale.startswith('zh_tw') or system_locale.startswith('zh_hk'):
|
||||
return 'zh_tw'
|
||||
elif system_locale.startswith('zh_cn'):
|
||||
return 'zh_cn'
|
||||
elif system_locale.startswith('en'):
|
||||
return 'en'
|
||||
|
||||
# Try to get language from LANG environment variable as fallback
|
||||
env_lang = os.getenv('LANG', '').lower()
|
||||
if 'tw' in env_lang or 'hk' in env_lang:
|
||||
return 'zh_tw'
|
||||
elif 'cn' in env_lang:
|
||||
return 'zh_cn'
|
||||
|
||||
return 'en'
|
||||
except:
|
||||
return 'en'
|
||||
|
||||
def load_translations(self):
|
||||
"""Load all available translations"""
|
||||
try:
|
||||
locales_dir = os.path.join(os.path.dirname(__file__), 'locales')
|
||||
if hasattr(sys, '_MEIPASS'):
|
||||
locales_dir = os.path.join(sys._MEIPASS, 'locales')
|
||||
|
||||
if not os.path.exists(locales_dir):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Locales directory not found{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
for file in os.listdir(locales_dir):
|
||||
if file.endswith('.json'):
|
||||
lang_code = file[:-5] # Remove .json
|
||||
try:
|
||||
with open(os.path.join(locales_dir, file), 'r', encoding='utf-8') as f:
|
||||
self.translations[lang_code] = json.load(f)
|
||||
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error loading {file}: {e}{Style.RESET_ALL}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to load translations: {e}{Style.RESET_ALL}")
|
||||
|
||||
def get(self, key, **kwargs):
|
||||
"""Get translated text with fallback support"""
|
||||
try:
|
||||
# Try current language
|
||||
result = self._get_translation(self.current_language, key)
|
||||
if result == key and self.current_language != self.fallback_language:
|
||||
# Try fallback language if translation not found
|
||||
result = self._get_translation(self.fallback_language, key)
|
||||
return result.format(**kwargs) if kwargs else result
|
||||
except Exception:
|
||||
return key
|
||||
|
||||
def _get_translation(self, lang_code, key):
|
||||
"""Get translation for a specific language"""
|
||||
try:
|
||||
keys = key.split('.')
|
||||
value = self.translations.get(lang_code, {})
|
||||
for k in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(k, key)
|
||||
else:
|
||||
return key
|
||||
return value
|
||||
except Exception:
|
||||
return key
|
||||
|
||||
def set_language(self, lang_code):
|
||||
"""Set current language with validation"""
|
||||
if lang_code in self.translations:
|
||||
self.current_language = lang_code
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_available_languages(self):
|
||||
"""Get list of available languages"""
|
||||
return list(self.translations.keys())
|
||||
|
||||
# 创建翻译器实例
|
||||
translator = Translator()
|
||||
|
||||
def print_menu():
|
||||
"""打印菜单选项"""
|
||||
print(f"\n{Fore.CYAN}{EMOJI['MENU']} Available Options | 可用选项:{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} Exit Program | 退出程序")
|
||||
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} Reset Machine Manual | 重置机器标识")
|
||||
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} Register Cursor | 注册 Cursor")
|
||||
print(f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['ERROR']} Quit Cursor | 退出 Cursor")
|
||||
# 在这里添加更多选项
|
||||
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
|
||||
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
|
||||
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')}")
|
||||
print(f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
|
||||
print(f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
|
||||
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
|
||||
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
|
||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||
|
||||
def select_language():
|
||||
"""Language selection menu"""
|
||||
print(f"\n{Fore.CYAN}{EMOJI['LANG']} {translator.get('menu.select_language')}:{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||
|
||||
languages = translator.get_available_languages()
|
||||
for i, lang in enumerate(languages):
|
||||
lang_name = translator.get(f"languages.{lang}")
|
||||
print(f"{Fore.GREEN}{i}{Style.RESET_ALL}. {lang_name}")
|
||||
|
||||
try:
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{len(languages)-1}')}: {Style.RESET_ALL}")
|
||||
if choice.isdigit() and 0 <= int(choice) < len(languages):
|
||||
translator.set_language(languages[int(choice)])
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
|
||||
return False
|
||||
except (ValueError, IndexError):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def check_latest_version():
|
||||
"""Check if current version matches the latest release version"""
|
||||
try:
|
||||
print(f"\n{Fore.CYAN}{EMOJI['UPDATE']} {translator.get('updater.checking')}{Style.RESET_ALL}")
|
||||
|
||||
# Get latest version from GitHub API with timeout and proper headers
|
||||
headers = {
|
||||
'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:
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
# 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:
|
||||
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:
|
||||
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}")
|
||||
return
|
||||
|
||||
def main():
|
||||
print_logo()
|
||||
check_latest_version() # Add version check before showing menu
|
||||
print_menu()
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}Enter your choice (0-3) | 输入选择 (0-3): {Style.RESET_ALL}")
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-6')}: {Style.RESET_ALL}")
|
||||
|
||||
if choice == "0":
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} Exiting program... | 正在退出程序...{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'═' * 50}{Style.RESET_ALL}")
|
||||
return # 直接返回,不等待按键
|
||||
return
|
||||
elif choice == "1":
|
||||
import reset_machine_manual
|
||||
reset_machine_manual.run()
|
||||
break
|
||||
reset_machine_manual.run(translator)
|
||||
print_menu()
|
||||
elif choice == "2":
|
||||
import cursor_register
|
||||
cursor_register.main()
|
||||
break
|
||||
cursor_register.main(translator)
|
||||
print_menu()
|
||||
elif choice == "3":
|
||||
import cursor_register_manual
|
||||
cursor_register_manual.main(translator)
|
||||
print_menu()
|
||||
elif choice == "4":
|
||||
import quit_cursor
|
||||
quit_cursor.quit_cursor()
|
||||
break
|
||||
quit_cursor.quit_cursor(translator)
|
||||
print_menu()
|
||||
elif choice == "5":
|
||||
if select_language():
|
||||
print_menu()
|
||||
continue
|
||||
elif choice == "6":
|
||||
import disable_auto_update
|
||||
disable_auto_update.run(translator)
|
||||
print_menu()
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Invalid choice. Please try again | 无效选择,请重试{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
|
||||
print_menu()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} Program terminated by user | 程序被用户终止{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.program_terminated')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'═' * 50}{Style.RESET_ALL}")
|
||||
return # 直接返回,不等待按键
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} An error occurred | 发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
break
|
||||
|
||||
# 只有在执行完其他选项后才显示按键退出提示
|
||||
print(f"\n{Fore.CYAN}{'═' * 50}{Style.RESET_ALL}")
|
||||
input(f"{EMOJI['INFO']} Press Enter to Exit | 按回车键退出...{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.error_occurred', error=str(e))}{Style.RESET_ALL}")
|
||||
print_menu()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
712
new_signup.py
Normal file
@@ -0,0 +1,712 @@
|
||||
from DrissionPage import ChromiumOptions, ChromiumPage
|
||||
import time
|
||||
import os
|
||||
import signal
|
||||
import random
|
||||
from colorama import Fore, Style
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# 在文件开头添加全局变量
|
||||
_translator = None
|
||||
|
||||
def cleanup_chrome_processes(translator=None):
|
||||
"""清理所有Chrome相关进程"""
|
||||
print("\n正在清理Chrome进程...")
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
os.system('taskkill /F /IM chrome.exe /T 2>nul')
|
||||
os.system('taskkill /F /IM chromedriver.exe /T 2>nul')
|
||||
else:
|
||||
os.system('pkill -f chrome')
|
||||
os.system('pkill -f chromedriver')
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.cleanup_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"清理进程时出错: {e}")
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
"""处理Ctrl+C信号"""
|
||||
global _translator
|
||||
if _translator:
|
||||
print(f"{Fore.CYAN}{_translator.get('register.exit_signal')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n接收到退出信号,正在关闭...")
|
||||
cleanup_chrome_processes(_translator)
|
||||
os._exit(0)
|
||||
|
||||
def simulate_human_input(page, url, 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))
|
||||
|
||||
# 访问目标页面
|
||||
page.get(url)
|
||||
time.sleep(random.uniform(2.0, 3.0)) # 等待页面加载
|
||||
|
||||
def fill_signup_form(page, first_name, last_name, email, translator=None):
|
||||
"""填写注册表单"""
|
||||
try:
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}📧 {translator.get('register.filling_form')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n正在填写注册表单...")
|
||||
|
||||
# 填写名字
|
||||
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))
|
||||
|
||||
# 填写姓氏
|
||||
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))
|
||||
|
||||
# 填写邮箱
|
||||
email_input = page.ele("@name=email")
|
||||
if email_input:
|
||||
email_input.input(email)
|
||||
time.sleep(random.uniform(0.5, 1.0))
|
||||
|
||||
# 点击提交按钮
|
||||
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.form_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("表单填写完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.form_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"填写表单时出错: {e}")
|
||||
return False
|
||||
|
||||
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"
|
||||
]
|
||||
|
||||
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 parse_random_time(time_str):
|
||||
"""解析随机时间范围配置"""
|
||||
try:
|
||||
if '-' in time_str:
|
||||
min_time, max_time = map(float, time_str.split('-'))
|
||||
elif ',' in time_str:
|
||||
min_time, max_time = map(float, time_str.split(','))
|
||||
else:
|
||||
min_time = max_time = float(time_str)
|
||||
return min_time, max_time
|
||||
except:
|
||||
return 1, 3 # 默认值
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
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.config_setup_error', error=str(e)) if translator else f'配置设置出错: {str(e)}'}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
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:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.handling_turnstile')}{Style.RESET_ALL}")
|
||||
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')
|
||||
min_random_time, max_random_time = parse_random_time(random_time_str)
|
||||
|
||||
max_retries = 2
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
retry_count += 1
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.retry_verification', attempt=retry_count)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"第 {retry_count} 次尝试验证...")
|
||||
|
||||
try:
|
||||
# 尝试重置 turnstile
|
||||
page.run_js("try { turnstile.reset() } catch(e) { }")
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
# 定位验证框元素
|
||||
challenge_check = (
|
||||
page.ele("@id=cf-turnstile", timeout=2)
|
||||
.child()
|
||||
.shadow_root.ele("tag:iframe")
|
||||
.ele("tag:body")
|
||||
.sr("tag:input")
|
||||
)
|
||||
|
||||
if challenge_check:
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.detect_turnstile')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("检测到验证框...")
|
||||
|
||||
# from config
|
||||
time.sleep(random.uniform(min_random_time, max_random_time))
|
||||
challenge_check.click()
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
# check verification result
|
||||
if check_verification_success(page, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证通过!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"验证尝试失败: {e}")
|
||||
|
||||
# 检查是否已经验证成功
|
||||
if check_verification_success(page, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证通过!")
|
||||
return True
|
||||
|
||||
time.sleep(random.uniform(min_random_time, max_random_time))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("超出最大重试次数")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"验证过程出错: {e}")
|
||||
return False
|
||||
|
||||
def check_verification_success(page, translator=None):
|
||||
"""检查验证是否成功"""
|
||||
try:
|
||||
# 检查是否存在后续表单元素,这表示验证已通过
|
||||
if (page.ele("@name=password", timeout=0.5) or
|
||||
page.ele("@name=email", timeout=0.5) or
|
||||
page.ele("@data-index=0", timeout=0.5) or
|
||||
page.ele("Account Settings", timeout=0.5)):
|
||||
return True
|
||||
|
||||
# 检查是否出现错误消息
|
||||
error_messages = [
|
||||
'xpath://div[contains(text(), "Can\'t verify the user is human")]',
|
||||
'xpath://div[contains(text(), "Error: 600010")]',
|
||||
'xpath://div[contains(text(), "Please try again")]'
|
||||
]
|
||||
|
||||
for error_xpath in error_messages:
|
||||
if page.ele(error_xpath):
|
||||
return False
|
||||
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def generate_password(length=12):
|
||||
"""生成随机密码"""
|
||||
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||||
return ''.join(random.choices(chars, k=length))
|
||||
|
||||
def fill_password(page, password: str, translator=None) -> bool:
|
||||
"""
|
||||
填写密码表单
|
||||
"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}")
|
||||
|
||||
# 等待密码框出现
|
||||
max_retries = 5
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
# 使用 DrissionPage 的方式查找密码输入框
|
||||
password_input = page.ele('@type=password', timeout=3)
|
||||
if password_input:
|
||||
break
|
||||
time.sleep(2)
|
||||
except:
|
||||
if i == max_retries - 1:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_field_not_found') if translator else '未找到密码输入框'}{Style.RESET_ALL}")
|
||||
return False
|
||||
continue
|
||||
|
||||
if password_input:
|
||||
# 清除可能存在的旧值
|
||||
password_input.click()
|
||||
time.sleep(0.5)
|
||||
password_input.input(password)
|
||||
time.sleep(1)
|
||||
|
||||
# 查找并点击提交按钮
|
||||
submit_button = page.ele('@type=submit')
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(2)
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.continue_button_not_found') if translator else '未找到继续按钮'}{Style.RESET_ALL}")
|
||||
return False
|
||||
else:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_input_failed') if translator else '密码输入失败'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_setting_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, translator=None):
|
||||
"""处理验证码"""
|
||||
try:
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
|
||||
# 检查是否使用手动输入验证码
|
||||
if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式
|
||||
verification_code = controller.get_verification_code()
|
||||
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))
|
||||
|
||||
print(f"{translator.get('register.verification_success')}")
|
||||
time.sleep(3)
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
|
||||
# 访问设置页面
|
||||
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) # 等待页面加载
|
||||
return True, browser_tab
|
||||
|
||||
return False, None
|
||||
|
||||
# 自动获取验证码逻辑
|
||||
elif email_tab:
|
||||
print(f"{translator.get('register.waiting_for_verification_code')}")
|
||||
time.sleep(5) # 等待验证码邮件
|
||||
|
||||
# 使用已有的 email_tab 刷新邮箱
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(3)
|
||||
|
||||
# 检查邮箱是否有验证码邮件
|
||||
if email_tab.check_for_cursor_email():
|
||||
verification_code = email_tab.get_verification_code()
|
||||
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))
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
|
||||
# 访问设置页面
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
browser_tab.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # 等待页面加载
|
||||
return True, browser_tab
|
||||
|
||||
else:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("最后一次验证失败")
|
||||
return False, None
|
||||
|
||||
# 获取验证码,设置超时
|
||||
verification_code = None
|
||||
max_attempts = 20
|
||||
retry_interval = 10
|
||||
start_time = time.time()
|
||||
timeout = 160
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}")
|
||||
|
||||
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}")
|
||||
break
|
||||
|
||||
verification_code = controller.get_verification_code()
|
||||
if verification_code:
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
break
|
||||
|
||||
remaining_time = int(timeout - (time.time() - start_time))
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}")
|
||||
|
||||
# 刷新邮箱
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(retry_interval)
|
||||
|
||||
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))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
|
||||
# 直接访问设置页面
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
browser_tab.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # 等待页面加载
|
||||
|
||||
# 直接返回成功,让 cursor_register.py 处理账户信息获取
|
||||
return True, browser_tab
|
||||
|
||||
else:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
def handle_sign_in(browser_tab, email, password, translator=None):
|
||||
"""处理登录流程"""
|
||||
try:
|
||||
# 检查是否在登录页面
|
||||
sign_in_header = browser_tab.ele('xpath://h1[contains(text(), "Sign in")]')
|
||||
if not sign_in_header:
|
||||
return True # 如果不是登录页面,说明已经登录成功
|
||||
|
||||
print(f"{Fore.CYAN}检测到登录页面,开始登录...{Style.RESET_ALL}")
|
||||
|
||||
# 填写邮箱
|
||||
email_input = browser_tab.ele('@name=email')
|
||||
if email_input:
|
||||
email_input.input(email)
|
||||
time.sleep(1)
|
||||
|
||||
# 点击 Continue
|
||||
continue_button = browser_tab.ele('xpath://button[contains(@class, "BrandedButton") and text()="Continue"]')
|
||||
if continue_button:
|
||||
continue_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
# 填写密码
|
||||
password_input = browser_tab.ele('@name=password')
|
||||
if password_input:
|
||||
password_input.input(password)
|
||||
time.sleep(1)
|
||||
|
||||
# 点击 Sign in
|
||||
sign_in_button = browser_tab.ele('xpath://button[@name="intent" and @value="password"]')
|
||||
if sign_in_button:
|
||||
sign_in_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
print(f"{Fore.GREEN}登录成功!{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
return True
|
||||
|
||||
print(f"{Fore.RED}登录失败{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}登录过程出错: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def main(email=None, password=None, first_name=None, last_name=None, email_tab=None, controller=None, translator=None):
|
||||
"""主函数,可以接收账号信息、邮箱标签页和翻译器"""
|
||||
global _translator
|
||||
_translator = translator # 保存到全局变量
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
page = None
|
||||
success = False
|
||||
try:
|
||||
config, page = setup_driver(translator)
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}")
|
||||
|
||||
# 访问注册页面
|
||||
url = "https://authenticator.cursor.sh/sign-up"
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
|
||||
|
||||
# 访问页面
|
||||
simulate_human_input(page, url, translator)
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
|
||||
time.sleep(5)
|
||||
|
||||
# 如果没有提供账号信息,则生成随机信息
|
||||
if not all([email, password, first_name, last_name]):
|
||||
first_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize()
|
||||
last_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize()
|
||||
email = f"{first_name.lower()}{random.randint(100,999)}@example.com"
|
||||
password = generate_password()
|
||||
|
||||
# 保存账号信息
|
||||
with open('test_accounts.txt', 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n{'='*50}\n")
|
||||
f.write(f"Email: {email}\n")
|
||||
f.write(f"Password: {password}\n")
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
# 填写表单
|
||||
if fill_signup_form(page, first_name, last_name, email, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}")
|
||||
|
||||
# 处理第一次 Turnstile 验证
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}")
|
||||
|
||||
# 填写密码
|
||||
if fill_password(page, password, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
|
||||
# 处理第二次 Turnstile 验证
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
if handle_verification_code(page, email_tab, controller, email, password, translator):
|
||||
success = True
|
||||
return True, page # 返回浏览器实例
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}")
|
||||
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"发生错误: {e}")
|
||||
return False, None
|
||||
finally:
|
||||
if page and not success: # 只在失败时清理
|
||||
try:
|
||||
page.quit()
|
||||
except:
|
||||
pass
|
||||
cleanup_chrome_processes(translator)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # 直接运行时不传参数,使用随机生成的信息
|
||||
368
new_tempemail.py
Normal file
@@ -0,0 +1,368 @@
|
||||
from DrissionPage import ChromiumPage, ChromiumOptions
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from colorama import Fore, Style, init
|
||||
import requests
|
||||
import random
|
||||
import string
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
class NewTempEmail:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
# Randomly choose between mail.tm and mail.gw
|
||||
self.services = [
|
||||
{"name": "mail.tm", "api_url": "https://api.mail.tm"},
|
||||
{"name": "mail.gw", "api_url": "https://api.mail.gw"}
|
||||
]
|
||||
self.selected_service = random.choice(self.services)
|
||||
self.api_url = self.selected_service["api_url"]
|
||||
self.token = None
|
||||
self.email = None
|
||||
self.password = None
|
||||
self.blocked_domains = self.get_blocked_domains()
|
||||
|
||||
def get_blocked_domains(self):
|
||||
"""Get blocked domains list"""
|
||||
try:
|
||||
block_url = "https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/block_domain.txt"
|
||||
response = requests.get(block_url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
# 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}")
|
||||
else:
|
||||
print(f"{Fore.CYAN}ℹ️ 已加载 {len(domains)} 个被屏蔽的域名{Style.RESET_ALL}")
|
||||
return domains
|
||||
return []
|
||||
except Exception as e:
|
||||
if self.translator:
|
||||
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.blocked_domains_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 获取被屏蔽域名列表失败: {str(e)}{Style.RESET_ALL}")
|
||||
return []
|
||||
|
||||
def exclude_blocked_domains(self, domains):
|
||||
"""Exclude blocked domains"""
|
||||
if not self.blocked_domains:
|
||||
return domains
|
||||
|
||||
filtered_domains = []
|
||||
for domain in domains:
|
||||
if domain['domain'] not in self.blocked_domains:
|
||||
filtered_domains.append(domain)
|
||||
|
||||
excluded_count = len(domains) - len(filtered_domains)
|
||||
if excluded_count > 0:
|
||||
if self.translator:
|
||||
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.domains_excluded', domains=excluded_count)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 已排除 {excluded_count} 个被屏蔽的域名{Style.RESET_ALL}")
|
||||
|
||||
return filtered_domains
|
||||
|
||||
def _generate_credentials(self):
|
||||
"""generate random username and password"""
|
||||
username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
||||
password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12))
|
||||
return username, password
|
||||
|
||||
def create_email(self):
|
||||
"""create temporary email"""
|
||||
max_retries = 3 # Maximum number of retries
|
||||
attempt = 0 # Current attempt count
|
||||
|
||||
while attempt < max_retries:
|
||||
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}")
|
||||
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:
|
||||
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:
|
||||
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}")
|
||||
|
||||
if not filtered_domains:
|
||||
if self.translator:
|
||||
print(f"{Fore.RED}❌ {self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}")
|
||||
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}")
|
||||
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:
|
||||
if attempt < max_retries:
|
||||
print(f"{Fore.YELLOW}⚠️ 尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}")
|
||||
else:
|
||||
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):
|
||||
"""close browser"""
|
||||
if self.page:
|
||||
self.page.quit()
|
||||
|
||||
def refresh_inbox(self):
|
||||
"""refresh inbox"""
|
||||
try:
|
||||
if self.translator:
|
||||
print(f"{Fore.CYAN}🔄 {self.translator.get('email.refreshing')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}")
|
||||
|
||||
# Use API to get latest email
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
if self.translator:
|
||||
print(f"{Fore.GREEN}✅ {self.translator.get('email.refresh_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}✅ 邮箱刷新成功{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
if self.translator:
|
||||
print(f"{Fore.RED}❌ {self.translator.get('email.refresh_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ 刷新邮箱失败{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if self.translator:
|
||||
print(f"{Fore.RED}❌ {self.translator.get('email.refresh_error')}: {str(e)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ 刷新邮箱出错: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def check_for_cursor_email(self):
|
||||
"""检查是否有 Cursor 的验证邮件"""
|
||||
try:
|
||||
# Use API to get email list
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
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:
|
||||
if self.translator:
|
||||
print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_found')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
if self.translator:
|
||||
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_not_found')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 未找到验证邮件{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if self.translator:
|
||||
print(f"{Fore.RED}❌ {self.translator.get('email.verification_error')}: {str(e)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ 检查验证邮件出错: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def get_verification_code(self):
|
||||
"""get verification code"""
|
||||
try:
|
||||
# Use API to get email list
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
response = requests.get(f"{self.api_url}/messages", headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
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"]
|
||||
# Find 6-digit verification code
|
||||
import re
|
||||
code_match = re.search(r'\b\d{6}\b', email_content)
|
||||
|
||||
if code_match:
|
||||
code = code_match.group(0)
|
||||
if self.translator:
|
||||
print(f"{Fore.GREEN}✅ {self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}")
|
||||
return code
|
||||
|
||||
if self.translator:
|
||||
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
if self.translator:
|
||||
print(f"{Fore.RED}❌ {self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def main(translator=None):
|
||||
temp_email = NewTempEmail(translator)
|
||||
|
||||
try:
|
||||
email = temp_email.create_email()
|
||||
if email:
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}📧 {translator.get('email.address')}: {email}{Style.RESET_ALL}")
|
||||
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()
|
||||
else:
|
||||
choice = input("\n按 R 刷新邮箱,按 Q 退出: ").lower()
|
||||
if choice == 'r':
|
||||
temp_email.refresh_inbox()
|
||||
elif choice == 'q':
|
||||
break
|
||||
|
||||
finally:
|
||||
temp_email.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,33 +1,33 @@
|
||||
import psutil
|
||||
import time
|
||||
from colorama import Fore, Style, init
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 初始化colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# 定义emoji和颜色常量
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
"FILE": "📄",
|
||||
"BACKUP": "💾",
|
||||
"PROCESS": "⚙️",
|
||||
"SUCCESS": "✅",
|
||||
"ERROR": "❌",
|
||||
"INFO": "ℹ️",
|
||||
"RESET": "🔄",
|
||||
"PROCESS": "⚙️",
|
||||
"WAIT": "⏳"
|
||||
}
|
||||
|
||||
class CursorQuitter:
|
||||
def __init__(self, timeout=5):
|
||||
def __init__(self, timeout=5, translator=None):
|
||||
self.timeout = timeout
|
||||
self.translator = translator # Use the passed translator
|
||||
|
||||
def quit_cursor(self):
|
||||
"""温和地关闭 Cursor 进程"""
|
||||
"""Gently close Cursor processes"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['PROCESS']} Start Quitting Cursor | 开始退出 Cursor...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.start')}...{Style.RESET_ALL}")
|
||||
cursor_processes = []
|
||||
|
||||
# 收集所有 Cursor 进程
|
||||
# Collect all Cursor processes
|
||||
for proc in psutil.process_iter(['pid', 'name']):
|
||||
try:
|
||||
if proc.info['name'].lower() in ['cursor.exe', 'cursor']:
|
||||
@@ -36,20 +36,20 @@ class CursorQuitter:
|
||||
continue
|
||||
|
||||
if not cursor_processes:
|
||||
print(f"{Fore.GREEN}{EMOJI['INFO']} No Running Cursor Process | 未发现运行中的 Cursor 进程{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('quit_cursor.no_process')}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
# 温和地请求进程终止
|
||||
# Gently request processes to terminate
|
||||
for proc in cursor_processes:
|
||||
try:
|
||||
if proc.is_running():
|
||||
print(f"{Fore.YELLOW}{EMOJI['PROCESS']} Terminating Process | 正在终止进程 {proc.pid}...{Style.RESET_ALL}")
|
||||
proc.terminate() # 发送终止信号
|
||||
print(f"{Fore.YELLOW}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.terminating', pid=proc.pid)}...{Style.RESET_ALL}")
|
||||
proc.terminate()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
|
||||
# 等待进程自然终止
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for Process to Exit | 等待进程退出...{Style.RESET_ALL}")
|
||||
# Wait for processes to terminate naturally
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('quit_cursor.waiting')}...{Style.RESET_ALL}")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < self.timeout:
|
||||
still_running = []
|
||||
@@ -61,28 +61,29 @@ class CursorQuitter:
|
||||
continue
|
||||
|
||||
if not still_running:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} All Cursor Processes Closed | 所有 Cursor 进程已正常关闭{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('quit_cursor.success')}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
# 等待一小段时间再检查
|
||||
time.sleep(0.5)
|
||||
|
||||
# 如果超时后仍有进程在运行
|
||||
# If processes are still running after timeout
|
||||
if still_running:
|
||||
process_list = ", ".join([str(p.pid) for p in still_running])
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Process Timeout | 以下进程未能在规定时间内关闭: {process_list}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.timeout', pids=process_list)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error Occurred | 关闭 Cursor 进程时发生错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def quit_cursor(timeout=5):
|
||||
"""便捷函数,用于直接调用退出功能"""
|
||||
quitter = CursorQuitter(timeout)
|
||||
def quit_cursor(translator=None, timeout=5):
|
||||
"""Convenient function for directly calling the quit function"""
|
||||
quitter = CursorQuitter(timeout, translator)
|
||||
return quitter.quit_cursor()
|
||||
|
||||
if __name__ == "__main__":
|
||||
quit_cursor()
|
||||
# If run directly, use the default translator
|
||||
from main import translator as main_translator
|
||||
quit_cursor(main_translator)
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Recaptcha Blocker",
|
||||
"version": "1.0",
|
||||
"description": "Blocks reCAPTCHA from loading",
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://yopmail.com/zh/wm*"],
|
||||
"js": ["script.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"webNavigation",
|
||||
"activeTab"
|
||||
],
|
||||
"host_permissions": [
|
||||
"*://yopmail.com/*",
|
||||
"*://*.google.com/*"
|
||||
]
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// Debug logging function
|
||||
function log(message) {
|
||||
console.log(`[reCAPTCHA Bypass] ${new Date().toISOString()}: ${message}`);
|
||||
}
|
||||
|
||||
// Function to get element by selector
|
||||
function qSelector(selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
// Constants
|
||||
const MAX_ATTEMPTS = 1;
|
||||
const SELECTORS = {
|
||||
CHECK_BOX: ".recaptcha-checkbox-border",
|
||||
AUDIO_BUTTON: "#recaptcha-audio-button",
|
||||
PLAY_BUTTON: ".rc-audiochallenge-play-button .rc-button-default",
|
||||
AUDIO_SOURCE: "#audio-source",
|
||||
IMAGE_SELECT: "#rc-imageselect",
|
||||
RESPONSE_FIELD: ".rc-audiochallenge-response-field",
|
||||
AUDIO_ERROR_MESSAGE: ".rc-audiochallenge-error-message",
|
||||
AUDIO_RESPONSE: "#audio-response",
|
||||
RELOAD_BUTTON: "#recaptcha-reload-button",
|
||||
RECAPTCHA_STATUS: "#recaptcha-accessible-status",
|
||||
DOSCAPTCHA: ".rc-doscaptcha-body",
|
||||
VERIFY_BUTTON: "#recaptcha-verify-button"
|
||||
};
|
||||
|
||||
// Function to check if element is hidden
|
||||
function isHidden(el) {
|
||||
return (el.offsetParent === null);
|
||||
}
|
||||
|
||||
// Function to handle the bypass process
|
||||
async function bypassRecaptcha() {
|
||||
try {
|
||||
log('Starting bypass process...');
|
||||
log('Waiting 3 seconds before starting...');
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
let solved = false;
|
||||
let checkBoxClicked = false;
|
||||
let requestCount = 0;
|
||||
|
||||
const recaptchaInitialStatus = qSelector(SELECTORS.RECAPTCHA_STATUS) ?
|
||||
qSelector(SELECTORS.RECAPTCHA_STATUS).innerText : "";
|
||||
|
||||
log('Initial reCAPTCHA status: ' + recaptchaInitialStatus);
|
||||
|
||||
// Main bypass logic
|
||||
try {
|
||||
if (!checkBoxClicked && qSelector(SELECTORS.CHECK_BOX) &&
|
||||
!isHidden(qSelector(SELECTORS.CHECK_BOX))) {
|
||||
log('Clicking checkbox...');
|
||||
qSelector(SELECTORS.CHECK_BOX).click();
|
||||
checkBoxClicked = true;
|
||||
}
|
||||
|
||||
// Check if the captcha is solved
|
||||
if (qSelector(SELECTORS.RECAPTCHA_STATUS) &&
|
||||
(qSelector(SELECTORS.RECAPTCHA_STATUS).innerText != recaptchaInitialStatus)) {
|
||||
solved = true;
|
||||
log('SOLVED!');
|
||||
}
|
||||
|
||||
if (requestCount > MAX_ATTEMPTS) {
|
||||
log('Attempted Max Retries. Stopping the solver');
|
||||
solved = true;
|
||||
}
|
||||
|
||||
// Stop solving when Automated queries message is shown
|
||||
if (qSelector(SELECTORS.DOSCAPTCHA) &&
|
||||
qSelector(SELECTORS.DOSCAPTCHA).innerText.length > 0) {
|
||||
log('Automated Queries Detected');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
log(`Error in main bypass logic: ${err.message}`);
|
||||
}
|
||||
|
||||
log('Bypass process completed');
|
||||
|
||||
// If not solved, retry after delay
|
||||
if (!solved && requestCount < MAX_ATTEMPTS) {
|
||||
requestCount++;
|
||||
log(`Retrying... Attempt ${requestCount} of ${MAX_ATTEMPTS}`);
|
||||
setTimeout(bypassRecaptcha, 2000);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(`Bypass failed: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a MutationObserver to watch for reCAPTCHA elements
|
||||
log('Setting up MutationObserver...');
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1 && // Element node
|
||||
((node.tagName === 'SCRIPT' && node.src && node.src.includes('recaptcha')) ||
|
||||
(node.tagName === 'IFRAME' && node.src && node.src.includes('recaptcha')))) {
|
||||
log(`Detected new reCAPTCHA element: ${node.tagName} - ${node.src}`);
|
||||
bypassRecaptcha();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Start observing
|
||||
observer.observe(document, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
log('MutationObserver started');
|
||||
|
||||
// Run on page load
|
||||
if (document.readyState === 'loading') {
|
||||
log('Document still loading, waiting for DOMContentLoaded');
|
||||
document.addEventListener('DOMContentLoaded', bypassRecaptcha);
|
||||
} else {
|
||||
log('Document already loaded, starting bypass process');
|
||||
bypassRecaptcha();
|
||||
}
|
||||
@@ -5,13 +5,16 @@ import uuid
|
||||
import hashlib
|
||||
import shutil
|
||||
import sqlite3
|
||||
from logo import print_logo
|
||||
import platform
|
||||
import re
|
||||
import tempfile
|
||||
from colorama import Fore, Style, init
|
||||
from typing import Tuple
|
||||
|
||||
# 初始化colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# 定义emoji和颜色常量
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
"FILE": "📄",
|
||||
"BACKUP": "💾",
|
||||
@@ -21,14 +24,305 @@ EMOJI = {
|
||||
"RESET": "🔄",
|
||||
}
|
||||
|
||||
class MachineIDResetter:
|
||||
def __init__(self):
|
||||
def get_cursor_paths(translator=None) -> Tuple[str, str]:
|
||||
""" Get Cursor related paths"""
|
||||
system = platform.system()
|
||||
|
||||
# 判断操作系统
|
||||
paths_map = {
|
||||
"Darwin": {
|
||||
"base": "/Applications/Cursor.app/Contents/Resources/app",
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
"Windows": {
|
||||
"base": os.path.join(
|
||||
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"
|
||||
),
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
"Linux": {
|
||||
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
}
|
||||
|
||||
if system not in paths_map:
|
||||
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
|
||||
|
||||
if system == "Linux":
|
||||
for base in paths_map["Linux"]["bases"]:
|
||||
pkg_path = os.path.join(base, paths_map["Linux"]["package"])
|
||||
if os.path.exists(pkg_path):
|
||||
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"]))
|
||||
raise OSError(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"]),
|
||||
)
|
||||
|
||||
def get_cursor_machine_id_path(translator=None) -> str:
|
||||
"""
|
||||
Get Cursor machineId file path based on operating system
|
||||
Returns:
|
||||
str: Path to machineId file
|
||||
"""
|
||||
if sys.platform == "win32": # Windows
|
||||
return os.path.join(os.getenv("APPDATA"), "Cursor", "machineId")
|
||||
elif sys.platform == "linux": # Linux
|
||||
return os.path.expanduser("~/.config/Cursor/machineId")
|
||||
elif sys.platform == "darwin": # macOS
|
||||
return os.path.expanduser("~/Library/Application Support/Cursor/machineId")
|
||||
else:
|
||||
raise OSError(f"Unsupported operating system: {sys.platform}")
|
||||
|
||||
def get_workbench_cursor_path(translator=None) -> str:
|
||||
"""Get Cursor workbench.desktop.main.js path"""
|
||||
system = platform.system()
|
||||
|
||||
paths_map = {
|
||||
"Darwin": { # macOS
|
||||
"base": "/Applications/Cursor.app/Contents/Resources/app",
|
||||
"main": "out/vs/workbench/workbench.desktop.main.js"
|
||||
},
|
||||
"Windows": {
|
||||
"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"],
|
||||
"main": "out/vs/workbench/workbench.desktop.main.js"
|
||||
}
|
||||
}
|
||||
|
||||
if system not in paths_map:
|
||||
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
|
||||
|
||||
if system == "Linux":
|
||||
for base in paths_map["Linux"]["bases"]:
|
||||
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"]
|
||||
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"""
|
||||
version_pattern = r"^\d+\.\d+\.\d+$"
|
||||
try:
|
||||
if not re.match(version_pattern, version):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def parse_version(ver: str) -> Tuple[int, ...]:
|
||||
return tuple(map(int, ver.split(".")))
|
||||
|
||||
current = parse_version(version)
|
||||
|
||||
if min_version and current < parse_version(min_version):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
if max_version and current > parse_version(max_version):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
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)
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{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:
|
||||
"""Modify main.js file"""
|
||||
try:
|
||||
original_stat = os.stat(main_path)
|
||||
original_mode = original_stat.st_mode
|
||||
original_uid = original_stat.st_uid
|
||||
original_gid = original_stat.st_gid
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
|
||||
with open(main_path, "r", encoding="utf-8") as main_file:
|
||||
content = main_file.read()
|
||||
|
||||
patterns = {
|
||||
r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}",
|
||||
r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}",
|
||||
}
|
||||
|
||||
for pattern, replacement in patterns.items():
|
||||
content = re.sub(pattern, replacement, content)
|
||||
|
||||
tmp_file.write(content)
|
||||
tmp_path = tmp_file.name
|
||||
|
||||
shutil.copy2(main_path, main_path + ".old")
|
||||
shutil.move(tmp_path, main_path)
|
||||
|
||||
os.chmod(main_path, original_mode)
|
||||
if os.name != "nt":
|
||||
os.chown(main_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():
|
||||
os.unlink(tmp_path)
|
||||
return False
|
||||
|
||||
def patch_cursor_get_machine_id(translator) -> bool:
|
||||
"""Patch Cursor getMachineId function"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.start_patching')}...{Style.RESET_ALL}")
|
||||
|
||||
# Get paths
|
||||
pkg_path, main_path = get_cursor_paths(translator)
|
||||
|
||||
# Check file permissions
|
||||
for file_path in [pkg_path, main_path]:
|
||||
if not os.path.isfile(file_path):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.file_not_found', path=file_path)}{Style.RESET_ALL}")
|
||||
return False
|
||||
if not os.access(file_path, os.W_OK):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_write_permission', path=file_path)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# Get version number
|
||||
try:
|
||||
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||
version = json.load(f)["version"]
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.current_version', version=version)}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.read_version_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# Check version
|
||||
if not version_check(version, min_version="0.45.0", translator=translator):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_not_supported')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.version_check_passed')}{Style.RESET_ALL}")
|
||||
|
||||
# Backup file
|
||||
backup_path = main_path + ".bak"
|
||||
if not os.path.exists(backup_path):
|
||||
shutil.copy2(main_path, backup_path)
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
|
||||
|
||||
# Modify file
|
||||
if not modify_main_js(main_path, translator):
|
||||
return False
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.patch_completed')}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.patch_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
class MachineIDResetter:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
|
||||
# Check operating system
|
||||
if sys.platform == "win32": # Windows
|
||||
appdata = os.getenv("APPDATA")
|
||||
if appdata is None:
|
||||
raise EnvironmentError("APPDATA Environment Variable Not Set | APPDATA 环境变量未设置")
|
||||
raise EnvironmentError("APPDATA Environment Variable Not Set")
|
||||
self.db_path = os.path.join(
|
||||
appdata, "Cursor", "User", "globalStorage", "storage.json"
|
||||
)
|
||||
@@ -42,48 +336,57 @@ class MachineIDResetter:
|
||||
self.sqlite_path = os.path.abspath(os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||
))
|
||||
elif sys.platform == "linux": # Linux 和其他类Unix系统
|
||||
self.db_path = os.path.abspath(os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/storage.json"
|
||||
elif sys.platform == "linux": # Linux
|
||||
# 获取实际用户的主目录
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
if sudo_user:
|
||||
actual_home = f"/home/{sudo_user}"
|
||||
else:
|
||||
actual_home = os.path.expanduser("~")
|
||||
|
||||
self.db_path = os.path.abspath(os.path.join(
|
||||
actual_home,
|
||||
".config/Cursor/User/globalStorage/storage.json"
|
||||
))
|
||||
self.sqlite_path = os.path.abspath(os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/state.vscdb"
|
||||
self.sqlite_path = os.path.abspath(os.path.join(
|
||||
actual_home,
|
||||
".config/Cursor/User/globalStorage/state.vscdb"
|
||||
))
|
||||
else:
|
||||
raise NotImplementedError(f"Not Supported Os| 不支持的操作系统: {sys.platform}")
|
||||
raise NotImplementedError(f"Not Supported OS: {sys.platform}")
|
||||
|
||||
def generate_new_ids(self):
|
||||
"""生成新的机器ID"""
|
||||
# 生成新的UUID
|
||||
"""Generate new machine ID"""
|
||||
# Generate new UUID
|
||||
dev_device_id = str(uuid.uuid4())
|
||||
|
||||
# 生成新的machineId (64个字符的十六进制)
|
||||
# Generate new machineId (64 characters of hexadecimal)
|
||||
machine_id = hashlib.sha256(os.urandom(32)).hexdigest()
|
||||
|
||||
# 生成新的macMachineId (128个字符的十六进制)
|
||||
# Generate new macMachineId (128 characters of hexadecimal)
|
||||
mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest()
|
||||
|
||||
# 生成新的sqmId
|
||||
# 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,
|
||||
"telemetry.machineId": machine_id,
|
||||
"telemetry.sqmId": sqm_id,
|
||||
"storage.serviceMachineId": dev_device_id, # 添加 storage.serviceMachineId
|
||||
"storage.serviceMachineId": dev_device_id, # Add storage.serviceMachineId
|
||||
}
|
||||
|
||||
def update_sqlite_db(self, new_ids):
|
||||
"""更新 SQLite 数据库中的机器ID"""
|
||||
"""Update machine ID in SQLite database"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Updating SQLite Database | 正在更新 SQLite 数据库...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_sqlite')}...{Style.RESET_ALL}")
|
||||
|
||||
# 创建数据库连接
|
||||
conn = sqlite3.connect(self.sqlite_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 确保表存在
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ItemTable (
|
||||
key TEXT PRIMARY KEY,
|
||||
@@ -91,108 +394,196 @@ class MachineIDResetter:
|
||||
)
|
||||
""")
|
||||
|
||||
# 准备更新数据
|
||||
updates = [
|
||||
(key, value) for key, value in new_ids.items()
|
||||
]
|
||||
|
||||
# 使用参数化查询来避免 SQL 注入和处理长文本
|
||||
for key, value in updates:
|
||||
cursor.execute("""
|
||||
INSERT OR REPLACE INTO ItemTable (key, value)
|
||||
VALUES (?, ?)
|
||||
""", (key, value))
|
||||
print(f"{EMOJI['INFO']} {Fore.CYAN}Updating Key-Value Pair | 更新键值对: {key}{Style.RESET_ALL}")
|
||||
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}")
|
||||
|
||||
# 提交更改并关闭连接
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} SQLite Database Updated Successfully | 数据库更新成功!{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.sqlite_success')}{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} SQLite Database Update Failed | 数据库更新失败: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.sqlite_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def reset_machine_ids(self):
|
||||
"""重置机器ID并备份原文件"""
|
||||
def update_system_ids(self, new_ids):
|
||||
"""Update system-level IDs"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Checking Config File | 正在检查配置文件...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_system_ids')}...{Style.RESET_ALL}")
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
self._update_windows_machine_guid()
|
||||
elif sys.platform == "darwin":
|
||||
self._update_macos_platform_uuid(new_ids)
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.system_ids_updated')}{Style.RESET_ALL}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _update_windows_machine_guid(self):
|
||||
"""Update Windows MachineGuid"""
|
||||
try:
|
||||
import winreg
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Cryptography",
|
||||
0,
|
||||
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
|
||||
)
|
||||
new_guid = str(uuid.uuid4())
|
||||
winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid)
|
||||
winreg.CloseKey(key)
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}")
|
||||
except PermissionError:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
|
||||
raise
|
||||
except Exception as 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):
|
||||
"""Update macOS Platform UUID"""
|
||||
try:
|
||||
uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist"
|
||||
if os.path.exists(uuid_file):
|
||||
# Use sudo to execute plutil command
|
||||
cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"'
|
||||
result = os.system(cmd)
|
||||
if result == 0:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}")
|
||||
else:
|
||||
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"{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):
|
||||
"""Reset machine ID and backup original file"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.checking')}...{Style.RESET_ALL}")
|
||||
|
||||
# 检查JSON文件是否存在
|
||||
if not os.path.exists(self.db_path):
|
||||
print(
|
||||
f"{Fore.RED}{EMOJI['ERROR']} Config File Not Found | 配置文件不存在: {self.db_path}{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.not_found')}: {self.db_path}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# 检查文件权限
|
||||
if not os.access(self.db_path, os.R_OK | os.W_OK):
|
||||
print(
|
||||
f"{Fore.RED}{EMOJI['ERROR']} Cannot Read or Write Config File, Please Check File Permissions | 无法读写配置文件,请检查文件权限!{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.no_permission')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# 读取现有配置
|
||||
print(f"{Fore.CYAN}{EMOJI['FILE']} Reading Current Config | 读取当前配置...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.reading')}...{Style.RESET_ALL}")
|
||||
with open(self.db_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
# 只在没有备份文件时创建备份
|
||||
backup_path = self.db_path + ".bak"
|
||||
if not os.path.exists(backup_path):
|
||||
print(
|
||||
f"{Fore.YELLOW}{EMOJI['BACKUP']} Creating Config Backup | 创建配置备份: {backup_path}{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.YELLOW}{EMOJI['BACKUP']} {self.translator.get('reset.creating_backup')}: {backup_path}{Style.RESET_ALL}")
|
||||
shutil.copy2(self.db_path, backup_path)
|
||||
else:
|
||||
print(
|
||||
f"{Fore.YELLOW}{EMOJI['INFO']} Backup File Already Exists, Skipping Backup Step | 已存在备份文件,跳过备份步骤{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_exists')}{Style.RESET_ALL}")
|
||||
|
||||
# 生成新的ID
|
||||
print(f"{Fore.CYAN}{EMOJI['RESET']} Generating New Machine ID | 生成新的机器标识...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['RESET']} {self.translator.get('reset.generating')}...{Style.RESET_ALL}")
|
||||
new_ids = self.generate_new_ids()
|
||||
|
||||
# 更新配置
|
||||
# Update configuration file
|
||||
config.update(new_ids)
|
||||
|
||||
# 保存新配置到 JSON
|
||||
print(f"{Fore.CYAN}{EMOJI['FILE']} Saving New Config to JSON | 保存新配置到 JSON...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.saving_json')}...{Style.RESET_ALL}")
|
||||
with open(self.db_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
# 更新 SQLite 数据库
|
||||
# Update SQLite database
|
||||
self.update_sqlite_db(new_ids)
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Machine ID Reset Successfully | 机器标识重置成功!{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.CYAN}New Machine ID | 新的机器标识:{Style.RESET_ALL}")
|
||||
# Update system IDs
|
||||
self.update_system_ids(new_ids)
|
||||
|
||||
# Modify workbench.desktop.main.js
|
||||
workbench_path = get_workbench_cursor_path(self.translator)
|
||||
modify_workbench_js(workbench_path, self.translator)
|
||||
|
||||
# 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}")
|
||||
patch_cursor_get_machine_id(self.translator)
|
||||
else:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}")
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.success')}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.CYAN}{self.translator.get('reset.new_id')}:{Style.RESET_ALL}")
|
||||
for key, value in new_ids.items():
|
||||
print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}")
|
||||
|
||||
return True
|
||||
|
||||
except PermissionError as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Permission Error | 权限错误: {str(e)}{Style.RESET_ALL}")
|
||||
print(
|
||||
f"{Fore.YELLOW}{EMOJI['INFO']} Please Try Running This Program as Administrator | 请尝试以管理员身份运行此程序{Style.RESET_ALL}"
|
||||
)
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_error', error=str(e))}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Reset Process 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
|
||||
|
||||
def run():
|
||||
"""Main function to be called from main.py"""
|
||||
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}")
|
||||
print(f"{Fore.CYAN}{EMOJI['RESET']} Cursor Machine ID Reset Tool | Cursor 机器标识重置工具{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
resetter = MachineIDResetter()
|
||||
resetter = MachineIDResetter(translator) # Correctly pass translator
|
||||
resetter.reset_machine_ids()
|
||||
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
input(f"{EMOJI['INFO']} Press Enter to Exit | 按回车键退出...")
|
||||
input(f"{EMOJI['INFO']} {translator.get('reset.press_enter')}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
from main import translator as main_translator
|
||||
run(main_translator)
|
||||
@@ -1,9 +1,4 @@
|
||||
# 檢查是否是通過權限提升啟動的
|
||||
param(
|
||||
[switch]$Elevated
|
||||
)
|
||||
|
||||
# 設置顏色主題
|
||||
# set color theme
|
||||
$Theme = @{
|
||||
Primary = 'Cyan'
|
||||
Success = 'Green'
|
||||
@@ -22,20 +17,7 @@ $Logo = @"
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
|
||||
"@
|
||||
|
||||
# 進度條函數
|
||||
function Write-ProgressBar {
|
||||
param (
|
||||
[int]$Percent,
|
||||
[string]$Activity
|
||||
)
|
||||
$width = $Host.UI.RawUI.WindowSize.Width - 20
|
||||
$completed = [math]::Floor($width * ($Percent / 100))
|
||||
$remaining = $width - $completed
|
||||
$progressBar = "[" + ("█" * $completed) + ("-" * $remaining) + "]"
|
||||
Write-Host "`r$Activity $progressBar $Percent%" -NoNewline
|
||||
}
|
||||
|
||||
# 美化輸出函數
|
||||
# Beautiful Output Function
|
||||
function Write-Styled {
|
||||
param (
|
||||
[string]$Message,
|
||||
@@ -43,14 +25,14 @@ function Write-Styled {
|
||||
[string]$Prefix = "",
|
||||
[switch]$NoNewline
|
||||
)
|
||||
$emoji = switch ($Color) {
|
||||
$Theme.Success { "✅" }
|
||||
$Theme.Error { "❌" }
|
||||
$Theme.Warning { "⚠️" }
|
||||
default { "ℹ️" }
|
||||
$symbol = switch ($Color) {
|
||||
$Theme.Success { "[OK]" }
|
||||
$Theme.Error { "[X]" }
|
||||
$Theme.Warning { "[!]" }
|
||||
default { "[*]" }
|
||||
}
|
||||
|
||||
$output = if ($Prefix) { "$emoji $Prefix :: $Message" } else { "$emoji $Message" }
|
||||
$output = if ($Prefix) { "$symbol $Prefix :: $Message" } else { "$symbol $Message" }
|
||||
if ($NoNewline) {
|
||||
Write-Host $output -ForegroundColor $Color -NoNewline
|
||||
} else {
|
||||
@@ -58,62 +40,7 @@ function Write-Styled {
|
||||
}
|
||||
}
|
||||
|
||||
# 檢查管理員權限
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
|
||||
if (-NOT $isAdmin) {
|
||||
Write-Styled "需要管理員權限來安裝" -Color $Theme.Warning -Prefix "權限"
|
||||
Write-Styled "正在請求管理員權限..." -Color $Theme.Primary -Prefix "提升"
|
||||
|
||||
# 顯示操作選項
|
||||
Write-Host "`n選擇操作:" -ForegroundColor $Theme.Primary
|
||||
Write-Host "1. 請求管理員權限" -ForegroundColor $Theme.Info
|
||||
Write-Host "2. 退出程序" -ForegroundColor $Theme.Info
|
||||
|
||||
$choice = Read-Host "`n請輸入選項 (1-2)"
|
||||
|
||||
if ($choice -ne "1") {
|
||||
Write-Styled "安裝已取消" -Color $Theme.Warning -Prefix "取消"
|
||||
Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
||||
exit
|
||||
}
|
||||
|
||||
$pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) {
|
||||
(Get-Command "pwsh").Source
|
||||
} elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") {
|
||||
"$env:ProgramFiles\PowerShell\7\pwsh.exe"
|
||||
} else {
|
||||
"powershell.exe"
|
||||
}
|
||||
|
||||
try {
|
||||
$arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`" -Elevated -WindowStyle Normal"
|
||||
Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $arguments
|
||||
Write-Host "`n請在新開啟的管理員權限視窗中繼續操作..." -ForegroundColor $Theme.Primary
|
||||
|
||||
# 等待用戶確認
|
||||
Write-Host "`n按任意鍵退出此窗口..." -ForegroundColor $Theme.Info
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
||||
exit
|
||||
}
|
||||
catch {
|
||||
Write-Styled "無法獲取管理員權限" -Color $Theme.Error -Prefix "錯誤"
|
||||
Write-Styled "請以管理員身份運行 PowerShell 後重試" -Color $Theme.Warning -Prefix "提示"
|
||||
Write-Styled "您可以:" -Color $Theme.Info
|
||||
Write-Host "1. 右鍵點擊 PowerShell,選擇「以系統管理員身分執行」" -ForegroundColor $Theme.Info
|
||||
Write-Host "2. 然後重新運行此安裝程序" -ForegroundColor $Theme.Info
|
||||
Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 如果是提升權限後的窗口,等待一下確保窗口可見
|
||||
if ($Elevated) {
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
# 獲取版本號函數
|
||||
# Get version number function
|
||||
function Get-LatestVersion {
|
||||
try {
|
||||
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest"
|
||||
@@ -122,101 +49,167 @@ function Get-LatestVersion {
|
||||
Assets = $latestRelease.assets
|
||||
}
|
||||
} catch {
|
||||
Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "錯誤"
|
||||
throw "無法獲取最新版本信息"
|
||||
Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error"
|
||||
throw "Cannot get latest version"
|
||||
}
|
||||
}
|
||||
|
||||
# 顯示 Logo
|
||||
# Show Logo
|
||||
Write-Host $Logo -ForegroundColor $Theme.Primary
|
||||
$releaseInfo = Get-LatestVersion
|
||||
$version = $releaseInfo.Version
|
||||
Write-Host "Version $version" -ForegroundColor $Theme.Info
|
||||
Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info
|
||||
|
||||
# 設置 TLS 1.2
|
||||
# Set TLS 1.2
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
# 創建臨時目錄
|
||||
$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString())
|
||||
New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null
|
||||
|
||||
# 清理函數
|
||||
function Cleanup {
|
||||
if (Test-Path $TmpDir) {
|
||||
Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# 主安裝函數
|
||||
# Main installation function
|
||||
function Install-CursorFreeVIP {
|
||||
Write-Styled "開始安裝 Cursor Free VIP" -Color $Theme.Primary -Prefix "安裝"
|
||||
|
||||
# 設置安裝目錄
|
||||
$InstallDir = "$env:ProgramFiles\CursorFreeVIP"
|
||||
if (!(Test-Path $InstallDir)) {
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
}
|
||||
Write-Styled "Start downloading Cursor Free VIP" -Color $Theme.Primary -Prefix "Download"
|
||||
|
||||
try {
|
||||
# 獲取最新版本
|
||||
Write-Styled "正在檢查最新版本..." -Color $Theme.Primary -Prefix "更新"
|
||||
# Get latest version
|
||||
Write-Styled "Checking latest version..." -Color $Theme.Primary -Prefix "Update"
|
||||
$releaseInfo = Get-LatestVersion
|
||||
$version = $releaseInfo.Version
|
||||
Write-Styled "找到最新版本: $version" -Color $Theme.Success -Prefix "版本"
|
||||
Write-Styled "Found latest version: $version" -Color $Theme.Success -Prefix "Version"
|
||||
|
||||
# 查找對應的資源
|
||||
# Find corresponding resources
|
||||
$asset = $releaseInfo.Assets | Where-Object { $_.name -eq "CursorFreeVIP_${version}_windows.exe" }
|
||||
if (!$asset) {
|
||||
Write-Styled "找不到檔案: CursorFreeVIP_${version}_windows.exe" -Color $Theme.Error -Prefix "錯誤"
|
||||
Write-Styled "可用的檔案:" -Color $Theme.Warning -Prefix "資訊"
|
||||
Write-Styled "File not found: CursorFreeVIP_${version}_windows.exe" -Color $Theme.Error -Prefix "Error"
|
||||
Write-Styled "Available files:" -Color $Theme.Warning -Prefix "Info"
|
||||
$releaseInfo.Assets | ForEach-Object {
|
||||
Write-Styled "- $($_.name)" -Color $Theme.Info
|
||||
}
|
||||
throw "找不到對應的安裝檔案"
|
||||
throw "Cannot find target file"
|
||||
}
|
||||
|
||||
# 下載
|
||||
Write-Styled "正在下載..." -Color $Theme.Primary -Prefix "下載"
|
||||
# Check if Downloads folder already exists for the corresponding version
|
||||
$DownloadsPath = [Environment]::GetFolderPath("UserProfile") + "\Downloads"
|
||||
$downloadPath = Join-Path $DownloadsPath "CursorFreeVIP_${version}_windows.exe"
|
||||
|
||||
if (Test-Path $downloadPath) {
|
||||
Write-Styled "Found existing installation file" -Color $Theme.Success -Prefix "Found"
|
||||
Write-Styled "Location: $downloadPath" -Color $Theme.Info -Prefix "Location"
|
||||
|
||||
# Check if running with administrator privileges
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if (-not $isAdmin) {
|
||||
Write-Styled "Requesting administrator privileges..." -Color $Theme.Warning -Prefix "Admin"
|
||||
|
||||
# Create new process with administrator privileges
|
||||
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$startInfo.FileName = $downloadPath
|
||||
$startInfo.UseShellExecute = $true
|
||||
$startInfo.Verb = "runas"
|
||||
|
||||
try {
|
||||
[System.Diagnostics.Process]::Start($startInfo)
|
||||
Write-Styled "Program started with admin privileges" -Color $Theme.Success -Prefix "Launch"
|
||||
return
|
||||
}
|
||||
catch {
|
||||
Write-Styled "Failed to start with admin privileges. Starting normally..." -Color $Theme.Warning -Prefix "Warning"
|
||||
Start-Process $downloadPath
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# If already running with administrator privileges, start directly
|
||||
Start-Process $downloadPath
|
||||
return
|
||||
}
|
||||
|
||||
Write-Styled "No existing installation file found, starting download..." -Color $Theme.Primary -Prefix "Download"
|
||||
|
||||
# Create WebClient and add progress event
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.Headers.Add("User-Agent", "PowerShell Script")
|
||||
|
||||
$downloadPath = Join-Path $TmpDir "CursorFreeVIP.exe"
|
||||
$webClient.DownloadFile($asset.browser_download_url, $downloadPath)
|
||||
|
||||
# 安裝
|
||||
Write-Styled "正在安裝到系統..." -Color $Theme.Primary -Prefix "安裝"
|
||||
Copy-Item -Path $downloadPath -Destination "$InstallDir\CursorFreeVIP.exe" -Force
|
||||
|
||||
# 添加到 PATH
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
if ($currentPath -notlike "*$InstallDir*") {
|
||||
[Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine")
|
||||
|
||||
# Define progress variables
|
||||
$Global:downloadedBytes = 0
|
||||
$Global:totalBytes = 0
|
||||
$Global:lastProgress = 0
|
||||
$Global:lastBytes = 0
|
||||
$Global:lastTime = Get-Date
|
||||
|
||||
# Download progress event
|
||||
$eventId = [guid]::NewGuid()
|
||||
Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action {
|
||||
$Global:downloadedBytes = $EventArgs.BytesReceived
|
||||
$Global:totalBytes = $EventArgs.TotalBytesToReceive
|
||||
$progress = [math]::Round(($Global:downloadedBytes / $Global:totalBytes) * 100, 1)
|
||||
|
||||
# Only update display when progress changes by more than 1%
|
||||
if ($progress -gt $Global:lastProgress + 1) {
|
||||
$Global:lastProgress = $progress
|
||||
$downloadedMB = [math]::Round($Global:downloadedBytes / 1MB, 2)
|
||||
$totalMB = [math]::Round($Global:totalBytes / 1MB, 2)
|
||||
|
||||
# Calculate download speed
|
||||
$currentTime = Get-Date
|
||||
$timeSpan = ($currentTime - $Global:lastTime).TotalSeconds
|
||||
if ($timeSpan -gt 0) {
|
||||
$bytesChange = $Global:downloadedBytes - $Global:lastBytes
|
||||
$speed = $bytesChange / $timeSpan
|
||||
|
||||
# Choose appropriate unit based on speed
|
||||
$speedDisplay = if ($speed -gt 1MB) {
|
||||
"$([math]::Round($speed / 1MB, 2)) MB/s"
|
||||
} elseif ($speed -gt 1KB) {
|
||||
"$([math]::Round($speed / 1KB, 2)) KB/s"
|
||||
} else {
|
||||
"$([math]::Round($speed, 2)) B/s"
|
||||
}
|
||||
|
||||
Write-Host "`rDownloading: $downloadedMB MB / $totalMB MB ($progress%) - $speedDisplay" -NoNewline -ForegroundColor Cyan
|
||||
|
||||
# Update last data
|
||||
$Global:lastBytes = $Global:downloadedBytes
|
||||
$Global:lastTime = $currentTime
|
||||
}
|
||||
}
|
||||
} | Out-Null
|
||||
|
||||
# Download completed event
|
||||
Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action {
|
||||
Write-Host "`r" -NoNewline
|
||||
Write-Styled "Download completed!" -Color $Theme.Success -Prefix "Complete"
|
||||
Unregister-Event -SourceIdentifier $eventId
|
||||
} | Out-Null
|
||||
|
||||
# Start download
|
||||
$webClient.DownloadFileAsync([Uri]$asset.browser_download_url, $downloadPath)
|
||||
|
||||
# Wait for download to complete
|
||||
while ($webClient.IsBusy) {
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
|
||||
Write-Styled "安裝完成!" -Color $Theme.Success -Prefix "完成"
|
||||
Write-Styled "正在啟動程序..." -Color $Theme.Primary -Prefix "啟動"
|
||||
|
||||
# 運行程序
|
||||
Start-Process "$InstallDir\CursorFreeVIP.exe"
|
||||
Write-Styled "File location: $downloadPath" -Color $Theme.Info -Prefix "Location"
|
||||
Write-Styled "Starting program..." -Color $Theme.Primary -Prefix "Launch"
|
||||
|
||||
# Run program
|
||||
Start-Process $downloadPath
|
||||
}
|
||||
catch {
|
||||
Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "錯誤"
|
||||
Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error"
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
# 執行安裝
|
||||
# Execute installation
|
||||
try {
|
||||
Install-CursorFreeVIP
|
||||
}
|
||||
catch {
|
||||
Write-Styled "安裝失敗" -Color $Theme.Error -Prefix "錯誤"
|
||||
Write-Styled "Download failed" -Color $Theme.Error -Prefix "Error"
|
||||
Write-Styled $_.Exception.Message -Color $Theme.Error
|
||||
}
|
||||
finally {
|
||||
Cleanup
|
||||
Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
|
||||
Write-Host "`nPress any key to exit..." -ForegroundColor $Theme.Info
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 顏色定義
|
||||
# Color definitions
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
@@ -22,7 +22,7 @@ EOF
|
||||
echo -e "${NC}"
|
||||
}
|
||||
|
||||
# 获取下载文件夹路径
|
||||
# Get download folder path
|
||||
get_downloads_dir() {
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
echo "$HOME/Downloads"
|
||||
@@ -36,59 +36,139 @@ get_downloads_dir() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 獲取最新版本
|
||||
# Get latest version
|
||||
get_latest_version() {
|
||||
echo -e "${CYAN}ℹ️ 正在檢查最新版本...${NC}"
|
||||
echo -e "${CYAN}ℹ️ Checking latest version...${NC}"
|
||||
local latest_release
|
||||
latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ 無法獲取最新版本信息${NC}"
|
||||
echo -e "${RED}❌ Cannot get latest version information${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$(echo "$latest_release" | grep -o '"tag_name": ".*"' | cut -d'"' -f4 | tr -d 'v')
|
||||
echo -e "${GREEN}✅ 找到最新版本: ${VERSION}${NC}"
|
||||
echo -e "${GREEN}✅ Found latest version: ${VERSION}${NC}"
|
||||
}
|
||||
|
||||
# 檢測系統類型
|
||||
# Detect system type and architecture
|
||||
detect_os() {
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
OS="mac"
|
||||
else
|
||||
# Detect macOS architecture
|
||||
ARCH=$(uname -m)
|
||||
if [[ "$ARCH" == "arm64" ]]; then
|
||||
OS="mac_arm64"
|
||||
echo -e "${CYAN}ℹ️ Detected macOS ARM64 architecture${NC}"
|
||||
else
|
||||
OS="mac_intel"
|
||||
echo -e "${CYAN}ℹ️ Detected macOS Intel architecture${NC}"
|
||||
fi
|
||||
elif [[ "$(uname)" == "Linux" ]]; then
|
||||
OS="linux"
|
||||
echo -e "${CYAN}ℹ️ Detected Linux system${NC}"
|
||||
else
|
||||
# Assume Windows
|
||||
OS="windows"
|
||||
echo -e "${CYAN}ℹ️ Detected Windows system${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 下載並安裝
|
||||
# Install and download
|
||||
install_cursor_free_vip() {
|
||||
local downloads_dir=$(get_downloads_dir)
|
||||
local binary_name="CursorFreeVIP_${VERSION}_${OS}"
|
||||
local binary_path="${downloads_dir}/cursor-free-vip"
|
||||
local binary_path="${downloads_dir}/${binary_name}"
|
||||
local download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
|
||||
|
||||
echo -e "${CYAN}ℹ️ 正在下載到 ${downloads_dir}...${NC}"
|
||||
# Check if file already exists
|
||||
if [ -f "${binary_path}" ]; then
|
||||
echo -e "${GREEN}✅ Found existing installation file${NC}"
|
||||
echo -e "${CYAN}ℹ️ Location: ${binary_path}${NC}"
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Requesting administrator privileges...${NC}"
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
echo -e "${CYAN}ℹ️ Starting program with sudo...${NC}"
|
||||
sudo chmod +x "${binary_path}"
|
||||
sudo "${binary_path}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ sudo not found, trying to run normally...${NC}"
|
||||
chmod +x "${binary_path}"
|
||||
"${binary_path}"
|
||||
fi
|
||||
else
|
||||
# Already running as root
|
||||
echo -e "${CYAN}ℹ️ Already running as root, starting program...${NC}"
|
||||
chmod +x "${binary_path}"
|
||||
"${binary_path}"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}ℹ️ No existing installation file found, starting download...${NC}"
|
||||
echo -e "${CYAN}ℹ️ Downloading to ${downloads_dir}...${NC}"
|
||||
echo -e "${CYAN}ℹ️ Download link: ${download_url}${NC}"
|
||||
|
||||
# Check if file exists
|
||||
if curl --output /dev/null --silent --head --fail "$download_url"; then
|
||||
echo -e "${GREEN}✅ File exists, starting download...${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Download link does not exist: ${download_url}${NC}"
|
||||
echo -e "${YELLOW}⚠️ Trying without architecture...${NC}"
|
||||
|
||||
# Try without architecture
|
||||
if [[ "$OS" == "mac_arm64" || "$OS" == "mac_intel" ]]; then
|
||||
OS="mac"
|
||||
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
|
||||
if ! curl -L -o "${binary_path}" "$download_url"; then
|
||||
echo -e "${RED}❌ 下載失敗${NC}"
|
||||
echo -e "${RED}❌ Download failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}ℹ️ 正在設置執行權限...${NC}"
|
||||
# Check downloaded file size
|
||||
local file_size=$(stat -f%z "${binary_path}" 2>/dev/null || stat -c%s "${binary_path}" 2>/dev/null)
|
||||
echo -e "${CYAN}ℹ️ Downloaded file size: ${file_size} bytes${NC}"
|
||||
|
||||
# If file is too small, it might be an error message
|
||||
if [ "$file_size" -lt 1000 ]; then
|
||||
echo -e "${YELLOW}⚠️ Warning: Downloaded file is too small, possibly not a valid executable file${NC}"
|
||||
echo -e "${YELLOW}⚠️ File content:${NC}"
|
||||
cat "${binary_path}"
|
||||
echo ""
|
||||
echo -e "${RED}❌ Download failed, please check version and operating system${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}ℹ️ Setting executable permissions...${NC}"
|
||||
chmod +x "${binary_path}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ 安裝完成!${NC}"
|
||||
echo -e "${CYAN}ℹ️ 程序已下載到: ${binary_path}${NC}"
|
||||
echo -e "${CYAN}ℹ️ 正在啟動程序...${NC}"
|
||||
echo -e "${GREEN}✅ Installation completed!${NC}"
|
||||
echo -e "${CYAN}ℹ️ Program downloaded to: ${binary_path}${NC}"
|
||||
echo -e "${CYAN}ℹ️ Starting program...${NC}"
|
||||
|
||||
# 直接运行程序
|
||||
# Run program directly
|
||||
"${binary_path}"
|
||||
else
|
||||
echo -e "${RED}❌ 安裝失敗${NC}"
|
||||
echo -e "${RED}❌ Installation failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主程序
|
||||
# Main program
|
||||
main() {
|
||||
print_logo
|
||||
get_latest_version
|
||||
@@ -96,5 +176,5 @@ main() {
|
||||
install_cursor_free_vip
|
||||
}
|
||||
|
||||
# 運行主程序
|
||||
main
|
||||
# Run main program
|
||||
main
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>uBlock — Your filters</title>
|
||||
|
||||
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="lib/codemirror/addon/hint/show-hint.css">
|
||||
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||
|
||||
<link rel="stylesheet" href="css/themes/default.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" href="css/1p-filters.css">
|
||||
<link rel="stylesheet" href="css/codemirror.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="body">
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="myFiltersPane"></div>
|
||||
<p>
|
||||
<button id="userFiltersApply" class="preferred iconified" type="button" disabled><span class="fa-icon">check</span><span data-i18n="1pApplyChanges">_</span><span class="hover"></span></button>
|
||||
<button id="userFiltersRevert" class="iconified" type="button" disabled><span class="fa-icon">undo</span><span data-i18n="genericRevert">_</span><span class="hover"></span></button>
|
||||
 
|
||||
<button id="importUserFiltersFromFile" class="iconified" type="button"><span class="fa-icon">download-alt</span><span data-i18n="1pImport">_</span><span class="hover"></span></button>
|
||||
<button id="exportUserFiltersToFile" class="iconified" type="button"><span class="fa-icon">upload-alt</span><span data-i18n="1pExport">_</span><span class="hover"></span></button>
|
||||
</p>
|
||||
<p data-i18n="1pTrustWarning"></p>
|
||||
<div class="li"><label><span id="enableMyFilters" class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pEnableMyFiltersLabel"></span></label></div>
|
||||
<div id="trustMyFilters" class="li"><label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pTrustMyFiltersLabel"></span></label></div>
|
||||
</div>
|
||||
<div id="userFilters" class="codeMirrorContainer codeMirrorBreakAll cm-theme-override" spellcheck="false"></div>
|
||||
<div class="hidden">
|
||||
<input id="importFilePicker" type="file" accept="text/plain">
|
||||
</div>
|
||||
|
||||
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||
<script src="lib/codemirror/addon/comment/comment.js"></script>
|
||||
<script src="lib/codemirror/addon/display/panel.js"></script>
|
||||
<script src="lib/codemirror/addon/edit/closebrackets.js"></script>
|
||||
<script src="lib/codemirror/addon/edit/matchbrackets.js"></script>
|
||||
<script src="lib/codemirror/addon/fold/foldcode.js"></script>
|
||||
<script src="lib/codemirror/addon/hint/show-hint.js"></script>
|
||||
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||
<script src="lib/diff/swatinem_diff.js"></script>
|
||||
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
|
||||
|
||||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
|
||||
<script src="js/codemirror/search.js" type="module"></script>
|
||||
<script src="js/codemirror/search-thread.js"></script>
|
||||
|
||||
<script src="js/fa-icons.js" type="module"></script>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/1p-filters.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,117 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<title>uBlock — Filter lists</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/3p-filters.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="tpFiltersPane"></div>
|
||||
<p id="actions">
|
||||
<button id="buttonApply" class="preferred disabled iconified dontshrink" type="button" data-i18n-title="3pApplyChanges"><span class="fa-icon">check</span><span data-i18n="3pApplyChanges">_</span><span class="hover"></span></button>
|
||||
<button id="buttonUpdate" class="preferred disabled iconified" type="button" data-i18n-title="3pUpdateNow"><span class="fa-icon">refresh</span><span data-i18n="3pUpdateNow">_</span><span class="hover"></span></button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<div class="li">
|
||||
<label><span class="input checkbox"><input type="checkbox" id="autoUpdate"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="3pAutoUpdatePrompt1"></span></label>
|
||||
</div>
|
||||
<div class="li">
|
||||
<label><span class="input checkbox"><input type="checkbox" id="suspendUntilListsAreLoaded"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="3pSuspendUntilListsAreLoaded"></span></label>
|
||||
</div>
|
||||
<div class="li">
|
||||
<label><span class="input checkbox"><input type="checkbox" id="parseCosmeticFilters"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span data-i18n="3pParseAllABPHideFiltersPrompt1"></span> <span class="fa-icon info" data-i18n-title="3pParseAllABPHideFiltersInfo">question-circle</span></span></label>
|
||||
</div>
|
||||
<div class="li">
|
||||
<label><span class="input checkbox"><input type="checkbox" id="ignoreGenericCosmeticFilters"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span data-i18n="3pIgnoreGenericCosmeticFilters"></span> <span class="fa-icon info" data-i18n-title="3pIgnoreGenericCosmeticFiltersInfo">question-circle</span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
</div>
|
||||
|
||||
<div id="lists">
|
||||
<div class="rootstats expandable" data-key="*">
|
||||
<span class="fa-icon listExpander">angle-up</span><span id="listsOfBlockedHostsPrompt"></span>
|
||||
</div>
|
||||
<div class="searchfield"><input type="search" spellcheck="false" placeholder="" /><span class="fa-icon">search</span></div>
|
||||
<div class="listEntries"></div>
|
||||
<div class="li listEntry expandable" data-role="import">
|
||||
<span class="detailbar">
|
||||
<label><span class="fa-icon listExpander">angle-up</span><span class="listname" data-i18n="3pImport"></span></label>
|
||||
<a class="fa-icon info towiki" href="https://github.com/gorhill/uBlock/wiki/Filter-lists-from-around-the-web" target="_blank">info-circle</a>
|
||||
</span>
|
||||
<textarea dir="ltr" spellcheck="false" placeholder="3pExternalListsHint"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none;">
|
||||
<div class="listEntries"></div>
|
||||
<div class="li listEntry" data-role="leaf">
|
||||
<span class="detailbar">
|
||||
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span class="listname forinput"></span>
|
||||
</span></label>
|
||||
<span class="iconbar"><!--
|
||||
--><a class="fa-icon content" href="#" type="text/plain" target="_blank" data-i18n-title="3pViewContent">eye-open</a><!--
|
||||
--><a class="fa-icon support" href="#" target="_blank">home</a><!--
|
||||
--><span class="fa-icon remove">trash-o</span><!--
|
||||
--><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
|
||||
--><span class="fa-icon status unsecure" title="http">unlock-alt</span><!--
|
||||
--><span class="fa-icon status obsolete" data-i18n-title="3pExternalListObsolete">exclamation-triangle</span><!--
|
||||
--><span class="fa-icon status cache">clock-o</span><!--
|
||||
--><span class="fa-icon status updating" data-i18n-title="3pUpdating">spinner</span><!--
|
||||
--><span class="fa-icon status failed" data-i18n-title="3pNetworkError">unlink</span>
|
||||
</span>
|
||||
<span class="leafstats"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="li listEntry expandable" data-role="node">
|
||||
<span class="detailbar">
|
||||
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span class="listname forinput"></span></label>
|
||||
<span class="nodestats"></span>
|
||||
<span class="fa-icon listExpander">angle-up</span>
|
||||
<span class="iconbar"><!--
|
||||
--><a class="fa-icon support" href="#" target="_blank">home</a><!--
|
||||
--><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
|
||||
--><span class="fa-icon status obsolete" data-i18n-title="3pExternalListObsolete">exclamation-triangle</span><!--
|
||||
--><span class="fa-icon status cache">clock-o</span><!--
|
||||
--><span class="fa-icon status updating" data-i18n-title="3pUpdating">spinner</span><!--
|
||||
--><span class="fa-icon status failed" data-i18n-title="3pNetworkError">unlink</span>
|
||||
</span>
|
||||
<span class="leafstats"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="li listEntry expandable" data-parent="root" data-role="node">
|
||||
<span class="detailbar">
|
||||
<label><span class="fa-icon listExpander">angle-up</span><span class="listname"></span></label>
|
||||
<span class="nodestats"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
|
||||
|
||||
<script src="js/fa-icons.js" type="module"></script>
|
||||
<script src="js/vapi.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/3p-filters.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/}
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{{project}} Copyright (C) {{year}} {{fullname}}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
{http://www.gnu.org/licenses/}.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
{http://www.gnu.org/philosophy/why-not-lgpl.html}.
|
||||