Compare commits

...

20 Commits

Author SHA1 Message Date
yeongpin
ad98bed98d Refactor: Remove GitHub trial reset feature and add development version check. Update translations for lifetime access and development version messages. 2025-03-16 10:54:43 +08:00
Pin Studios
6f2ec1b373 Merge pull request #250 from yeongpin/revert-248-feature/github-reset-trial
Revert "feat: Add GitHub-based trial reset"
2025-03-16 10:45:52 +08:00
Pin Studios
5574091273 Revert "feat: Add GitHub-based trial reset" 2025-03-16 10:44:40 +08:00
Pin Studios
0226c9e735 Update CHANGELOG.md 2025-03-16 10:18:35 +08:00
Pin Studios
375347a7a5 Update .env 2025-03-16 10:05:57 +08:00
Pin Studios
8301ea74d5 Delete pr_description.md 2025-03-16 10:05:41 +08:00
Pin Studios
96981a2c37 Merge pull request #248 from Nigel1992/feature/github-reset-trial
feat: Add GitHub-based trial reset
2025-03-16 10:04:44 +08:00
Nigel1992
975647765d feat: Add JavaScript trial reset code and update PR description 2025-03-15 23:52:39 +01:00
Nigel1992
808b1ba3dc feat: Add JavaScript trial reset code for automatic account deletion 2025-03-15 23:48:55 +01:00
Nigel1992
9595a8792f docs: Update PR description to include JavaScript trial reset code 2025-03-15 23:45:15 +01:00
Nigel1992
6b5dfab362 feat: Add GitHub-based trial reset with improved organization 2025-03-15 23:39:02 +01:00
Pin Studios
d5fceb2624 Update .env 2025-03-15 23:46:50 +08:00
Pin Studios
45046002df Merge pull request #243 from mALIk-sHAHId/feature/google-github-auth
feat: Add Google and GitHub OAuth Authentication with Lifetime Access…
2025-03-15 23:46:28 +08:00
mALIk-sHAHId
6bb33cec4c feat: Add Google and GitHub OAuth Authentication with Lifetime Access - Add OAuth integrations, update menu system, add multi-language support, and update documentation 2025-03-15 19:39:30 +05:30
Pin Studios
66f6197e6d Update block_domain.txt 2025-03-15 11:10:30 +08:00
Pin Studios
5514e47759 Update block_domain.txt 2025-03-15 10:29:03 +08:00
Pin Studios
3a53d59d52 Merge pull request #227 from distributedStorage/enhance-readme
doc: unify the README & CHANGELOG style
2025-03-14 23:02:31 +08:00
imbajin
c2e5499f19 doc: unify the README & CHANGELOG 2025-03-14 15:29:49 +08:00
Pin Studios
9a223756c5 Merge pull request #226 from distributedStorage/fix-download
fix(script): avoid empty version when call release api
2025-03-14 15:26:39 +08:00
imbajin
9b5912357d fix(script): avoid empty version when call release api
Also enhance the way for judging $?
2025-03-14 15:11:57 +08:00
14 changed files with 1053 additions and 111 deletions

4
.env
View File

@@ -1,2 +1,2 @@
version=1.7.07
VERSION=1.7.07
version=1.7.09
VERSION=1.7.09

View File

@@ -1,31 +1,44 @@
# Change Log
## v1.7.09
1. Add: Development Version Check | 增加開發版本檢查
2. Remove: Github Trial Reset | 移除 Github 試用重置
3. Fixed: Some Issues | 修復一些問題
## v1.7.08
1. Add: Google OAuth Authentication | 增加 Google OAuth 認證
2. Add: GitHub OAuth Authentication | 增加 GitHub OAuth 認證
3. Add: Lifetime Access for OAuth Users | 增加 OAuth 用戶終身訪問權限
4. Add: OAuth Authentication Integration | 增加 OAuth 認證集成
5. Update: Menu System with OAuth Options | 更新菜單系統,添加 OAuth 選項
6. Add: Multi-language Support for OAuth | 增加 OAuth 多語言支持
## v1.7.07
1. Add: Vietnamese Language | 增加越南語言
2. Add: Admin Privilege Management for Windows Executable | 增加Windows可執行文件管理員權限
3. Implement admin privilege detection for Windows platform | 實現Windows平台管理員權限檢測
2. Add: Admin Privilege Management for Windows Executable | 增加 Windows 可執行文件管理員權限
3. Implement admin privilege detection for Windows platform | 實現 Windows 平台管理員權限檢測
4. Add functions to check and request admin rights when running as a frozen executable | 增加檢查和請求管理員權限的功能
5. Enhance startup process with admin privilege verification | 增強啟動過程中的管理員權限驗證
6. Add new admin-related emoji to the EMOJI dictionary | 增加新的管理員相關表情符號到EMOJI字典
7. Provide fallback mechanism for non-Windows platforms (macos and linux ) | 提供非Windows平台macoslinux的回退機制
6. Add new admin-related emoji to the EMOJI dictionary | 增加新的管理員相關表情符號到 EMOJI 字典
7. Provide fallback mechanism for non-Windows platforms (macos and linux ) | 提供非 Windows 平台macoslinux的回退機制
These changes make the application more user-friendly by only requesting admin privileges when necessary (when running as an executable). | 這些改進使應用程序更易於使用,只在必要時(當作為可執行文件運行時)請求管理員權限。
## v1.7.06
1. Add: Update Confirm | 增加更新確認
2. Add: Update Skipped | 增加更新跳過
3. Add: Invalid Choice | 增加無效選擇
4. Fix: Cursor Path | 修復Cursor路徑
4. Fix: Cursor Path | 修復 Cursor 路徑
5. Fix: Path Encoding | 修復路徑編碼
6. Fix: Getting Verification Code | 修復獲取驗證碼
7. Fix: Setting Password | 修復設置密碼
8. Fix: Disable Auto Update | 修復禁用自動更新
9. Add Config.py | 增加Config.py
10. Add utils.py | 增加utils.py
9. Add Config.py | 增加 Config.py
10. Add utils.py | 增加 utils.py
11. Rebuild some logic | 重新構建一些邏輯
## v1.7.05
1. Fix: Cursor Version Check | 修復Cursor版本檢查
1. Fix: Cursor Version Check | 修復 Cursor 版本檢查
2. Fix: Small Problem | 修復一些小問題
@@ -36,10 +49,10 @@ These changes make the application more user-friendly by only requesting admin p
1. Hotfix: Small Problem | 修復一些小問題
## v1.7.02
1. Fix: Cursor Path | 修復Cursor路徑
1. Fix: Cursor Path | 修復 Cursor 路徑
2. Add: Config File | 增加配置文件
3. Remove: Workbench Cursor Path | 移除Workbench Cursor路徑
4. Remove: Cursor Main JS | 移除Cursor main.js
3. Remove: Workbench Cursor Path | 移除 Workbench Cursor 路徑
4. Remove: Cursor Main JS | 移除 Cursor main.js
## v1.7.01
- Refactoring: Extract configuration-related code from the `setup_driver` function to an independent `setup_config` function
@@ -54,10 +67,10 @@ These changes make the application more user-friendly by only requesting admin p
2. Add: Test some Bypass Code | 測試一些繞過代碼
## v1.6.01
1. Fix: Cursor Auth | 修復Cursor Auth
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失敗
3. Fix: Cursor Auth Error | 修復 Cursor Auth 錯誤
4. Fix: Update Curl Faild | 修復更新 Curl 失敗
## v1.5.03
1. HOTFIX: Stuck on starting browser | 修復啟動瀏覽器卡住問題
@@ -80,8 +93,8 @@ These changes make the application more user-friendly by only requesting admin p
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調用以再次顯示菜單
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
@@ -96,7 +109,7 @@ These changes make the application more user-friendly by only requesting admin p
## v1.4.05
1. Fix: macOS Language Detection | 修復macOS語言檢測
1. Fix: macOS Language Detection | 修復 macOS 語言檢測
## v1.4.04
@@ -108,46 +121,46 @@ These changes make the application more user-friendly by only requesting admin p
## v1.4.03
1. Switch to API-based Registration System | 改用API註冊系統替代瀏覽器操作
2. Add Support for Latest Cursor Version | 增加支持最新版本Cursor
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集成
6. Add New API Integration | 新增 API 集成
7. Optimize Performance and Stability | 優化性能和穩定性
## v1.4.01
1. Add Disable Cursor Auto Upgrade | 增加禁用Cursor自動升級
1. Add Disable Cursor Auto Upgrade | 增加禁用 Cursor 自動升級
## v1.3.02
1. Add Buy Me a Coffee | 增加請我喝杯咖啡
2. Add PayPal | 增加PayPal
2. Add PayPal | 增加 PayPal
3. Very Small Fix | 非常小的修復
4. Fix main.py option number | 修復main.py選項數量
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選項
3. Fix Cursor Options | 修復 Cursor 選項
## v1.2.02
1. Add PBlock | 增加PBlock
2. Remove uBlock0.chromium | 移除uBlock0.chromium
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人機驗證問題
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問題
4. Fix Cursor Cloudflare Problem | 修復 Cursor Cloudflare 問題
## v1.1.01
@@ -156,15 +169,15 @@ These changes make the application more user-friendly by only requesting admin p
<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人機驗證問題
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啟動開發者問題
1. Hot Fix Mac Chrome Problem | 修復 Mac Chrome 問題
2. Fix Linux Start Donet Problem | 修復 Linux 啟動開發者問題
## v1.0.9
@@ -173,15 +186,15 @@ These changes make the application more user-friendly by only requesting admin p
<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版本重置機器
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
4. Add Remake main.js | 重做 main.js
## v1.0.8
1. Fix New 0.45 Version Reset Machine | 修復新0.45版本重置機器
1. Fix New 0.45 Version Reset Machine | 修復新 0.45 版本重置機器
2. Fix Locale Language | 修復多語言
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
@@ -202,17 +215,17 @@ These changes make the application more user-friendly by only requesting admin p
## v1.0.6
1. Add Quit Cursor Option | 增加退出Cursor選項
2. Add Recaptcha Path Patch | 增加Recaptcha路徑修復
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瀏覽器控制問題
1. Fix: Mac Browser Control | 修復 Mac 瀏覽器控制問題
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
3. Add Linux Support | 增加Linux支持
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>
@@ -220,7 +233,7 @@ These changes make the application more user-friendly by only requesting admin p
## v1.0.5
1. Remove MachineID | 移除機器碼ID
1. Remove MachineID | 移除機器碼 ID
2. Change to automatic registration account | 全面改為自動註冊賬號
3. Use your own exclusive new account | 使用自己獨享的新賬號
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
@@ -231,16 +244,16 @@ These changes make the application more user-friendly by only requesting admin p
## v1.0.4
1. Fix: Cursor's configuration | 修復Cursor的配置問題
1. Fix: Cursor's configuration | 修復 Cursor 的配置問題
2. Fix Cloud Lame | 修復雲端慢速模式
## v1.0.3
1. Fix: Cursor's configuration | 修復Cursor的配置問題
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支持
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>
@@ -252,21 +265,21 @@ These changes make the application more user-friendly by only requesting admin p
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 問題
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模式
8. Fix: Cursor's configuration | 修復 Cursor 的配置問題
9. Fix cursor-slow mode | 修復 cursor-slow 模式
## v1.0.1
1. Fix: Reset machine ID | 修復了重置機器ID的問題
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的配置的問題
3. Fix: Auto upgrade to "pro" membership | 修復了 自動升級為 pro 會員的問題
4. Fix: Real-time send Token request | 修復了 實時發送 Token 請求的問題
5. Fix: Reset Cursor's configuration | 修復了 重置 Cursor 的配置的問題
@@ -278,7 +291,7 @@ These changes make the application more user-friendly by only requesting admin 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 聯繫作者
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下 MachineID 聯繫作者
<br>
<p align="center">

View File

@@ -1,4 +1,5 @@
# ➤ Cursor Free VIP
<div align="center">
<p align="center">
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200" style="border-radius: 6px;"/>
@@ -12,45 +13,50 @@
[![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a)](https://github.com/yeongpin/cursor-free-vip/releases/latest)
</p>
<h4>Support Latest 0.47.x Version | 支持最新0.47.x版本</h4>
<h4>Support Latest 0.47.x Version | 支持最新 0.47.x 版本</h4>
This is a tool to automatically register , support Windows and macOS systems, complete Auth verification, and reset Cursor's configuration.
This is a tool to automatically register, support Windows and macOS systems, complete Auth verification, and reset
Cursor's configuration.
這是一個自動化工具,自動註冊 ,支持 Windows 和 macOS 系統完成Auth驗證重置Cursor的配置。
這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。
<p align="center">
<img src="./images/new_2025-02-27_10-42-44.png" alt="new" width="400" style="border-radius: 6px;"/><br>
</p>
##### If you dont have google chrome , you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
##### If you don't 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>
## 🔄 Change Log | 更新日志
[Watch Change Log | 查看更新日志](CHANGELOG.md)
## ✨ Features | 功能特點
* Automatically register Cursor membership<br>自動註冊Cursor會員<br>
* 🌟 Google OAuth Authentication with Lifetime Access<br>使用 Google OAuth 認證(終身訪問)<br>
* ⭐ GitHub OAuth Authentication with Lifetime Access<br>使用 GitHub OAuth 認證(終身訪問)<br>
* Automatically register Cursor membership<br>自動註冊 Cursor 會員<br>
* Support Windows and macOS systems<br>支持 Windows 和 macOS 系統<br>
* Complete Auth verification<br>完成Auth驗證<br>
* Complete Auth verification<br>完成 Auth 驗證<br>
* Reset Cursor's configuration<br>重置Cursor的配置<br>
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
## 💻 System Support | 系統支持
|Windows|x64|✅|macOS|Intel|✅|
|:---:|:---:|:---:|:---:|:---:|:---:|
|Windows|x86|✅|macOS|Apple Silicon|✅|
|Linux|x64|✅|Linux|x86|✅|
|Linux|ARM64|✅|Linux|ARM64|✅|
| Windows | x64 | ✅ | macOS | Intel | ✅ |
|:-------:|:-----:|:-:|:-----:|:-------------:|:-:|
| Windows | x86 | ✅ | macOS | Apple Silicon | ✅ |
| Linux | x64 | ✅ | Linux | x86 | ✅ |
| Linux | ARM64 | ✅ | Linux | ARM64 | ✅ |
## 👀 How to use | 如何使用
@@ -58,28 +64,34 @@ This is a tool to automatically register , support Windows and macOS systems, co
<summary><b>⭐ Auto Run Script | 腳本自動化運行</b></summary>
**Linux/macOS**
```bash
curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh -o install.sh && chmod +x install.sh && ./install.sh
```
**Windows**
```powershell
irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex
```
</details>
<details>
<summary><b>⭐ Manual Reset Machine | 手動運行重置機器</b></summary>
**Linux/macOS**
```bash
curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/reset.sh | sudo bash
```
**Windows**
```powershell
irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/reset.ps1 | iex
```
</details>
2. If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
@@ -140,6 +152,7 @@ retry_interval = 8-12
# Max Timeout | 最大超時時間
max_timeout = 160
```
</details>
* Use administrator to run the script <br>請使用管理員身份運行腳本
@@ -150,15 +163,11 @@ max_timeout = 160
* Please comply with the relevant software usage terms when using this tool <br>使用本工具時請遵守相關軟件使用條款
## 🚨 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 |
## 🤩 Contribution | 貢獻
@@ -170,12 +179,12 @@ max_timeout = 160
</a>
<br /><br />
## 📩 Disclaimer | 免責聲明
本工具僅供學習和研究使用,使用本工具所產生的任何後果由使用者自行承擔。 <br>
This tool is only for learning and research purposes, and any consequences arising from the use of this tool are borne by the user.
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 | 請我喝杯咖啡
@@ -202,9 +211,5 @@ This tool is only for learning and research purposes, and any consequences arisi
## 📝 License | 授權
本項目採用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 授權。
本項目採用 [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.

View File

@@ -1,3 +1,5 @@
oakon.com
famamail.com
2925.com
indigobook.com
teihu.com

View File

@@ -0,0 +1,5 @@
from oauth_auth import main as oauth_main
def main(translator=None):
"""Handle GitHub OAuth registration"""
oauth_main('github', translator)

View File

@@ -0,0 +1,5 @@
from oauth_auth import main as oauth_main
def main(translator=None):
"""Handle Google OAuth registration"""
oauth_main('google', translator)

View File

@@ -4,6 +4,8 @@
"exit": "Exit Program",
"reset": "Reset Machine ID",
"register": "Register New Cursor Account",
"register_google": "Register with Google Account",
"register_github": "Register with GitHub Account",
"register_manual": "Register Cursor with Custom Email",
"quit": "Close Cursor Application",
"select_language": "Change Language",
@@ -12,7 +14,8 @@
"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"
"disable_auto_update": "Disable Cursor Auto-Update",
"lifetime_access_enabled": "LIFETIME ACCESS ENABLED"
},
"languages": {
"en": "English",
@@ -276,6 +279,7 @@
"continue_anyway": "Continuing with current version...",
"update_confirm": "Do you want to update to the latest version? (Y/n)",
"update_skipped": "Skipping update.",
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'."
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'.",
"development_version": "Development Version {current} > {latest}"
}
}

View File

@@ -12,7 +12,8 @@
"program_terminated": "Chương trình đã bị người dùng chấm dứt",
"error_occurred": "Đã xảy ra lỗi: {error}. Vui lòng thử lại",
"press_enter": "Nhấn Enter để Thoát",
"disable_auto_update": "Tắt Tự Động Cập Nhật Cursor"
"disable_auto_update": "Tắt Tự Động Cập Nhật Cursor",
"lifetime_access_enabled": "LIFETIME ACCESS ENABLED"
},
"languages": {
"en": "English",
@@ -276,6 +277,7 @@
"continue_anyway": "Tiếp Tục Với Phiên Bản Hiện Tại...",
"update_confirm": "Bạn Có Muốn Cập Nhật Lên Phiên Bản Mới Nhất Không? (Y/n)",
"update_skipped": "Bỏ Qua Cập Nhật.",
"invalid_choice": "Lựa Chọn Không Hợp Lệ. Vui Lòng Nhập 'Y' Hoặc 'n'."
"invalid_choice": "Lựa Chọn Không Hợp Lệ. Vui Lòng Nhập 'Y' Hoặc 'n'.",
"development_version": "Phiên Bản Phát Triển {current} > {latest}"
}
}

View File

@@ -4,6 +4,8 @@
"exit": "退出程序",
"reset": "重置机器标识",
"register": "注册新 Cursor 账号",
"register_google": "使用 Google 账号注册",
"register_github": "使用 GitHub 账号注册",
"register_manual": "使用自定义邮箱注册",
"quit": "关闭 Cursor 应用",
"select_language": "更改语言",
@@ -12,7 +14,8 @@
"program_terminated": "程序已被用户终止",
"error_occurred": "发生错误:{error},请重试",
"press_enter": "按回车键退出",
"disable_auto_update": "禁用 Cursor 自动更新"
"disable_auto_update": "禁用 Cursor 自动更新",
"lifetime_access_enabled": "永久订阅"
},
"languages": {
"en": "English",
@@ -272,6 +275,7 @@
"continue_anyway": "继续使用当前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'."
"invalid_choice": "选择无效。请输入 'Y' 或 'n'.",
"development_version": "开发版本 {current} > {latest}"
}
}

View File

@@ -12,7 +12,8 @@
"program_terminated": "程式已被使用者終止",
"error_occurred": "發生錯誤:{error},請重試",
"press_enter": "按返回鍵退出",
"disable_auto_update": "停用 Cursor 自動更新"
"disable_auto_update": "停用 Cursor 自動更新",
"lifetime_access_enabled": "永久訂閱"
},
"languages": {
"en": "English",
@@ -254,6 +255,7 @@
"continue_anyway": "繼續使用當前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'."
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.",
"development_version": "開發版本 {current} > {latest}"
}
}

56
main.py
View File

@@ -219,10 +219,14 @@ def print_menu():
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.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
print(f"{Fore.YELLOW} ┗━━ 🔥 {translator.get('menu.lifetime_access_enabled')} 🔥{Style.RESET_ALL}")
print(f"{Fore.GREEN}4{Style.RESET_ALL}. {translator.get('menu.register_github')}")
print(f"{Fore.YELLOW} ┗━━ 🚀 {translator.get('menu.lifetime_access_enabled')} 🚀{Style.RESET_ALL}")
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
def select_language():
@@ -277,7 +281,27 @@ def check_latest_version():
if not latest_version:
raise Exception("Invalid version format received")
if latest_version != version:
# Parse versions for proper comparison
def parse_version(version_str):
"""Parse version string into tuple for proper comparison"""
try:
return tuple(map(int, version_str.split('.')))
except ValueError:
# Fallback to string comparison if parsing fails
return version_str
current_version_tuple = parse_version(version)
latest_version_tuple = parse_version(latest_version)
# Compare versions properly
is_newer_version_available = False
if isinstance(current_version_tuple, tuple) and isinstance(latest_version_tuple, tuple):
is_newer_version_available = current_version_tuple < latest_version_tuple
else:
# Fallback to string comparison
is_newer_version_available = version != latest_version
if is_newer_version_available:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
# Ask user if they want to update
@@ -324,7 +348,11 @@ def check_latest_version():
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}")
# If current version is newer or equal to latest version
if current_version_tuple > latest_version_tuple:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.development_version', current=version, latest=latest_version)}{Style.RESET_ALL}")
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}")
@@ -358,7 +386,7 @@ def main():
while True:
try:
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-6')}: {Style.RESET_ALL}")
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-8')}: {Style.RESET_ALL}")
if choice == "0":
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
@@ -373,18 +401,26 @@ def main():
cursor_register.main(translator)
print_menu()
elif choice == "3":
import cursor_register_google
cursor_register_google.main(translator)
print_menu()
elif choice == "4":
import cursor_register_github
cursor_register_github.main(translator)
print_menu()
elif choice == "5":
import cursor_register_manual
cursor_register_manual.main(translator)
print_menu()
elif choice == "4":
elif choice == "6":
import quit_cursor
quit_cursor.quit_cursor(translator)
print_menu()
elif choice == "5":
elif choice == "7":
if select_language():
print_menu()
continue
elif choice == "6":
elif choice == "8":
import disable_auto_update
disable_auto_update.run(translator)
print_menu()

826
oauth_auth.py Normal file
View File

@@ -0,0 +1,826 @@
import os
from colorama import Fore, Style, init
import time
import random
import webbrowser
import sys
import json
from DrissionPage import ChromiumPage, ChromiumOptions
from cursor_auth import CursorAuth
from utils import get_random_wait_time, get_default_chrome_path
from config import get_config
import platform
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'START': '🚀',
'OAUTH': '🔑',
'SUCCESS': '',
'ERROR': '',
'WAIT': '',
'INFO': ''
}
class OAuthHandler:
def __init__(self, translator=None):
self.translator = translator
self.config = get_config(translator)
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
def _get_active_profile(self, user_data_dir):
"""Find the existing default/active Chrome profile"""
try:
# List all profile directories
profiles = []
for item in os.listdir(user_data_dir):
if item == 'Default' or (item.startswith('Profile ') and os.path.isdir(os.path.join(user_data_dir, item))):
profiles.append(item)
if not profiles:
print(f"{Fore.YELLOW}{EMOJI['INFO']} No Chrome profiles found, using Default{Style.RESET_ALL}")
return 'Default'
# First check if Default profile exists
if 'Default' in profiles:
print(f"{Fore.CYAN}{EMOJI['INFO']} Found Default Chrome profile{Style.RESET_ALL}")
return 'Default'
# If no Default profile, check Local State for last used profile
local_state_path = os.path.join(user_data_dir, 'Local State')
if os.path.exists(local_state_path):
with open(local_state_path, 'r', encoding='utf-8') as f:
local_state = json.load(f)
# Get info about last used profile
profile_info = local_state.get('profile', {})
last_used = profile_info.get('last_used', '')
info_cache = profile_info.get('info_cache', {})
# Try to find an active profile
for profile in profiles:
profile_path = profile.replace('\\', '/')
if profile_path in info_cache:
#print(f"{Fore.CYAN}{EMOJI['INFO']} Using existing Chrome profile: {profile}{Style.RESET_ALL}")
return profile
# If no profile found in Local State, use the first available profile
print(f"{Fore.CYAN}{EMOJI['INFO']} Using first available Chrome profile: {profiles[0]}{Style.RESET_ALL}")
return profiles[0]
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}")
return 'Default'
def setup_browser(self):
"""Setup browser for OAuth flow using active profile"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}")
# Platform-specific initialization
platform_name = platform.system().lower()
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}")
# Kill existing browser processes
self._kill_browser_processes()
# Get browser paths and user data directory
user_data_dir = self._get_user_data_directory()
chrome_path = self._get_browser_path()
if not chrome_path:
raise Exception(f"No compatible browser found. Please install Google Chrome or Chromium.\nSupported browsers for {platform_name}:\n" +
"- Windows: Google Chrome, Chromium\n" +
"- macOS: Google Chrome, Chromium\n" +
"- Linux: Google Chrome, Chromium, chromium-browser")
# Get active profile
active_profile = self._get_active_profile(user_data_dir)
print(f"{Fore.CYAN}{EMOJI['INFO']} Using browser profile: {active_profile}{Style.RESET_ALL}")
# Configure browser options
co = self._configure_browser_options(chrome_path, user_data_dir, active_profile)
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}")
self.browser = ChromiumPage(co)
# Verify browser launched successfully
if not self.browser:
raise Exception("Failed to initialize browser instance")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Browser setup failed: {str(e)}{Style.RESET_ALL}")
if "DevToolsActivePort file doesn't exist" in str(e):
print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running with administrator/root privileges{Style.RESET_ALL}")
elif "Chrome failed to start" in str(e):
print(f"{Fore.YELLOW}{EMOJI['INFO']} Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}")
return False
def _kill_browser_processes(self):
"""Kill existing browser processes based on platform"""
try:
if os.name == 'nt': # Windows
processes = ['chrome.exe', 'chromium.exe']
for proc in processes:
os.system(f'taskkill /f /im {proc} >nul 2>&1')
else: # Linux/Mac
processes = ['chrome', 'chromium', 'chromium-browser']
for proc in processes:
os.system(f'pkill -f {proc} >/dev/null 2>&1')
time.sleep(1) # Wait for processes to close
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Warning: Could not kill existing browser processes: {e}{Style.RESET_ALL}")
def _get_user_data_directory(self):
"""Get the appropriate user data directory based on platform"""
try:
if os.name == 'nt': # Windows
possible_paths = [
os.path.expandvars(r'%LOCALAPPDATA%\Google\Chrome\User Data'),
os.path.expandvars(r'%LOCALAPPDATA%\Chromium\User Data')
]
elif sys.platform == 'darwin': # macOS
possible_paths = [
os.path.expanduser('~/Library/Application Support/Google/Chrome'),
os.path.expanduser('~/Library/Application Support/Chromium')
]
else: # Linux
possible_paths = [
os.path.expanduser('~/.config/google-chrome'),
os.path.expanduser('~/.config/chromium'),
'/usr/bin/google-chrome',
'/usr/bin/chromium-browser'
]
# Try each possible path
for path in possible_paths:
if os.path.exists(path):
print(f"{Fore.CYAN}{EMOJI['INFO']} Found browser data directory: {path}{Style.RESET_ALL}")
return path
# Create temporary profile if no existing profile found
temp_profile = os.path.join(os.path.expanduser('~'), '.cursor_temp_profile')
print(f"{Fore.YELLOW}{EMOJI['INFO']} Creating temporary profile at: {temp_profile}{Style.RESET_ALL}")
os.makedirs(temp_profile, exist_ok=True)
return temp_profile
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error getting user data directory: {e}{Style.RESET_ALL}")
raise
def _get_browser_path(self):
"""Get the browser executable path based on platform"""
try:
# Try default path first
chrome_path = get_default_chrome_path()
if chrome_path and os.path.exists(chrome_path):
return chrome_path
print(f"{Fore.YELLOW}{EMOJI['INFO']} Searching for alternative browser installations...{Style.RESET_ALL}")
# Platform-specific paths
if os.name == 'nt': # Windows
alt_paths = [
r'C:\Program Files\Google\Chrome\Application\chrome.exe',
r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
r'C:\Program Files\Chromium\Application\chrome.exe',
os.path.expandvars(r'%ProgramFiles%\Google\Chrome\Application\chrome.exe'),
os.path.expandvars(r'%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe')
]
elif sys.platform == 'darwin': # macOS
alt_paths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
'~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'~/Applications/Chromium.app/Contents/MacOS/Chromium'
]
else: # Linux
alt_paths = [
'/usr/bin/google-chrome',
'/usr/bin/chromium-browser',
'/usr/bin/chromium',
'/snap/bin/chromium',
'/usr/local/bin/chrome',
'/usr/local/bin/chromium'
]
# Try each alternative path
for path in alt_paths:
expanded_path = os.path.expanduser(path)
if os.path.exists(expanded_path):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found browser at: {expanded_path}{Style.RESET_ALL}")
return expanded_path
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error finding browser path: {e}{Style.RESET_ALL}")
return None
def _configure_browser_options(self, chrome_path, user_data_dir, active_profile):
"""Configure browser options based on platform"""
try:
co = ChromiumOptions()
co.set_paths(browser_path=chrome_path, user_data_path=user_data_dir)
co.set_argument(f'--profile-directory={active_profile}')
# Basic options
co.set_argument('--no-first-run')
co.set_argument('--no-default-browser-check')
co.set_argument('--disable-gpu')
# Platform-specific options
if sys.platform.startswith('linux'):
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
co.set_argument('--disable-setuid-sandbox')
elif sys.platform == 'darwin':
co.set_argument('--disable-gpu-compositing')
elif os.name == 'nt':
co.set_argument('--disable-features=TranslateUI')
co.set_argument('--disable-features=RendererCodeIntegrity')
return co
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}")
raise
def handle_google_auth(self):
"""Handle Google OAuth authentication"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start')}{Style.RESET_ALL}")
# Setup browser
if not self.setup_browser():
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
return False, None
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Look for Google auth button
selectors = [
"//a[contains(@href,'GoogleOAuth')]",
"//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]",
"(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback
]
auth_btn = None
for selector in selectors:
try:
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
if auth_btn and auth_btn.is_displayed():
break
except:
continue
if not auth_btn:
raise Exception("Could not find Google authentication button")
# Click the button and wait for page load
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting Google authentication...{Style.RESET_ALL}")
auth_btn.click()
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Check if we're on account selection page
if "accounts.google.com" in self.browser.url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account to continue...{Style.RESET_ALL}")
try:
self.browser.run_js("""
alert('Please select your Google account to continue with Cursor authentication');
""")
except:
pass # Alert is optional
# Wait for authentication to complete
auth_info = self._wait_for_auth()
if not auth_info:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
return False, None
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
return True, auth_info
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
return False, None
finally:
try:
if self.browser:
self.browser.quit()
except:
pass
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
return False, None
def _wait_for_auth(self):
"""Wait for authentication to complete and extract auth info"""
try:
max_wait = 300 # 5 minutes
start_time = time.time()
check_interval = 2 # Check every 2 seconds
print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for authentication (timeout: 5 minutes)...{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
# Check for authentication cookies
cookies = self.browser.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
value = cookie.get("value", "")
token = None
if "::" in value:
token = value.split("::")[-1]
elif "%3A%3A" in value:
token = value.split("%3A%3A")[-1]
if token:
# Get email from settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3)
email = None
try:
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
if email_element:
email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {email}{Style.RESET_ALL}")
except:
email = "user@cursor.sh" # Fallback email
# Check usage count
try:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150":
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, creating new account...{Style.RESET_ALL}")
# Delete current account
if self._delete_current_account():
# Start new authentication
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
return self.handle_google_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}")
return {"email": email, "token": token}
# Also check URL as backup
if "cursor.com/settings" in self.browser.url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected successful login{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}")
time.sleep(check_interval)
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error while waiting for authentication: {str(e)}{Style.RESET_ALL}")
return None
def handle_github_auth(self):
"""Handle GitHub OAuth authentication"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.github_start')}{Style.RESET_ALL}")
# Setup browser
if not self.setup_browser():
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
return False, None
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Look for GitHub auth button
selectors = [
"//a[contains(@href,'GitHubOAuth')]",
"//a[contains(@class,'auth-method-button') and contains(@href,'GitHubOAuth')]",
"(//a[contains(@class,'auth-method-button')])[2]" # Second auth button as fallback
]
auth_btn = None
for selector in selectors:
try:
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
if auth_btn and auth_btn.is_displayed():
break
except:
continue
if not auth_btn:
raise Exception("Could not find GitHub authentication button")
# Click the button and wait for page load
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting GitHub authentication...{Style.RESET_ALL}")
auth_btn.click()
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Wait for authentication to complete
auth_info = self._wait_for_auth()
if not auth_info:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
return False, None
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
return True, auth_info
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
return False, None
finally:
try:
if self.browser:
self.browser.quit()
except:
pass
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
return False, None
def _handle_oauth(self, auth_type):
"""Handle OAuth authentication for both Google and GitHub
Args:
auth_type (str): Type of authentication ('google' or 'github')
"""
try:
if not self.setup_browser():
return False, None
# Navigate to auth URL
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Set selectors based on auth type
if auth_type == "google":
selectors = [
"//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'][contains(@href,'GoogleOAuth')]",
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[1]"
]
else: # github
selectors = [
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[2]"
]
# Wait for the button to be available
auth_btn = None
max_button_wait = 30 # 30 seconds
button_start_time = time.time()
while time.time() - button_start_time < max_button_wait:
for selector in selectors:
try:
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=1)
if auth_btn and auth_btn.is_displayed():
break
except:
continue
if auth_btn:
break
time.sleep(1)
if auth_btn:
# Click the button and wait for page load
auth_btn.click()
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Check if we're on account selection page
if auth_type == "google" and "accounts.google.com" in self.browser.url:
alert_js = """
alert('Please select your Google account manually to continue with Cursor authentication');
"""
try:
self.browser.run_js(alert_js)
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Alert display failed: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account manually to continue with Cursor authentication...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Waiting for authentication to complete...{Style.RESET_ALL}")
# Wait for authentication to complete
max_wait = 300 # 5 minutes
start_time = time.time()
last_url = self.browser.url
print(f"{Fore.CYAN}{EMOJI['WAIT']} Checking authentication status...{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
# Check for authentication cookies
cookies = self.browser.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
value = cookie.get("value", "")
if "::" in value:
token = value.split("::")[-1]
elif "%3A%3A" in value:
token = value.split("%3A%3A")[-1]
if token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}")
# Navigate to settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3) # Wait for settings page to load
# Get email from settings page
try:
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
if email_element:
actual_email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
try:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150": # Changed back to actual condition
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
delete_js = """
function deleteAccount() {
return new Promise((resolve, reject) => {
fetch('https://www.cursor.com/api/dashboard/delete-account', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(response => {
if (response.status === 200) {
resolve('Account deleted successfully');
} else {
reject('Failed to delete account: ' + response.status);
}
})
.catch(error => {
reject('Error: ' + error);
});
});
}
return deleteAccount();
"""
try:
result = self.browser.run_js(delete_js)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page and repeat authentication
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
# Explicitly navigate to the authentication page
#self.browser.get("https://authenticator.cursor.sh/sign-up")
# time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Call handle_google_auth again to repeat the entire process
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
return self.handle_google_auth()
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
# Remove the browser stay open prompt and input wait
return True, {"email": actual_email, "token": token}
# Also check URL as backup
current_url = self.browser.url
if "cursor.com/settings" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Already on settings page!{Style.RESET_ALL}")
time.sleep(1)
cookies = self.browser.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
value = cookie.get("value", "")
if "::" in value:
token = value.split("::")[-1]
elif "%3A%3A" in value:
token = value.split("%3A%3A")[-1]
if token:
# Get email and check usage here too
try:
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
if email_element:
actual_email = email_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
try:
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
if usage_element:
usage_text = usage_element.text
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
# Check if account is expired
if usage_text.strip() == "150 / 150": # Changed back to actual condition
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
delete_js = """
function deleteAccount() {
return new Promise((resolve, reject) => {
fetch('https://www.cursor.com/api/dashboard/delete-account', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(response => {
if (response.status === 200) {
resolve('Account deleted successfully');
} else {
reject('Failed to delete account: ' + response.status);
}
})
.catch(error => {
reject('Error: ' + error);
});
});
}
return deleteAccount();
"""
try:
result = self.browser.run_js(delete_js)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page and repeat authentication
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
# Explicitly navigate to the authentication page
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
# Call handle_google_auth again to repeat the entire process
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
return self.handle_google_auth()
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
# Remove the browser stay open prompt and input wait
return True, {"email": actual_email, "token": token}
elif current_url != last_url:
print(f"{Fore.CYAN}{EMOJI['INFO']} Page changed, checking auth...{Style.RESET_ALL}")
last_url = current_url
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Status check error: {str(e)}{Style.RESET_ALL}")
time.sleep(1)
continue
time.sleep(1)
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
return False, None
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}")
return False, None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication failed: {str(e)}{Style.RESET_ALL}")
return False, None
finally:
if self.browser:
self.browser.quit()
def _extract_auth_info(self):
"""Extract authentication information after successful OAuth"""
try:
# Get cookies with retry
max_retries = 3
for attempt in range(max_retries):
try:
cookies = self.browser.cookies()
if cookies:
break
time.sleep(1)
except:
if attempt == max_retries - 1:
raise
time.sleep(1)
# Debug cookie information
print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}")
email = None
token = None
for cookie in cookies:
name = cookie.get("name", "")
if name == "WorkosCursorSessionToken":
try:
value = cookie.get("value", "")
if "::" in value:
token = value.split("::")[-1]
elif "%3A%3A" in value:
token = value.split("%3A%3A")[-1]
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Token extraction error: {str(e)}{Style.RESET_ALL}")
elif name == "cursor_email":
email = cookie.get("value")
if email and token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful - Email: {email}{Style.RESET_ALL}")
return True, {"email": email, "token": token}
else:
missing = []
if not email:
missing.append("email")
if not token:
missing.append("token")
print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}")
return False, None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to extract auth info: {str(e)}{Style.RESET_ALL}")
return False, None
def main(auth_type, translator=None):
"""Main function to handle OAuth authentication
Args:
auth_type (str): Type of authentication ('google' or 'github')
translator: Translator instance for internationalization
"""
handler = OAuthHandler(translator)
if auth_type.lower() == 'google':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")
success, auth_info = handler.handle_google_auth()
elif auth_type.lower() == 'github':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start')}{Style.RESET_ALL}")
success, auth_info = handler.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} Invalid authentication type{Style.RESET_ALL}")
return False
if success and auth_info:
# Update Cursor authentication
auth_manager = CursorAuth(translator)
if auth_manager.update_auth(
email=auth_info["email"],
access_token=auth_info["token"],
refresh_token=auth_info["token"]
):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success')}{Style.RESET_ALL}")
# Close the browser after successful authentication
if handler.browser:
handler.browser.quit()
print(f"{Fore.CYAN}{EMOJI['INFO']} Browser closed{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}")
return False

15
scripts/install.sh Normal file → Executable file
View File

@@ -39,14 +39,17 @@ get_downloads_dir() {
# Get latest version
get_latest_version() {
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
latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest) || {
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')
if [ -z "$VERSION" ]; then
echo -e "${RED}❌ Failed to parse version from GitHub API response:\n${latest_release}"
exit 1
fi
echo -e "${GREEN}✅ Found latest version: ${VERSION}${NC}"
}
@@ -170,9 +173,7 @@ install_cursor_free_vip() {
fi
echo -e "${CYAN} Setting executable permissions...${NC}"
chmod +x "${binary_path}"
if [ $? -eq 0 ]; then
if chmod +x "${binary_path}"; then
echo -e "${GREEN}✅ Installation completed!${NC}"
echo -e "${CYAN} Program downloaded to: ${binary_path}${NC}"
echo -e "${CYAN} Starting program...${NC}"

View File

@@ -1,6 +1,7 @@
import os
import sys
import platform
import random
def get_user_documents_path():
"""Get user documents path"""
@@ -29,4 +30,40 @@ def get_linux_cursor_path():
]
# return the first path that exists
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
def get_random_wait_time(config, timing_key):
"""Get random wait time based on configuration timing settings
Args:
config (dict): Configuration dictionary containing timing settings
timing_key (str): Key to look up in the timing settings
Returns:
float: Random wait time in seconds
"""
try:
# Get timing value from config
timing = config.get('Timing', {}).get(timing_key)
if not timing:
# Default to 0.5-1.5 seconds if timing not found
return random.uniform(0.5, 1.5)
# Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5")
if isinstance(timing, str):
if '-' in timing:
min_time, max_time = map(float, timing.split('-'))
elif ',' in timing:
min_time, max_time = map(float, timing.split(','))
else:
# Single value, use it as both min and max
min_time = max_time = float(timing)
else:
# If timing is a number, use it as both min and max
min_time = max_time = float(timing)
return random.uniform(min_time, max_time)
except (ValueError, TypeError, AttributeError):
# Return default value if any error occurs
return random.uniform(0.5, 1.5)