Compare commits

...

59 Commits

Author SHA1 Message Date
Pin Studios
74be8a0a77 refactor: Simplify profile selection process in OAuthHandler
- Removed the user data directory retrieval in main.py for cleaner profile selection.
- Updated profile selection logic to include an exit option in oauth_auth.py.
- Enhanced user prompts in multiple languages to improve user experience during profile selection.
2025-04-05 19:10:21 +08:00
Pin Studios
bce205b252 chore: Update README image size for better visibility
- Increased the width of the image in README.md from 400px to 800px to enhance visual clarity and presentation.
2025-04-05 18:49:05 +08:00
Pin Studios
8fcf3b62cf chore: Update README with new image and improve visual content
- Replaced the old image with a new one in the README.md to enhance visual appeal.
- Added the new image file 'pro_2025-04-05_18-47-56.png' to the images directory.
2025-04-05 18:48:28 +08:00
Pin Studios
eeaee073cf feat: Add multilingual feature request templates for improved user engagement
- Introduced new feature request templates in both English and Chinese to enhance user experience and encourage suggestions.
- Removed the old single-language feature request template to streamline the process.
2025-04-05 18:40:12 +08:00
Pin Studios
2f353da381 Fix: Address issues with custom Chrome installations in CHANGELOG.md 2025-04-05 18:37:29 +08:00
Pin Studios
efd1417407 Merge pull request #480 from handwerk2016/main
[Windows] Fix for custom Chrome Installations
2025-04-05 18:36:25 +08:00
Pin Studios
2cc7cce283 Refactor OAuthHandler for improved Chrome profile management
- Introduced methods to retrieve and select available Chrome profiles.
- Enhanced error handling for profile loading and selection.
- Removed unnecessary Linux-specific checks and subprocess calls.
- Streamlined browser setup process to utilize selected profiles.
- Improved account usage limit detection with a dedicated function.
- Cleaned up code by removing deprecated methods and comments.
2025-04-05 18:36:00 +08:00
Pin Studios
4c30afcd7f chore: update version to 1.8.06 in .env file 2025-04-05 18:31:33 +08:00
Pin Studios
bfb2f5e95a Merge pull request #494 from saadtahir995/main
feat: Added Google OAuth Acc Deletion Tool
2025-04-05 18:30:57 +08:00
Pin Studios
35e01edf9c feat: Enhance account deletion process with improved translations and error handling
- Added multilingual support for account deletion messages in delete_cursor_google.py and oauth_auth.py
- Updated CHANGELOG.md to reflect new features and fixes
- Improved user prompts and error messages for better clarity and user experience
2025-04-05 18:30:44 +08:00
Pin Studios
df9e88131b Merge pull request #469 from Nigel1992/feat/feature-request-template-new
feat: add comprehensive feature request issue template [EN only]
2025-04-05 18:16:20 +08:00
Pin Studios
8a4d22103e Merge pull request #492 from Lucaszmv/fix/usage-limits-check
Fix: Improve usage limits check and tuple index error
2025-04-05 18:16:02 +08:00
Pin Studios
1244d86994 Merge pull request #489 from Hexoso/main
Fix: bug in disable cursor auto update
2025-04-05 18:14:56 +08:00
Pin Studios
a2833fcbd4 fix: handle permission errors in auto-update disabling process and enhance localization messages 2025-04-05 18:14:42 +08:00
Saad
b3e7c101d3 feat: Added Google OAuth Acc Deletion Tool 2025-04-05 12:16:02 +05:00
Sadegh Barzegar
0a779063bf chore: modify disable auto update & config 2025-04-05 05:01:46 +03:30
Lucas
c96433e446 Fix: Improve usage limits check and tuple index error 2025-04-04 17:23:27 -03:00
Sadegh Barzegar
5604f5073d fix: bug in disable cursor update 2025-04-04 12:19:14 +03:30
Alex
3cad68be36 Update oauth_auth.py
changed locale
2025-04-04 09:31:54 +03:00
Pin Studios
d8799cdf2e Merge pull request #485 from moelbanna-bot/main
fix-linux-appimage
2025-04-04 06:12:59 +08:00
Pin Studios
3e5c22b816 Merge pull request #479 from vauns/main
feat: Add support for custom Cursor installation paths on Windows
2025-04-04 06:12:10 +08:00
Pin Studios
893c3f8a52 Merge pull request #467 from Nigel1992/feat/chrome-profile-selection
feat(oauth): add Chrome profile selection feature
2025-04-04 06:10:07 +08:00
Mohamed Elbanna
edb74e21be Enhance Linux cursor path detection to include extracted AppImage directories and add debug output for found paths 2025-04-03 15:47:58 +02:00
Mohamed Elbanna
be52ce98a6 Improve Linux cursor path detection to include extracted AppImage with correct usr structure and add debug output for found paths 2025-04-03 15:20:38 +02:00
Mohamed Elbanna
163748fd96 Refactor Linux path detection to use extracted AppImage with correct usr structure 2025-04-03 14:55:56 +02:00
Mohamed Elbanna
f888b8c90c Enhance Linux path detection for Cursor AppImage installations 2025-04-03 14:35:07 +02:00
Alex
3c12561997 [Windows] Fix for Custom Chrome Installations
This commit fixes that error when you had an custom chrome installation. For example: D:\Chromium or something.
Now it uses windows PATH to start chrome and if there's no PATH installation script would check default location.

Here's demo for current bug: https://youtu.be/O8lPblnBIGQ
2025-04-03 10:55:45 +03:00
吕海龙
80aab8741f feat: Add support for custom Cursor installation paths on Windows
- Modify base_path detection to use custom paths from config
- Replace forward slashes with backslashes in Windows path definition
2025-04-03 15:42:10 +08:00
Nigel
2595c4c95d Merge branch 'yeongpin:main' into feat/feature-request-template-new 2025-04-03 01:11:38 +02:00
Nigel1992
f18e65f391 feat(template): add feature request issue template 2025-04-03 01:10:19 +02:00
Nigel1992
671307b53b fix(oauth): remove duplicate browser data directory message 2025-04-03 01:02:57 +02:00
Nigel1992
f58bb70d3a fix(oauth): fix translation keys in Chrome profile selection 2025-04-03 01:01:58 +02:00
Nigel1992
5bfe653a92 fix(oauth): remove duplicate Chrome warning message 2025-04-03 01:00:38 +02:00
Nigel1992
a66a0e5395 feat(oauth): add Chrome profile selection feature
- Add new menu option to select which Chrome profile to use

- Display actual profile names from Chrome's Local State

- Add safety warning and confirmation before closing Chrome

- Add translations for all supported languages
2025-04-03 00:51:04 +02:00
Pin Studios
ea44218a8a Update CHANGELOG.md 2025-04-03 02:09:29 +08:00
Pin Studios
fdc8317380 Update .env 2025-04-03 02:07:49 +08:00
Pin Studios
523daea54e Merge pull request #463 from blobs0/patch-1
Patch Linux Path Not Found
2025-04-03 02:06:52 +08:00
Pin Studios
5a4e32f3f4 Merge pull request #460 from Lucaszmv/fix/oauth-account-usage-limit
fix: improve account usage limit detection
2025-04-03 02:06:09 +08:00
Valentin Guerdin
d91485ec75 Patch Linux Path Not Found
add folder /usr/lib/cursor/app/
2025-04-02 14:32:27 +02:00
Lucas
51cef9c2b2 fix: improve account usage limit detection
- Add support for detecting both 150/150 and 50/50 usage limits
- Improve usage parsing and validation
- Add better error handling for usage detection
- Add more descriptive log messages
2025-04-01 15:42:05 -03:00
yeongpin
6e6180e331 fix: update reset emoji and correct button patterns for cross-platform compatibility 2025-03-31 17:49:35 +08:00
yeongpin
e2d7c1c496 chore: update version to 1.8.04 and enhance CHANGELOG with new features and fixes 2025-03-31 12:35:35 +08:00
yeongpin
a56b978669 feat: add new error messages for browser process handling in English, Simplified Chinese, and Traditional Chinese locales 2025-03-31 12:33:25 +08:00
Pin Studios
9793b91bc7 Merge pull request #437 from Nigel1992/fix/433-linux-chrome-visibility
fix: improve Linux Chrome visibility and root user handling
2025-03-31 12:00:11 +08:00
yeongpin
735dd8c1eb refactor: enhance user feedback and error messages with translation support across multiple modules 2025-03-31 11:58:54 +08:00
Pin Studios
e5d192efcb Merge pull request #434 from BasaiCorp/main
Update totally_reset_cursor.py face 1 wait for face 3
2025-03-31 11:23:52 +08:00
Pin Studios
a4d5b0e467 Update totally_reset_cursor.py
Fix & Add Locale
2025-03-31 11:23:31 +08:00
Nigel1992
d7b056b339 fix: improve Linux Chrome visibility and root user handling 2025-03-30 22:27:08 +02:00
Nigel1992
bd152be4e8 fix: improve Linux path handling and fix permission issues 2025-03-30 22:14:03 +02:00
Prathmesh Barot
172d9fb4bb Update totally_reset_cursor.py face 1 wait for face 3
1/3
2025-03-30 23:23:10 +05:30
Pin Studios
e2d70c1738 Update CHANGELOG.md 2025-03-31 01:03:13 +08:00
Pin Studios
650e2d33db Update .env 2025-03-31 01:01:02 +08:00
Pin Studios
2d1347c8c6 Merge pull request #430 from Lostata/main
Fixed grammar errors in README.md
2025-03-31 01:00:37 +08:00
Pin Studios
67d3554664 Merge pull request #426 from Nigel1992/fix/412-case-insensitive-cursor
Fix: improve Linux path handling and add case-insensitive Cursor directory detection
2025-03-31 00:59:50 +08:00
yeongpin
cb202e08e5 refactor: enhance configuration error messages with translation support and improve user feedback 2025-03-31 00:58:51 +08:00
Lostata
0c0ec47432 Fixed grammar errors in README.md 2025-03-30 09:05:46 +03:00
Nigel1992
f00678ddcb fix: add case-insensitive handling for Cursor/cursor directory 2025-03-29 23:59:04 +01:00
Nigel1992
dbc220053d fix: improve Linux path handling for config file detection 2025-03-29 23:53:45 +01:00
yeongpin
d7ab362c4c Update README.md to reflect support for the latest 0.48.x version and add new configuration options for checking updates and displaying account information. 2025-03-29 22:30:59 +08:00
31 changed files with 2513 additions and 836 deletions

4
.env
View File

@@ -1,2 +1,2 @@
version=1.8.02
VERSION=1.8.02
version=1.8.06
VERSION=1.8.06

View File

@@ -0,0 +1,75 @@
name: 💡 功能建议
description: 为这个项目提出新的想法
title: "[功能建议]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
感谢您抽出时间提出新功能建议!
请确保填写以下所有部分。
- type: textarea
id: feature-description
attributes:
label: 功能描述
description: 清晰简洁地描述您想要实现的功能。
placeholder: "我希望能够..."
validations:
required: true
- type: textarea
id: problem-solution
attributes:
label: 问题和解决方案
description: 描述您试图解决的问题,以及这个功能将如何帮助解决。
placeholder: "目前,当我尝试..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 考虑过的替代方案
description: 描述您考虑过的任何替代解决方案或功能。
placeholder: "我考虑过..."
- type: dropdown
id: priority
attributes:
label: 优先级
description: 这个功能对您来说有多重要?
options:
- 高(对我的工作流程至关重要)
- 中(会很有帮助)
- 低(有更好)
validations:
required: true
- type: dropdown
id: impact
attributes:
label: 影响范围
description: 有多少用户会从这个功能中受益?
options:
- 所有用户
- 大多数用户
- 部分用户
- 仅我个人
validations:
required: true
- type: textarea
id: technical-details
attributes:
label: 技术细节
description: 您想分享的任何技术考虑或实现细节。
placeholder: "这个功能可以通过..."
- type: textarea
id: screenshots
attributes:
label: 截图
description: 如果适用,添加截图以帮助解释您的功能建议。
placeholder: "您可以在此处拖放截图..."
- type: checkboxes
id: terms
attributes:
label: 行为准则
description: 提交此问题即表示您同意遵守本项目的行为准则
options:
- label: 我同意遵守本项目的行为准则
required: true

View File

@@ -0,0 +1,75 @@
name: 💡 Feature Request
description: Suggest an idea for this project
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest a new feature!
Please make sure to fill out all the sections below.
- type: textarea
id: feature-description
attributes:
label: Feature Description
description: A clear and concise description of what you want to happen.
placeholder: "I would like to..."
validations:
required: true
- type: textarea
id: problem-solution
attributes:
label: Problem & Solution
description: Describe the problem you're trying to solve and how this feature would help.
placeholder: "Currently, when I try to..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Describe any alternative solutions or features you've considered.
placeholder: "I've considered..."
- type: dropdown
id: priority
attributes:
label: Priority
description: How important is this feature to you?
options:
- High (Critical for my workflow)
- Medium (Would be very helpful)
- Low (Nice to have)
validations:
required: true
- type: dropdown
id: impact
attributes:
label: Impact
description: How many users would benefit from this feature?
options:
- All users
- Most users
- Some users
- Just me
validations:
required: true
- type: textarea
id: technical-details
attributes:
label: Technical Details
description: Any technical considerations or implementation details you'd like to share.
placeholder: "The feature could be implemented by..."
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your feature.
placeholder: "You can drag and drop screenshots here..."
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow this project's Code of Conduct
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -6,7 +6,7 @@ on:
version:
description: 'Version number (e.g. 1.0.9)'
required: true
default: '1.8.02'
default: '1.8.04'
permissions:
contents: write

View File

@@ -1,5 +1,32 @@
# Change Log
## v1.8.06
1. Add: Google Account Deletion Feature | 添加 Google 账号删除功能
2. Update: Menu with new account deletion option | 更新菜单添加账号删除选项
3. Add: Multilanguage support for account deletion | 添加账号删除功能的多语言支持
4. Fix: Improve usage limits check and tuple index error | 修复使用限制检查和元组索引错误
5. Fix: bug in disable cursor auto update | 修复禁用 Cursor 自动更新的错误
6. Fix: Linux-appimage | 修复 Linux-appimage 问题
7. Add: Support for custom Cursor installation paths on Windows | 添加 Windows 系统下自定义 Cursor 安装路径支持
8. Add: Chrome profile selection feature | 添加 Chrome 配置文件选择功能
9. Fix: improve account usage limit detection | 修復賬號檢測
10. Fix: For custom Chrome Installations | 修復自定義chrome遊覽器安裝
## v1.8.05
1. Fix: Linux Path Not Found 修復linuxpath問題
2. Add: support for detecting both 150/150 and 50/50 usage limits 添加偵測50 或者150的使用量
3. Improve: usage parsing and validation 檢測使用量
## v1.8.04
1. Update totally_reset_cursor.py | 更新 totally_reset_cursor.py
2. Fix: improve Linux Chrome visibility and root user handling | 修復 Linux Chrome 可見性以及 root 用戶處理
3. Fix: improve Linux path handling and fix permission issues | 修復 Linux 路徑處理以及修復權限問題
4. Fix: Some Issues | 修復一些問題
## v1.8.03
1. Fix: Improve Linux path handling and add case-insensitive Cursor directory detection 修復Linux系統路徑錯誤以及添加cursor 路徑偵測
2. Fix: Some Issues | 修復一些問題
## v1.8.02
1. Add: New Temp Email | 增加新臨時郵箱
2. Add: Config Options | 增加配置選項

View File

@@ -13,22 +13,21 @@
[![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a1)](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.48.x Version | 支持最新 0.48.x 版本</h4>
This tool register accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes,reset and wipe Cursor data and hardware info.
This tool registers accounts with custom emails, support Google and GitHub account registrations, temporary GitHub account registration, kills all Cursor's running processes, resets and wipes Cursor data and hardware info.
Supports Windows, macOS and Linux.
For optimal performance, run with privileges and always stay up to date.
Always clean your browser cache and cookies. If possible, user a VPN to create new accounts.
Always clean your browser's cache and cookies. If possible, use a VPN to create new accounts.
這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。
<p align="center">
<img src="./images/new_2025-03-22_19-53-10.png" alt="new" width="400" style="border-radius: 6px;"/><br>
<img src="./images/pro_2025-04-05_18-47-56.png" alt="new" width="800" style="border-radius: 6px;"/><br>
</p>
##### If you don't have Google Chrome, you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
@@ -55,6 +54,8 @@ Always clean your browser cache and cookies. If possible, user a VPN to create n
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
* Delete Cursor Google Account<br>删除 Cursor Google 账号<br>
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
## 💻 System Support | 系統支持
@@ -101,7 +102,7 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
</details>
2. If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
## ❗ Note | 注意事項
@@ -116,9 +117,9 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe
[Turnstile]
# Handle Tuenstile Wait Time | 等待人機驗證時間
# Handle Turnstile Wait Time | 等待人機驗證時間
handle_turnstile_time = 2
# Handle Tuenstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
# Handle Turnstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
handle_turnstile_random_time = 1-3
[OSPaths]
@@ -159,11 +160,17 @@ failed_retry_time = 0.5-1
retry_interval = 8-12
# Max Timeout | 最大超時時間
max_timeout = 160
[Utils]
# Check Update | 檢查更新
check_update = True
# Show Account Info | 顯示賬號信息
show_account_info = True
```
</details>
* Use administrator to run the script <br>請使用管理員身份運行腳本
* Use administrator privileges to run the script <br>請使用管理員身份運行腳本
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>

0
build.sh Normal file → Executable file
View File

View File

@@ -33,6 +33,8 @@ a = Analysis(
('new_tempemail.py', '.'),
('quit_cursor.py', '.'),
('cursor_register_manual.py', '.'),
('oauth_auth.py', '.'),
('utils.py', '.'),
('.env', '.'),
('block_domain.txt', '.')
],
@@ -42,7 +44,9 @@ a = Analysis(
'new_signup',
'new_tempemail',
'quit_cursor',
'cursor_register_manual'
'cursor_register_manual',
'oauth_auth',
'utils'
],
hookspath=[],
hooksconfig={},

141
config.py
View File

@@ -66,8 +66,12 @@ def setup_config(translator=None):
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
'updater_path': os.path.join(localappdata, "cursor-updater"),
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml")
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml"),
'product_json_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
}
# Create storage directory
os.makedirs(os.path.dirname(default_config['WindowsPaths']['storage_path']), exist_ok=True)
elif sys.platform == "darwin":
default_config['MacPaths'] = {
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
@@ -75,19 +79,116 @@ def setup_config(translator=None):
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml",
'product_json_path': "/Applications/Cursor.app/Contents/Resources/app/product.json"
}
elif sys.platform == "linux":
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
# Create storage directory
os.makedirs(os.path.dirname(default_config['MacPaths']['storage_path']), exist_ok=True)
elif sys.platform == "linux":
# Get the actual user's home directory, handling both sudo and normal cases
sudo_user = os.environ.get('SUDO_USER')
current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME'))
if not current_user:
current_user = os.path.expanduser('~').split('/')[-1]
# Handle sudo case
if sudo_user:
actual_home = f"/home/{sudo_user}"
root_home = "/root"
else:
actual_home = f"/home/{current_user}"
root_home = None
if not os.path.exists(actual_home):
actual_home = os.path.expanduser("~")
# Define base config directory
config_base = os.path.join(actual_home, ".config")
# Try both "Cursor" and "cursor" directory names in both user and root locations
cursor_dir = None
possible_paths = [
os.path.join(config_base, "Cursor"),
os.path.join(config_base, "cursor"),
os.path.join(root_home, ".config", "Cursor") if root_home else None,
os.path.join(root_home, ".config", "cursor") if root_home else None
]
for path in possible_paths:
if path and os.path.exists(path):
cursor_dir = path
break
if not cursor_dir:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.neither_cursor_nor_cursor_directory_found', config_base=config_base) if translator else f'Neither Cursor nor cursor directory found in {config_base}'}{Style.RESET_ALL}")
if root_home:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.also_checked', path=f'{root_home}/.config') if translator else f'Also checked {root_home}/.config'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Define Linux paths using the found cursor directory
storage_path = os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/storage.json")) if cursor_dir else ""
storage_dir = os.path.dirname(storage_path) if storage_path else ""
# Verify paths and permissions
try:
# Check storage directory
if storage_dir and not os.path.exists(storage_dir):
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_directory_not_found', storage_dir=storage_dir) if translator else f'Storage directory not found: {storage_dir}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Check storage.json with more detailed verification
if storage_path and os.path.exists(storage_path):
# Get file stats
try:
stat = os.stat(storage_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.storage_file_found', storage_path=storage_path) if translator else f'Storage file found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', size=stat.st_size) if translator else f'File size: {stat.st_size} bytes'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', permissions=oct(stat.st_mode & 0o777)) if translator else f'File permissions: {oct(stat.st_mode & 0o777)}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', owner=stat.st_uid) if translator else f'File owner: {stat.st_uid}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', group=stat.st_gid) if translator else f'File group: {stat.st_gid}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_getting_file_stats', error=str(e)) if translator else f'Error getting file stats: {str(e)}'}{Style.RESET_ALL}")
# Check if file is readable and writable
if not os.access(storage_path, os.R_OK | os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.permission_denied', storage_path=storage_path) if translator else f'Permission denied: {storage_path}'}{Style.RESET_ALL}")
if sudo_user:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {sudo_user}:{sudo_user} {storage_path}') if translator else f'Try running: chown {sudo_user}:{sudo_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {current_user}:{current_user} {storage_path}') if translator else f'Try running: chown {current_user}:{current_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
# Try to read the file to verify it's not corrupted
try:
with open(storage_path, 'r') as f:
content = f.read()
if not content.strip():
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_is_empty', storage_path=storage_path) if translator else f'Storage file is empty: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted, please reinstall Cursor'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data') if translator else 'Storage file is valid and contains data'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_reading_storage_file', error=str(e)) if translator else f'Error reading storage file: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted. Please reinstall Cursor'}{Style.RESET_ALL}")
elif storage_path:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_not_found', storage_path=storage_path) if translator else f'Storage file not found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
except (OSError, IOError) as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_checking_linux_paths', error=str(e)) if translator else f'Error checking Linux paths: {str(e)}'}{Style.RESET_ALL}")
# Define all paths using the found cursor directory
default_config['LinuxPaths'] = {
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/.config/cursor/machineid"),
'storage_path': storage_path,
'sqlite_path': os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/state.vscdb")) if cursor_dir else "",
'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "",
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.expanduser("~/.config/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
'updater_path': os.path.join(config_base, "cursor-updater"),
'update_yml_path': os.path.join(cursor_dir, "resources/app-update.yml") if cursor_dir else "",
'product_json_path': os.path.join(cursor_dir, "resources/app/product.json") if cursor_dir else ""
}
# Read existing configuration and merge
@@ -104,13 +205,13 @@ def setup_config(translator=None):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW} {translator.get('register.config_option_added', option=f'{section}.{option}')}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.config_option_added', option=f'{section}.{option}') if translator else f'Config option added: {section}.{option}'}{Style.RESET_ALL}")
if config_modified:
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN} {translator.get('register.config_updated')}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_updated') if translator else 'Config updated'}{Style.RESET_ALL}")
else:
for section, options in default_config.items():
config.add_section(section)
@@ -120,33 +221,31 @@ def setup_config(translator=None):
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN} {translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_created', config_file=config_file) if translator else f'Config created: {config_file}'}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED} {translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.config_setup_error', error=str(e)) if translator else f'Error setting up config: {str(e)}'}{Style.RESET_ALL}")
return None
def print_config(config, translator=None):
"""Print configuration in a readable format"""
if not config:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available')}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available') if translator else 'Configuration not available'}{Style.RESET_ALL}")
return
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration')}:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration') if translator else 'Configuration'}:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
for section in config.sections():
print(f"{Fore.GREEN}[{section}]{Style.RESET_ALL}")
for key, value in config.items(section):
# 对布尔值进行特殊处理,使其显示为彩色
if value.lower() in ('true', 'yes', 'on', '1'):
value_display = f"{Fore.GREEN}{translator.get('config.enabled')}{Style.RESET_ALL}"
value_display = f"{Fore.GREEN}{translator.get('config.enabled') if translator else 'Enabled'}{Style.RESET_ALL}"
elif value.lower() in ('false', 'no', 'off', '0'):
value_display = f"{Fore.RED}{translator.get('config.disabled')}{Style.RESET_ALL}"
value_display = f"{Fore.RED}{translator.get('config.disabled') if translator else 'Disabled'}{Style.RESET_ALL}"
else:
value_display = value
@@ -154,7 +253,7 @@ def print_config(config, translator=None):
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip", "config.ini")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory')}: {config_dir}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory') if translator else 'Config Directory'}: {config_dir}{Style.RESET_ALL}")
print()

386
delete_cursor_google.py Normal file
View File

@@ -0,0 +1,386 @@
from oauth_auth import OAuthHandler
import time
from colorama import Fore, Style, init
import sys
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'START': '🚀',
'DELETE': '🗑️',
'SUCCESS': '',
'ERROR': '',
'WAIT': '',
'INFO': '',
'WARNING': '⚠️'
}
class CursorGoogleAccountDeleter(OAuthHandler):
def __init__(self, translator=None):
super().__init__(translator, auth_type='google')
def delete_google_account(self):
"""Delete Cursor account using Google OAuth"""
try:
# Setup browser and select profile
if not self.setup_browser():
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.starting_process') if self.translator else 'Starting account deletion process...'}{Style.RESET_ALL}")
# Navigate to Cursor auth page - using the same URL as in registration
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(2)
# Click Google auth button using same selectors as in registration
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:
break
except:
continue
if not auth_btn:
raise Exception(self.translator.get('account_delete.google_button_not_found') if self.translator else "Google login button not found")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.logging_in') if self.translator else 'Logging in with Google...'}{Style.RESET_ALL}")
auth_btn.click()
# Wait for authentication to complete using a more robust method
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('account_delete.waiting_for_auth', fallback='Waiting for Google authentication...')}{Style.RESET_ALL}")
# Dynamic wait for authentication
max_wait_time = 120 # Increase maximum wait time to 120 seconds
start_time = time.time()
check_interval = 3 # Check every 3 seconds
google_account_alert_shown = False # Track if we've shown the alert already
while time.time() - start_time < max_wait_time:
current_url = self.browser.url
# If we're already on the settings or dashboard page, we're successful
if "/dashboard" in current_url or "/settings" in current_url or "cursor.com" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.login_successful') if self.translator else 'Login successful'}{Style.RESET_ALL}")
break
# If we're on Google accounts page or accounts.google.com, wait for user selection
if "accounts.google.com" in current_url:
# Only show the alert once to avoid spamming
if not google_account_alert_shown:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.select_google_account', fallback='Please select your Google account...')}{Style.RESET_ALL}")
# Alert to indicate user action needed
try:
self.browser.run_js("""
alert('Please select your Google account to continue with Cursor authentication');
""")
google_account_alert_shown = True # Mark that we've shown the alert
except:
pass # Alert is optional
# Sleep before checking again
time.sleep(check_interval)
else:
# If the loop completed without breaking, it means we hit the timeout
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.auth_timeout', fallback='Authentication timeout, continuing anyway...')}{Style.RESET_ALL}")
# Check current URL to determine next steps
current_url = self.browser.url
# If we're already on the settings page, no need to navigate
if "/settings" in current_url and "cursor.com" in current_url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.already_on_settings', fallback='Already on settings page')}{Style.RESET_ALL}")
# If we're on the dashboard or any Cursor page but not settings, navigate to settings
elif "cursor.com" in current_url or "authenticator.cursor.sh" in current_url:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.navigating_to_settings', fallback='Navigating to settings page...')}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
# If we're still on Google auth or somewhere else, try directly going to settings
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.login_redirect_failed', fallback='Login redirection failed, trying direct navigation...')}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
# Wait for the settings page to load
time.sleep(3) # Reduced from 5 seconds
# First look for the email element to confirm we're logged in
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']} {self.translator.get('account_delete.found_email', email=email, fallback=f'Found email: {email}')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.email_not_found', error=str(e), fallback=f'Email not found: {str(e)}')}{Style.RESET_ALL}")
# Click on "Advanced" tab or dropdown - keep only the successful approach
advanced_found = False
# Direct JavaScript querySelector approach that worked according to logs
try:
advanced_element_js = self.browser.run_js("""
// Try to find the Advanced dropdown using querySelector with the exact classes
let advancedElement = document.querySelector('div.mb-0.flex.cursor-pointer.items-center.text-xs:not([style*="display: none"])');
// If not found, try a more general approach
if (!advancedElement) {
const allDivs = document.querySelectorAll('div');
for (const div of allDivs) {
if (div.textContent.includes('Advanced') &&
div.className.includes('mb-0') &&
div.className.includes('flex') &&
div.className.includes('cursor-pointer')) {
advancedElement = div;
break;
}
}
}
// Click the element if found
if (advancedElement) {
advancedElement.click();
return true;
}
return false;
""")
if advanced_element_js:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.advanced_tab_clicked', fallback='Found and clicked Advanced using direct JavaScript selector')}{Style.RESET_ALL}")
advanced_found = True
time.sleep(1) # Reduced from 2 seconds
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.advanced_tab_error', error=str(e), fallback='JavaScript querySelector approach failed: {str(e)}')}{Style.RESET_ALL}")
if not advanced_found:
# Fallback to direct URL navigation which is faster and more reliable
try:
self.browser.get("https://www.cursor.com/settings?tab=advanced")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('account_delete.direct_advanced_navigation', fallback='Trying direct navigation to advanced tab')}{Style.RESET_ALL}")
advanced_found = True
except:
raise Exception(self.translator.get('account_delete.advanced_tab_not_found') if self.translator else "Advanced option not found after multiple attempts")
# Wait for dropdown/tab content to load
time.sleep(2) # Reduced from 4 seconds
# Find and click the "Delete Account" button
delete_button_found = False
# Simplified approach for delete button based on what worked
delete_button_selectors = [
'xpath://button[contains(., "Delete Account")]',
'xpath://button[text()="Delete Account"]',
'xpath://div[contains(text(), "Delete Account")]',
'xpath://button[contains(text(), "Delete") and contains(text(), "Account")]'
]
for selector in delete_button_selectors:
try:
delete_button = self.browser.ele(selector, timeout=2)
if delete_button:
delete_button.click()
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.delete_button_clicked') if self.translator else 'Clicked on Delete Account button'}{Style.RESET_ALL}")
delete_button_found = True
break
except:
continue
if not delete_button_found:
raise Exception(self.translator.get('account_delete.delete_button_not_found') if self.translator else "Delete Account button not found")
# Wait for confirmation dialog to appear
time.sleep(2)
# Check if we need to input "Delete" at all - some modals might not require it
input_required = True
try:
# Try detecting if the DELETE button is already enabled
delete_button_enabled = self.browser.run_js("""
const buttons = Array.from(document.querySelectorAll('button'));
const deleteButtons = buttons.filter(btn =>
btn.textContent.trim() === 'DELETE' ||
btn.textContent.trim() === 'Delete'
);
if (deleteButtons.length > 0) {
return !deleteButtons.some(btn => btn.disabled);
}
return false;
""")
if delete_button_enabled:
print(f"{Fore.CYAN}{EMOJI['INFO']} DELETE button appears to be enabled already. Input may not be required.{Style.RESET_ALL}")
input_required = False
except:
pass
# Type "Delete" in the confirmation input - only if required
delete_input_found = False
if input_required:
# Try common selectors for the input field
delete_input_selectors = [
'xpath://input[@placeholder="Delete"]',
'xpath://div[contains(@class, "modal")]//input',
'xpath://input',
'css:input'
]
for selector in delete_input_selectors:
try:
delete_input = self.browser.ele(selector, timeout=3)
if delete_input:
delete_input.clear()
delete_input.input("Delete")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete', fallback='Typed \"Delete\" in confirmation box')}{Style.RESET_ALL}")
delete_input_found = True
time.sleep(2)
break
except:
# Try direct JavaScript input as fallback
try:
self.browser.run_js(f"""
arguments[0].value = "Delete";
const event = new Event('input', {{ bubbles: true }});
arguments[0].dispatchEvent(event);
const changeEvent = new Event('change', {{ bubbles: true }});
arguments[0].dispatchEvent(changeEvent);
""", delete_input)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete_js', fallback='Typed \"Delete\" using JavaScript')}{Style.RESET_ALL}")
delete_input_found = True
time.sleep(2)
break
except:
continue
if not delete_input_found:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.delete_input_not_found', fallback='Delete confirmation input not found, continuing anyway')}{Style.RESET_ALL}")
time.sleep(2)
# Wait before clicking the final DELETE button
time.sleep(2)
# Click on the final DELETE button
confirm_button_found = False
# Use JavaScript approach for the DELETE button
try:
delete_button_js = self.browser.run_js("""
// Try to find the DELETE button by exact text content
const buttons = Array.from(document.querySelectorAll('button'));
const deleteButton = buttons.find(btn =>
btn.textContent.trim() === 'DELETE' ||
btn.textContent.trim() === 'Delete'
);
if (deleteButton) {
console.log("Found DELETE button with JavaScript");
deleteButton.click();
return true;
}
// If not found by text, try to find right-most button in the modal
const modalButtons = Array.from(document.querySelectorAll('.relative button, [role="dialog"] button, .modal button, [aria-modal="true"] button'));
if (modalButtons.length > 1) {
modalButtons.sort((a, b) => {
const rectA = a.getBoundingClientRect();
const rectB = b.getBoundingClientRect();
return rectB.right - rectA.right;
});
console.log("Clicking right-most button in modal");
modalButtons[0].click();
return true;
} else if (modalButtons.length === 1) {
console.log("Clicking single button found in modal");
modalButtons[0].click();
return true;
}
return false;
""")
if delete_button_js:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Clicked DELETE button')}{Style.RESET_ALL}")
confirm_button_found = True
except:
pass
if not confirm_button_found:
# Fallback to simple selectors
delete_button_selectors = [
'xpath://button[text()="DELETE"]',
'xpath://div[contains(@class, "modal")]//button[last()]'
]
for selector in delete_button_selectors:
try:
delete_button = self.browser.ele(selector, timeout=2)
if delete_button:
delete_button.click()
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Account deleted successfully!')}{Style.RESET_ALL}")
confirm_button_found = True
break
except:
continue
if not confirm_button_found:
raise Exception(self.translator.get('account_delete.confirm_button_not_found') if self.translator else "Confirm button not found")
# Wait a moment to see the confirmation
time.sleep(2)
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('account_delete.error', error=str(e)) if self.translator else f'Error during account deletion: {str(e)}'}{Style.RESET_ALL}")
return False
finally:
# Clean up browser
if self.browser:
try:
self.browser.quit()
except:
pass
def main(translator=None):
"""Main function to handle Google account deletion"""
print(f"\n{Fore.CYAN}{EMOJI['START']} {translator.get('account_delete.title') if translator else 'Cursor Google Account Deletion Tool'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{'' * 50}{Style.RESET_ALL}")
deleter = CursorGoogleAccountDeleter(translator)
try:
# Ask for confirmation
print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}")
confirm = input(f"{Fore.RED} {translator.get('account_delete.confirm_prompt') if translator else 'Are you sure you want to proceed? (y/N): '}{Style.RESET_ALL}").lower()
if confirm != 'y':
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}")
return
success = deleter.delete_google_account()
if success:
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('account_delete.success') if translator else 'Your Cursor account has been successfully deleted!'}{Style.RESET_ALL}")
else:
print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.failed') if translator else 'Account deletion process failed or was cancelled.'}{Style.RESET_ALL}")
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.interrupted') if translator else 'Account deletion process interrupted by user.'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
finally:
print(f"{Fore.YELLOW}{'' * 50}{Style.RESET_ALL}")
if __name__ == "__main__":
main()

View File

@@ -34,12 +34,15 @@ class AutoUpdateDisabler:
if self.system == "Windows":
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"))
self.product_json_path = config.get('WindowsPaths', 'product_json_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"))
elif self.system == "Darwin":
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml")
self.product_json_path = config.get('MacPaths', 'product_json_path', fallback="/Applications/Cursor.app/Contents/Resources/app/product.json")
elif self.system == "Linux":
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml"))
self.product_json_path = config.get('LinuxPaths', 'product_json_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/product.json"))
else:
# If configuration loading fails, use default paths
self.updater_paths = {
@@ -56,21 +59,29 @@ class AutoUpdateDisabler:
}
self.update_yml_path = self.update_yml_paths.get(self.system)
def _change_main_js(self):
"""Change main.js"""
self.product_json_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"),
"Darwin": "/Applications/Cursor.app/Contents/Resources/app/product.json",
"Linux": os.path.expanduser("~/.config/cursor/resources/app/product.json")
}
self.product_json_path = self.product_json_paths.get(self.system)
def _remove_update_url(self):
"""Remove update URL"""
try:
main_path = get_config(self.translator).get('main_js_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/main.js"))
original_stat = os.stat(main_path)
original_stat = os.stat(self.product_json_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()
with open(self.product_json_path, "r", encoding="utf-8") as product_json_file:
content = product_json_file.read()
patterns = {
r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"",
r"https://api2.cursor.sh/updates": r"",
r"http://cursorapi.com/updates": r"",
}
for pattern, replacement in patterns.items():
@@ -79,12 +90,12 @@ class AutoUpdateDisabler:
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(main_path, main_path + ".old")
shutil.move(tmp_path, main_path)
shutil.copy2(self.product_json_path, self.product_json_path + ".old")
shutil.move(tmp_path, self.product_json_path)
os.chmod(main_path, original_mode)
os.chmod(self.product_json_path, original_mode)
if os.name != "nt":
os.chown(main_path, original_uid, original_gid)
os.chown(self.product_json_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
@@ -122,17 +133,18 @@ class AutoUpdateDisabler:
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}")
try:
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}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.directory_locked', path=updater_path) if self.translator else f'更新程序目录已被锁定,跳过删除: {updater_path}'}{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}")
# 即使删除失败,也返回 True继续执行下一步
return True
def _clear_update_yml_file(self):
@@ -145,15 +157,15 @@ class AutoUpdateDisabler:
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}")
if os.path.exists(update_yml_path):
# 清空文件内容
with open(update_yml_path, 'w') as f:
f.write('')
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}")
return True
try:
with open(update_yml_path, 'w') as f:
f.write('')
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已被锁定,跳过清空'}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}")
return True
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.clear_update_yml_failed', error=str(e)) if self.translator else f'清空更新配置文件失败: {e}'}{Style.RESET_ALL}")
@@ -170,37 +182,43 @@ class AutoUpdateDisabler:
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
# 创建 updater_path 阻止文件
os.makedirs(os.path.dirname(updater_path), exist_ok=True)
open(updater_path, 'w').close()
# 设置 updater_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{updater_path}"')
else:
os.chmod(updater_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}")
try:
os.makedirs(os.path.dirname(updater_path), exist_ok=True)
open(updater_path, 'w').close()
# 设置 updater_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{updater_path}"')
else:
os.chmod(updater_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.block_file_locked') if self.translator else '阻止文件已被锁定,跳过创建'}{Style.RESET_ALL}")
# 检查 update_yml_path
update_yml_path = self.update_yml_path
if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)):
# 创建 update_yml_path 阻止文件
with open(update_yml_path, 'w') as f:
f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n')
# 设置 update_yml_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{update_yml_path}"')
else:
os.chmod(update_yml_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}")
try:
# 创建 update_yml_path 阻止文件
with open(update_yml_path, 'w') as f:
f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n')
# 设置 update_yml_path 为只读
if self.system == "Windows":
os.system(f'attrib +r "{update_yml_path}"')
else:
os.chmod(update_yml_path, 0o444) # 设置为只读
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_already_locked') 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
return True # 返回 True 以继续执行后续步骤
def disable_auto_update(self):
"""Disable auto update"""
@@ -222,8 +240,8 @@ class AutoUpdateDisabler:
if not self._create_blocking_file():
return False
# 5. Change main.js
if not self._change_main_js():
# 5. Remove update URL from product.json
if not self._remove_update_url():
return False
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -388,5 +388,17 @@
"electron_localstorage_files_removed": "Electron localStorage файлове бяха премахнати",
"electron_localstorage_files_removal_error": "Грешка при премахване на Electron localStorage файлове: {error}",
"removing_electron_localstorage_files_completed": "Премахването на Electron localStorage файлове беше завършено"
},
"chrome_profile": {
"title": "Избор на Chrome Профил",
"select_profile": "Изберете Chrome профил за използване:",
"profile_list": "Налични профили:",
"default_profile": "Профил по Подразбиране",
"profile": "Профил {number}",
"no_profiles": "Не са намерени Chrome профили",
"error_loading": "Грешка при зареждане на Chrome профили: {error}",
"profile_selected": "Избран профил: {profile}",
"invalid_selection": "Невалиден избор. Моля, опитайте отново",
"warning_chrome_close": "Предупреждение: Това ще затвори всички работещи Chrome процеси"
}
}

View File

@@ -6,10 +6,11 @@
"register": "Neues Cursor-Konto Registrieren",
"register_google": "Mit Google-Konto Registrieren",
"register_github": "Mit GitHub-Konto Registrieren",
"register_manual": "Cursor mit Benutzerdefinierter E-Mail Registrieren",
"register_manual": "Cursor Mit Benutzerdefinierter E-Mail Registrieren",
"quit": "Cursor-Anwendung Schließen",
"select_language": "Sprache Ändern",
"input_choice": "Bitte Auswahl eingeben ({choices})",
"select_chrome_profile": "Chrome-Profil Auswählen",
"input_choice": "Bitte geben Sie Ihre Auswahl ein ({choices})",
"invalid_choice": "Ungültige Auswahl. Bitte eine Nummer von {choices} eingeben",
"program_terminated": "Programm wurde vom Benutzer beendet",
"error_occurred": "Ein Fehler ist aufgetreten: {error}. Bitte erneut versuchen",
@@ -376,5 +377,17 @@
"electron_localstorage_files_removed": "Electron localStorage-Dateien entfernt",
"electron_localstorage_files_removal_error": "Fehler beim Entfernen von Electron localStorage-Dateien: {error}",
"removing_electron_localstorage_files_completed": "Entfernen von Electron localStorage-Dateien abgeschlossen"
},
"chrome_profile": {
"title": "Chrome-Profil Auswahl",
"select_profile": "Wählen Sie ein Chrome-Profil zum Verwenden:",
"profile_list": "Verfügbare Profile:",
"default_profile": "Standard-Profil",
"profile": "Profil {number}",
"no_profiles": "Keine Chrome-Profile gefunden",
"error_loading": "Fehler beim Laden der Chrome-Profile: {error}",
"profile_selected": "Ausgewähltes Profil: {profile}",
"invalid_selection": "Ungültige Auswahl. Bitte versuchen Sie es erneut",
"warning_chrome_close": "Warnung: Dies wird alle laufenden Chrome-Prozesse beenden"
}
}

View File

@@ -9,6 +9,7 @@
"register_manual": "Register Cursor with Custom Email",
"quit": "Close Cursor Application",
"select_language": "Change Language",
"select_chrome_profile": "Select Chrome Profile",
"input_choice": "Please enter your choice ({choices})",
"invalid_choice": "Invalid selection. Please enter a number from {choices}",
"program_terminated": "Program was terminated by user",
@@ -24,7 +25,11 @@
"coming_soon": "Coming Soon",
"fixed_soon": "Fixed Soon",
"contribute": "Contribute to the Project",
"config": "Show Config"
"config": "Show Config",
"delete_google_account": "Delete Cursor Google Account",
"continue_prompt": "Continue? (y/N): ",
"operation_cancelled_by_user": "Operation cancelled by user",
"exiting": "Exiting ……"
},
"languages": {
"en": "English",
@@ -280,7 +285,13 @@
"available_domains_loaded": "Available Domains Loaded: {count}",
"domains_filtered": "Domains Filtered: {count}",
"trying_to_create_email": "Trying to create email: {email}",
"domain_blocked": "Domain Blocked: {domain}"
"domain_blocked": "Domain Blocked: {domain}",
"using_chrome_profile": "Using Chrome profile from: {user_data_dir}",
"no_display_found": "No display found. Make sure X server is running.",
"try_export_display": "Try: export DISPLAY=:0",
"extension_load_error": "Extension Load Error: {error}",
"make_sure_chrome_chromium_is_properly_installed": "Make sure Chrome/Chromium is properly installed",
"try_install_chromium": "Try: sudo apt install chromium-browser"
},
"update": {
"title": "Disable Cursor Auto Update",
@@ -300,7 +311,16 @@
"clear_update_yml_failed": "Failed to clear update.yml file: {error}",
"unsupported_os": "Unsupported OS: {system}",
"remove_directory_failed": "Failed to remove directory: {error}",
"create_block_file_failed": "Failed to create block file: {error}"
"create_block_file_failed": "Failed to create block file: {error}",
"directory_locked": "Directory is locked: {path}",
"yml_locked": "update.yml file is locked",
"block_file_locked": "block file is locked",
"yml_already_locked": "update.yml file is already locked",
"block_file_already_locked": "block file is already locked",
"block_file_locked_error": "Block file locked error: {error}",
"yml_locked_error": "update.yml file locked error: {error}",
"block_file_already_locked_error": "Block file already locked error: {error}",
"yml_already_locked_error": "update.yml file already locked error: {error}"
},
"updater": {
"checking": "Checking for updates...",
@@ -426,7 +446,24 @@
"cursor_reset_completed": "Cursor AI Editor has been fully reset and trial detection bypassed!",
"cursor_reset_failed": "Cursor AI Editor reset failed: {error}",
"cursor_reset_cancelled": "Cursor AI Editor reset cancelled. Exiting without making any changes.",
"operation_cancelled": "Operation cancelled. Exiting without making any changes."
"operation_cancelled": "Operation cancelled. Exiting without making any changes.",
"navigating_to_settings": "Navigating to settings page...",
"already_on_settings": "Already on settings page",
"login_redirect_failed": "Login redirection failed, trying direct navigation...",
"advanced_tab_not_found": "Advanced tab not found after multiple attempts",
"advanced_tab_retry": "Advanced tab not found, attempt {attempt}/{max_attempts}",
"advanced_tab_error": "Error finding Advanced tab: {error}",
"advanced_tab_clicked": "Clicked on Advanced tab",
"direct_advanced_navigation": "Trying direct navigation to advanced tab",
"delete_button_not_found": "Delete Account button not found after multiple attempts",
"delete_button_retry": "Delete button not found, attempt {attempt}/{max_attempts}",
"delete_button_error": "Error finding Delete button: {error}",
"delete_button_clicked": "Clicked on Delete Account button",
"found_danger_zone": "Found Danger Zone section",
"delete_input_not_found": "Delete confirmation input not found after multiple attempts",
"delete_input_retry": "Delete input not found, attempt {attempt}/{max_attempts}",
"delete_input_error": "Error finding Delete input: {error}",
"delete_input_not_found_continuing": "Delete confirmation input not found, trying to continue anyway"
},
"github_register": {
"title": "GitHub + Cursor AI Registration Automation",
@@ -493,6 +530,144 @@
"configuration": "Configuration",
"enabled": "Enabled",
"disabled": "Disabled",
"config_directory": "Config Directory"
"config_directory": "Config Directory",
"neither_cursor_nor_cursor_directory_found": "Neither Cursor nor Cursor directory found in {config_base}",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "Please make sure Cursor is installed and has been run at least once",
"storage_directory_not_found": "Storage directory not found: {storage_dir}",
"storage_file_found": "Storage file found: {storage_path}",
"file_size": "File size: {size} bytes",
"file_permissions": "File permissions: {permissions}",
"file_owner": "File owner: {owner}",
"file_group": "File group: {group}",
"error_getting_file_stats": "Error getting file stats: {error}",
"permission_denied": "Permission denied: {storage_path}",
"try_running": "Try running: {command}",
"and": "And",
"storage_file_is_empty": "Storage file is empty: {storage_path}",
"the_file_might_be_corrupted_please_reinstall_cursor": "The file might be corrupted, please reinstall Cursor",
"storage_file_not_found": "Storage file not found: {storage_path}",
"error_checking_linux_paths": "Error checking Linux paths: {error}",
"config_option_added": "Config option added: {option}",
"config_updated": "Config updated",
"config_created": "Config created: {config_file}",
"config_setup_error": "Error setting up config: {error}",
"storage_file_is_valid_and_contains_data": "Storage file is valid and contains data",
"error_reading_storage_file": "Error reading storage file: {error}",
"also_checked": "Also checked {path}"
},
"oauth": {
"authentication_button_not_found": "Authentication button not found",
"authentication_failed": "Authentication failed: {error}",
"found_cookies": "Found {count} cookies",
"token_extraction_error": "Token extraction error: {error}",
"authentication_successful": "Authentication successful - Email: {email}",
"missing_authentication_data": "Missing authentication data: {data}",
"failed_to_delete_account": "Failed to delete account: {error}",
"invalid_authentication_type": "Invalid authentication type",
"auth_update_success": "Auth update success",
"browser_closed": "Browser closed",
"auth_update_failed": "Auth update failed",
"google_start": "Google start",
"github_start": "Github start",
"usage_count": "Usage count: {usage}",
"account_has_reached_maximum_usage": "Account has reached maximum usage, {deleting}",
"starting_new_authentication_process": "Starting new authentication process...",
"failed_to_delete_expired_account": "Failed to delete expired account",
"could_not_check_usage_count": "Could not check usage count: {error}",
"found_email": "Found email: {email}",
"could_not_find_email": "Could not find email: {error}",
"could_not_find_usage_count": "Could not find usage count: {error}",
"already_on_settings_page": "Already on settings page!",
"failed_to_extract_auth_info": "Failed to extract auth info: {error}",
"no_chrome_profiles_found": "No Chrome profiles found, using Default",
"found_default_chrome_profile": "Found Default Chrome profile",
"using_first_available_chrome_profile": "Using first available Chrome profile: {profile}",
"error_finding_chrome_profile": "Error finding Chrome profile, using Default: {error}",
"initializing_browser_setup": "Initializing browser setup...",
"detected_platform": "Detected platform: {platform}",
"running_as_root_warning": "Running as root is not recommended for browser automation",
"consider_running_without_sudo": "Consider running the script without sudo",
"no_compatible_browser_found": "No compatible browser found. Please install Google Chrome or Chromium.",
"supported_browsers": "Supported browsers for {platform}",
"using_browser_profile": "Using browser profile: {profile}",
"starting_browser": "Starting browser at: {path}",
"browser_setup_completed": "Browser setup completed successfully",
"browser_setup_failed": "Browser setup failed: {error}",
"try_running_without_sudo_admin": "Try running without sudo/administrator privileges",
"redirecting_to_authenticator_cursor_sh": "Redirecting to authenticator.cursor.sh...",
"starting_google_authentication": "Starting Google authentication...",
"starting_github_authentication": "Starting GitHub authentication...",
"waiting_for_authentication": "Waiting for authentication...",
"page_changed_checking_auth": "Page changed, checking auth...",
"status_check_error": "Status check error: {error}",
"authentication_timeout": "Authentication timeout",
"account_is_still_valid": "Account is still valid (Usage: {usage})",
"starting_re_authentication_process": "Starting re-authentication process...",
"starting_new_google_authentication": "Starting new Google authentication...",
"failed_to_delete_account_or_re_authenticate": "Failed to delete account or re-authenticate: {error}",
"navigating_to_authentication_page": "Navigating to authentication page...",
"please_select_your_google_account_to_continue": "Please select your Google account to continue...",
"found_browser_data_directory": "Found browser data directory: {path}",
"authentication_successful_getting_account_info": "Authentication successful, getting account info...",
"warning_could_not_kill_existing_browser_processes": "Warning: Could not kill existing browser processes: {error}",
"browser_failed_to_start": "Browser failed to start: {error}",
"browser_failed": "Browser failed to start: {error}",
"browser_failed_to_start_fallback": "Browser failed to start: {error}"
},
"chrome_profile": {
"title": "Chrome Profile Selection",
"select_profile": "Select a Chrome profile to use:",
"profile_list": "Available profiles:",
"default_profile": "Default Profile",
"profile": "Profile {number}",
"no_profiles": "No Chrome profiles found",
"error_loading": "Error loading Chrome profiles: {error}",
"profile_selected": "Selected profile: {profile}",
"invalid_selection": "Invalid selection. Please try again",
"warning_chrome_close": "Warning: This will close all running Chrome processes"
},
"account_delete": {
"title": "Cursor Google Account Deletion Tool",
"warning": "WARNING: This will permanently delete your Cursor account. This action cannot be undone.",
"cancelled": "Account deletion cancelled.",
"starting_process": "Starting account deletion process...",
"google_button_not_found": "Google login button not found",
"logging_in": "Logging in with Google...",
"waiting_for_auth": "Waiting for Google authentication...",
"login_successful": "Login successful",
"unexpected_page": "Unexpected page after login: {url}",
"trying_settings": "Trying to navigate to settings page...",
"select_google_account": "Please select your Google account...",
"auth_timeout": "Authentication timeout, continuing anyway...",
"navigating_to_settings": "Navigating to settings page...",
"already_on_settings": "Already on settings page",
"login_redirect_failed": "Login redirection failed, trying direct navigation...",
"advanced_tab_not_found": "Advanced tab not found after multiple attempts",
"advanced_tab_retry": "Advanced tab not found, attempt {attempt}/{max_attempts}",
"advanced_tab_error": "Error finding Advanced tab: {error}",
"advanced_tab_clicked": "Clicked on Advanced tab",
"direct_advanced_navigation": "Trying direct navigation to advanced tab",
"delete_button_not_found": "Delete Account button not found after multiple attempts",
"delete_button_retry": "Delete button not found, attempt {attempt}/{max_attempts}",
"delete_button_error": "Error finding Delete button: {error}",
"delete_button_clicked": "Clicked on Delete Account button",
"found_danger_zone": "Found Danger Zone section",
"delete_input_not_found": "Delete confirmation input not found after multiple attempts",
"delete_input_retry": "Delete input not found, attempt {attempt}/{max_attempts}",
"delete_input_error": "Error finding Delete input: {error}",
"delete_input_not_found_continuing": "Delete confirmation input not found, trying to continue anyway",
"typed_delete": "Typed \"Delete\" in confirmation box",
"confirm_button_not_found": "Confirm button not found after multiple attempts",
"confirm_button_retry": "Confirm button not found, attempt {attempt}/{max_attempts}",
"confirm_button_error": "Error finding Confirm button: {error}",
"account_deleted": "Account deleted successfully!",
"error": "Error during account deletion: {error}",
"success": "Your Cursor account has been successfully deleted!",
"failed": "Account deletion process failed or was cancelled.",
"interrupted": "Account deletion process interrupted by user.",
"unexpected_error": "Unexpected error: {error}",
"found_email": "Found email: {email}",
"email_not_found": "Email not found: {error}",
"confirm_prompt": "Are you sure you want to proceed? (y/N): "
}
}

View File

@@ -439,5 +439,17 @@
"completed_successfully": "¡Registro de GitHub + Cursor completado exitosamente!",
"registration_encountered_issues": "El registro de GitHub + Cursor encontró problemas.",
"check_browser_windows_for_manual_intervention_or_try_again_later": "Revise las ventanas del navegador para intervención manual o intente nuevamente más tarde."
}
},
"chrome_profile": {
"title": "Selección de Perfil de Chrome",
"select_profile": "Seleccione un perfil de Chrome para usar:",
"profile_list": "Perfiles disponibles:",
"default_profile": "Perfil Predeterminado",
"profile": "Perfil {number}",
"no_profiles": "No se encontraron perfiles de Chrome",
"error_loading": "Error al cargar perfiles de Chrome: {error}",
"profile_selected": "Perfil seleccionado: {profile}",
"invalid_selection": "Selección inválida. Por favor, intente de nuevo",
"warning_chrome_close": "Advertencia: Esto cerrará todos los procesos de Chrome en ejecución"
}
}

View File

@@ -374,5 +374,17 @@
"electron_localstorage_files_removed": "Fichiers localStorage Electron supprimés",
"electron_localstorage_files_removal_error": "Erreur de suppression des fichiers localStorage Electron: {error}",
"removing_electron_localstorage_files_completed": "Suppression des fichiers localStorage Electron terminée"
},
"chrome_profile": {
"title": "Sélection du Profil Chrome",
"select_profile": "Sélectionnez un profil Chrome à utiliser :",
"profile_list": "Profils disponibles :",
"default_profile": "Profil par Défaut",
"profile": "Profil {number}",
"no_profiles": "Aucun profil Chrome trouvé",
"error_loading": "Erreur lors du chargement des profils Chrome : {error}",
"profile_selected": "Profil sélectionné : {profile}",
"invalid_selection": "Sélection invalide. Veuillez réessayer",
"warning_chrome_close": "Attention : Cela fermera tous les processus Chrome en cours d'exécution"
}
}

View File

@@ -1,15 +1,16 @@
{
"menu": {
"title": "Beschikbare opties",
"exit": "Programma afsluiten",
"reset": "Machine-ID resetten",
"register": "Nieuw Cursor-account registreren",
"register_google": "Registreren met Google-account",
"register_github": "Registreren met GitHub-account",
"register_manual": "Cursor registreren met aangepast e-mailadres",
"quit": "Cursor-applicatie sluiten",
"select_language": "Taal wijzigen",
"input_choice": "Voer uw keuze in: {choices}",
"title": "Beschikbare Opties",
"exit": "Programma Afsluiten",
"reset": "Machine ID Resetten",
"register": "Nieuw Cursor Account Registreren",
"register_google": "Registreren met Google Account",
"register_github": "Registreren met GitHub Account",
"register_manual": "Cursor Registreren met Aangepaste E-mail",
"quit": "Cursor Toepassing Sluiten",
"select_language": "Taal Wijzigen",
"select_chrome_profile": "Chrome Profiel Selecteren",
"input_choice": "Voer uw keuze in ({choices})",
"invalid_choice": "Ongeldige selectie. Voer een nummer in uit {choices}.",
"program_terminated": "Programma is beëindigd door de gebruiker",
"error_occurred": "Er is een fout opgetreden: {error}. Probeer het opnieuw.",
@@ -374,5 +375,17 @@
"electron_localstorage_files_removed": "Electron localStorage-bestanden verwijderd",
"electron_localstorage_files_removal_error": "Fout bij het verwijderen van Electron localStorage-bestanden: {error}",
"removing_electron_localstorage_files_completed": "Electron localStorage-bestanden verwijderd"
},
"chrome_profile": {
"title": "Chrome Profiel Selectie",
"select_profile": "Selecteer een Chrome profiel om te gebruiken:",
"profile_list": "Beschikbare profielen:",
"default_profile": "Standaard Profiel",
"profile": "Profiel {number}",
"no_profiles": "Geen Chrome profielen gevonden",
"error_loading": "Fout bij laden van Chrome profielen: {error}",
"profile_selected": "Geselecteerd profiel: {profile}",
"invalid_selection": "Ongeldige selectie. Probeer het opnieuw",
"warning_chrome_close": "Waarschuwing: Dit zal alle actieve Chrome processen sluiten"
}
}

View File

@@ -383,5 +383,17 @@
"electron_localstorage_files_removed": "Arquivos localStorage do Electron removidos",
"electron_localstorage_files_removal_error": "Erro ao remover arquivos localStorage do Electron: {error}",
"removing_electron_localstorage_files_completed": "Remoção dos arquivos localStorage do Electron concluída"
},
"chrome_profile": {
"title": "Seleção de Perfil do Chrome",
"select_profile": "Selecione um perfil do Chrome para usar:",
"profile_list": "Perfis disponíveis:",
"default_profile": "Perfil Padrão",
"profile": "Perfil {number}",
"no_profiles": "Nenhum perfil do Chrome encontrado",
"error_loading": "Erro ao carregar perfis do Chrome: {error}",
"profile_selected": "Perfil selecionado: {profile}",
"invalid_selection": "Seleção inválida. Por favor, tente novamente",
"warning_chrome_close": "Aviso: Isso fechará todos os processos do Chrome em execução"
}
}

View File

@@ -383,5 +383,17 @@
"electron_localstorage_files_removed": "Файлы localStorage Electron удалены",
"electron_localstorage_files_removal_error": "Ошибка удаления файлов localStorage Electron: {error}",
"removing_electron_localstorage_files_completed": "Удаление файлов localStorage Electron завершено"
},
"chrome_profile": {
"title": "Выбор Профиля Chrome",
"select_profile": "Выберите профиль Chrome для использования:",
"profile_list": "Доступные профили:",
"default_profile": "Профиль по умолчанию",
"profile": "Профиль {number}",
"no_profiles": "Профили Chrome не найдены",
"error_loading": "Ошибка загрузки профилей Chrome: {error}",
"profile_selected": "Выбран профиль: {profile}",
"invalid_selection": "Неверный выбор. Пожалуйста, попробуйте снова",
"warning_chrome_close": "Предупреждение: Это закроет все запущенные процессы Chrome"
}
}

View File

@@ -386,5 +386,17 @@
"electron_localstorage_files_removed": "Electron localStorage dosyaları kaldırıldı",
"electron_localstorage_files_removal_error": "Electron localStorage dosyaları kaldırılırken hata: {error}",
"removing_electron_localstorage_files_completed": "Electron localStorage dosyaları kaldırma işlemi tamamlandı"
},
"chrome_profile": {
"title": "Chrome Profil Seçimi",
"select_profile": "Kullanılacak Chrome profilini seçin:",
"profile_list": "Mevcut profiller:",
"default_profile": "Varsayılan Profil",
"profile": "Profil {number}",
"no_profiles": "Chrome profili bulunamadı",
"error_loading": "Chrome profilleri yüklenirken hata: {error}",
"profile_selected": "Seçilen profil: {profile}",
"invalid_selection": "Geçersiz seçim. Lütfen tekrar deneyin",
"warning_chrome_close": "Uyarı: Bu işlem tüm çalışan Chrome işlemlerini kapatacaktır"
}
}

View File

@@ -1,12 +1,15 @@
{
"menu": {
"title": "Các Tùy Chọn Khả Dụng",
"title": "Các Tùy Chọn",
"exit": "Thoát Chương Trình",
"reset": "Đặt Lại ID Máy",
"register": "Đăng Ký Tài Khoản Cursor Mới",
"register_google": "Đăng Ký Bằng Tài Khoản Google",
"register_github": "Đăng Ký Bằng Tài Khoản GitHub",
"register_manual": "Đăng Ký Cursor Với Email Tùy Chỉnh",
"quit": "Đóng Ứng Dụng Cursor",
"select_language": "Thay Đổi Ngôn Ngữ",
"select_chrome_profile": "Chọn Hồ Sơ Chrome",
"input_choice": "Vui lòng nhập lựa chọn của bạn ({choices})",
"invalid_choice": "Lựa chọn không hợp lệ. Vui lòng nhập một số từ {choices}",
"program_terminated": "Chương trình đã bị người dùng chấm dứt",
@@ -381,5 +384,17 @@
"electron_localstorage_files_removed": "Đã xóa các tệp Electron localStorage",
"electron_localstorage_files_removal_error": "Lỗi khi xóa các tệp Electron localStorage: {error}",
"removing_electron_localstorage_files_completed": "Đã hoàn tất việc xóa các tệp Electron localStorage"
},
"chrome_profile": {
"title": "Chọn Hồ Sơ Chrome",
"select_profile": "Chọn hồ sơ Chrome để sử dụng:",
"profile_list": "Các hồ sơ có sẵn:",
"default_profile": "Hồ Sơ Mặc Định",
"profile": "Hồ Sơ {number}",
"no_profiles": "Không tìm thấy hồ sơ Chrome",
"error_loading": "Lỗi khi tải hồ sơ Chrome: {error}",
"profile_selected": "Đã chọn hồ sơ: {profile}",
"invalid_selection": "Lựa chọn không hợp lệ. Vui lòng thử lại",
"warning_chrome_close": "Cảnh báo: Điều này sẽ đóng tất cả các tiến trình Chrome đang chạy"
}
}

View File

@@ -2,13 +2,14 @@
"menu": {
"title": "可用选项",
"exit": "退出程序",
"reset": "重置机器标识",
"register": "注册新 Cursor 账号",
"register_google": "使用 Google 账号注册",
"register_github": "使用 GitHub 账号注册",
"register_manual": "使用自定义邮箱注册",
"quit": "关闭 Cursor 应用",
"reset": "重置机器ID",
"register": "注册新Cursor账户",
"register_google": "使用Google账户注册",
"register_github": "使用GitHub账户注册",
"register_manual": "使用自定义邮箱注册Cursor",
"quit": "关闭Cursor应用",
"select_language": "更改语言",
"select_chrome_profile": "选择Chrome配置文件",
"input_choice": "请输入您的选择 ({choices})",
"invalid_choice": "选择无效,请输入 {choices} 范围内的数字",
"program_terminated": "程序已被用户终止",
@@ -24,7 +25,11 @@
"coming_soon": "即将推出",
"fixed_soon": "即将修复",
"contribute": "贡献项目",
"config": "显示配置"
"config": "显示配置",
"delete_google_account": "删除 Cursor Google 账号",
"continue_prompt": "继续?(y/N): ",
"operation_cancelled_by_user": "操作被用户取消",
"exiting": "退出中 ……"
},
"languages": {
"en": "英语",
@@ -275,7 +280,13 @@
"available_domains_loaded": "获取到 {count} 个可用域名",
"domains_filtered": "过滤后剩餘 {count} 個可用域名",
"trying_to_create_email": "尝试创建邮箱: {email}",
"domain_blocked": "域名被屏蔽: {domain}"
"domain_blocked": "域名被屏蔽: {domain}",
"using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}",
"no_display_found": "未找到显示器。确保 X 服务器正在运行。",
"try_export_display": "尝试: export DISPLAY=:0",
"extension_load_error": "加载插件失败: {error}",
"make_sure_chrome_chromium_is_properly_installed": "确保 Chrome/Chromium 已正确安装",
"try_install_chromium": "尝试: sudo apt install chromium-browser"
},
"update": {
"title": "禁用 Cursor 自动更新",
@@ -295,7 +306,16 @@
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
"unsupported_os": "不支持的操作系统: {system}",
"remove_directory_failed": "删除目录失败: {error}",
"create_block_file_failed": "创建阻止文件失败: {error}"
"create_block_file_failed": "创建阻止文件失败: {error}",
"directory_locked": "目录被锁定: {path}",
"yml_locked": "update.yml 文件被锁定",
"block_file_locked": "阻止文件被锁定",
"yml_already_locked": "update.yml 文件已锁定",
"block_file_already_locked": "阻止文件已锁定",
"block_file_locked_error": "阻止文件锁定错误: {error}",
"yml_locked_error": "update.yml 文件锁定错误: {error}",
"block_file_already_locked_error": "阻止文件已锁定错误: {error}",
"yml_already_locked_error": "update.yml 文件已锁定错误: {error}"
},
"updater": {
"checking": "检查更新...",
@@ -488,6 +508,144 @@
"configuration": "配置",
"enabled": "已启用",
"disabled": "已禁用",
"config_directory": "配置目录"
"config_directory": "配置目录",
"neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目录",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "请确保 Cursor 已安装并至少运行一次",
"storage_directory_not_found": "未找到存储目录",
"storage_file_found": "找到存储文件",
"file_size": "文件大小",
"file_permissions": "文件权限",
"file_owner": "文件所有者",
"file_group": "文件组",
"error_getting_file_stats": "获取文件统计信息时出错",
"permission_denied": "权限拒绝",
"try_running": "尝试运行: {command}",
"and": "和",
"storage_file_is_empty": "存储文件为空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已损坏,请重新安装 Cursor",
"storage_file_not_found": "未找到存储文件",
"error_checking_linux_paths": "检查 Linux 路径时出错",
"config_option_added": "添加配置选项",
"config_updated": "配置更新",
"config_created": "配置已创建",
"config_setup_error": "配置设置错误",
"storage_file_is_valid_and_contains_data": "存储文件有效且包含数据",
"error_reading_storage_file": "读取存储文件时出错",
"also_checked": "也检查了 {path}"
},
"oauth": {
"authentication_button_not_found": "未找到认证按钮",
"authentication_failed": "认证失败: {error}",
"found_cookies": "找到 {count} 个 Cookie",
"token_extraction_error": "Token 提取错误: {error}",
"authentication_successful": "认证成功 - 邮箱: {email}",
"missing_authentication_data": "缺少认证数据: {data}",
"failed_to_delete_account": "删除账户失败: {error}",
"invalid_authentication_type": "无效的认证类型",
"auth_update_success": "认证更新成功",
"browser_closed": "浏览器已关闭",
"auth_update_failed": "认证更新失败",
"google_start": "Google 开始",
"github_start": "Github 开始",
"usage_count": "使用次数: {usage}",
"account_has_reached_maximum_usage": "账户已达到最大使用量, {deleting}",
"starting_new_authentication_process": "开始新的认证过程...",
"failed_to_delete_expired_account": "删除过期账户失败",
"could_not_check_usage_count": "无法检查使用次数: {error}",
"found_email": "找到邮箱: {email}",
"could_not_find_email": "未找到邮箱: {error}",
"could_not_find_usage_count": "未找到使用次数: {error}",
"already_on_settings_page": "已处于设置页面",
"failed_to_extract_auth_info": "提取认证信息失败: {error}",
"no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默认配置文件",
"found_default_chrome_profile": "找到默认 Chrome 配置文件",
"using_first_available_chrome_profile": "使用第一个可用的 Chrome 配置文件: {profile}",
"error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默认配置文件: {error}",
"initializing_browser_setup": "初始化浏览器设置...",
"detected_platform": "检测平台: {platform}",
"running_as_root_warning": "以 root 运行不推荐用于浏览器自动化",
"consider_running_without_sudo": "考虑不使用 sudo 运行脚本",
"no_compatible_browser_found": "未找到兼容的浏览器。请安装 Google Chrome 或 Chromium。",
"supported_browsers": "支持的浏览器: {platform}",
"using_browser_profile": "使用浏览器配置文件: {profile}",
"starting_browser": "正在启动浏览器: {path}",
"browser_setup_completed": "浏览器设置完成",
"browser_setup_failed": "浏览器设置失败: {error}",
"try_running_without_sudo_admin": "尝试不使用 sudo/管理员权限运行",
"redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...",
"starting_google_authentication": "开始 Google 认证...",
"starting_github_authentication": "开始 Github 认证...",
"waiting_for_authentication": "等待认证...",
"page_changed_checking_auth": "页面改变, 检查认证...",
"status_check_error": "状态检查错误: {error}",
"authentication_timeout": "认证超时",
"account_is_still_valid": "账户仍然有效 (使用量: {usage})",
"starting_re_authentication_process": "开始重新认证过程...",
"starting_new_google_authentication": "开始新的 Google 认证...",
"failed_to_delete_account_or_re_authenticate": "删除账户或重新认证失败: {error}",
"navigating_to_authentication_page": "正在导航到认证页面...",
"please_select_your_google_account_to_continue": "请选择您的 Google 账户以继续...",
"found_browser_data_directory": "找到浏览器数据目录: {path}",
"authentication_successful_getting_account_info": "认证成功, 获取账户信息...",
"warning_could_not_kill_existing_browser_processes": "警告: 无法杀死现有浏览器进程: {error}",
"browser_failed_to_start": "浏览器启动失败: {error}",
"browser_failed": "浏览器启动失败: {error}",
"browser_failed_to_start_fallback": "浏览器启动失败: {error}"
},
"chrome_profile": {
"title": "Chrome配置文件选择",
"select_profile": "选择要使用的Chrome配置文件",
"profile_list": "可用配置文件:",
"default_profile": "默认配置文件",
"profile": "配置文件 {number}",
"no_profiles": "未找到Chrome配置文件",
"error_loading": "加载Chrome配置文件时出错{error}",
"profile_selected": "已选择配置文件:{profile}",
"invalid_selection": "选择无效。请重试",
"warning_chrome_close": "警告这将关闭所有正在运行的Chrome进程"
},
"account_delete": {
"title": "Cursor Google 账号删除工具",
"warning": "警告:这将永久删除您的 Cursor 账号。此操作无法撤销。",
"cancelled": "账号删除已取消。",
"starting_process": "开始账号删除过程...",
"google_button_not_found": "未找到 Google 登录按钮",
"logging_in": "正在使用 Google 登录...",
"waiting_for_auth": "等待 Google 验证...",
"login_successful": "登录成功",
"unexpected_page": "登录后页面异常:{url}",
"trying_settings": "尝试导航到设置页面...",
"select_google_account": "请选择您的 Google 账号...",
"auth_timeout": "认证超时,继续执行...",
"navigating_to_settings": "正在导航到设置页面...",
"already_on_settings": "已在设置页面",
"login_redirect_failed": "登录重定向失败,尝试直接导航...",
"advanced_tab_not_found": "多次尝试后未找到高级选项卡",
"advanced_tab_retry": "未找到高级选项卡,尝试 {attempt}/{max_attempts}",
"advanced_tab_error": "查找高级选项卡时出错:{error}",
"advanced_tab_clicked": "已点击高级选项卡",
"direct_advanced_navigation": "尝试直接导航到高级选项卡",
"delete_button_not_found": "多次尝试后未找到删除账号按钮",
"delete_button_retry": "未找到删除按钮,尝试 {attempt}/{max_attempts}",
"delete_button_error": "查找删除按钮时出错:{error}",
"delete_button_clicked": "已点击删除账号按钮",
"delete_input_not_found": "多次尝试后未找到删除确认输入框",
"delete_input_retry": "未找到删除输入框,尝试 {attempt}/{max_attempts}",
"delete_input_error": "查找删除输入框时出错:{error}",
"delete_input_not_found_continuing": "未找到删除确认输入框,尝试继续执行...",
"typed_delete": "已在确认框中输入\"Delete\"",
"confirm_button_not_found": "多次尝试后未找到确认按钮",
"confirm_button_retry": "未找到确认按钮,尝试 {attempt}/{max_attempts}",
"confirm_button_error": "查找确认按钮时出错:{error}",
"account_deleted": "账号删除成功!",
"error": "账号删除过程中出错:{error}",
"success": "您的 Cursor 账号已成功删除!",
"failed": "账号删除过程失败或已取消。",
"interrupted": "账号删除过程被用户中断。",
"unexpected_error": "意外错误:{error}",
"found_email": "找到邮箱:{email}",
"email_not_found": "未找到邮箱: {error}",
"found_danger_zone": "已找到危险区域部分",
"confirm_prompt": "您确定要继续吗?(y/N): "
}
}

View File

@@ -2,11 +2,14 @@
"menu": {
"title": "可用選項",
"exit": "退出程式",
"reset": "重置機器識別碼",
"register": "註冊新 Cursor 帳號",
"register_manual": "使用自訂郵箱註冊",
"quit": "關閉 Cursor 應用程式",
"select_language": "變更語言",
"reset": "重置機器ID",
"register": "註冊新Cursor帳戶",
"register_google": "使用Google帳戶註冊",
"register_github": "使用GitHub帳戶註冊",
"register_manual": "使用自定義郵箱註冊Cursor",
"quit": "關閉Cursor應用",
"select_language": "更改語言",
"select_chrome_profile": "選擇Chrome配置檔案",
"input_choice": "請輸入您的選擇 ({choices})",
"invalid_choice": "選擇無效,請輸入 {choices} 範圍內的數字",
"program_terminated": "程式已被使用者終止",
@@ -22,7 +25,11 @@
"coming_soon": "即將推出",
"fixed_soon": "即將修復",
"contribute": "貢獻項目",
"config": "顯示配置"
"config": "顯示配置",
"delete_google_account": "刪除 Cursor Google 帳號",
"continue_prompt": "繼續?(y/N): ",
"operation_cancelled_by_user": "操作被使用者取消",
"exiting": "退出中 ……"
},
"languages": {
"en": "英文",
@@ -254,7 +261,14 @@
"blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}",
"available_domains_loaded": "獲取到 {count} 個可用域名",
"domains_filtered": "過濾後剩餘 {count} 個可用域名",
"trying_to_create_email": "嘗試創建郵箱: {email}"
"trying_to_create_email": "嘗試創建郵箱: {email}",
"domain_blocked": "域名被屏蔽: {domain}",
"using_chrome_profile": "使用 Chrome 配置文件: {user_data_dir}",
"no_display_found": "未找到顯示器。確保 X 伺服器正在運行。",
"try_export_display": "嘗試: export DISPLAY=:0",
"extension_load_error": "加載插件失敗: {error}",
"make_sure_chrome_chromium_is_properly_installed": "確保 Chrome/Chromium 已正確安裝",
"try_install_chromium": "嘗試: sudo apt install chromium-browser"
},
"update": {
"title": "禁用 Cursor 自动更新",
@@ -274,7 +288,16 @@
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
"unsupported_os": "不支持的操作系统: {system}",
"remove_directory_failed": "刪除目錄失败: {error}",
"create_block_file_failed": "創建阻止文件失败: {error}"
"create_block_file_failed": "創建阻止文件失败: {error}",
"directory_locked": "目錄被鎖定: {path}",
"yml_locked": "update.yml 文件被鎖定",
"block_file_locked": "阻止文件被鎖定",
"yml_already_locked": "update.yml 文件已鎖定",
"block_file_already_locked": "阻止文件已鎖定",
"block_file_locked_error": "阻止文件锁定错误: {error}",
"yml_locked_error": "update.yml 文件锁定错误: {error}",
"block_file_already_locked_error": "阻止文件已锁定错误: {error}",
"yml_already_locked_error": "update.yml 文件已锁定错误: {error}"
},
"updater": {
"checking": "檢查更新...",
@@ -467,6 +490,144 @@
"configuration": "配置",
"enabled": "已啟用",
"disabled": "已禁用",
"config_directory": "配置目錄"
"config_directory": "配置目錄",
"neither_cursor_nor_cursor_directory_found": "未找到 Cursor 或 Cursor 目錄",
"please_make_sure_cursor_is_installed_and_has_been_run_at_least_once": "請確保 Cursor 已安裝並至少運行一次",
"storage_directory_not_found": "未找到儲存目錄",
"storage_file_found": "找到儲存文件",
"file_size": "文件大小",
"file_permissions": "文件權限",
"file_owner": "文件所有者",
"file_group": "文件組",
"error_getting_file_stats": "獲取文件統計信息時出錯",
"permission_denied": "權限拒絕",
"try_running": "嘗試運行: {command}",
"and": "和",
"storage_file_is_empty": "儲存文件為空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已損壞,請重新安裝 Cursor",
"storage_file_not_found": "未找到儲存文件",
"error_checking_linux_paths": "檢查 Linux 路徑時出錯",
"config_option_added": "添加配置選項",
"config_updated": "配置更新",
"config_created": "配置已創建",
"config_setup_error": "配置設置錯誤",
"storage_file_is_valid_and_contains_data": "儲存文件有效且包含數據",
"error_reading_storage_file": "讀取儲存文件時出錯",
"also_checked": "也檢查了 {path}"
},
"oauth": {
"authentication_button_not_found": "未找到認證按鈕",
"authentication_failed": "認證失敗: {error}",
"found_cookies": "找到 {count} 個 Cookie",
"token_extraction_error": "Token 提取錯誤: {error}",
"authentication_successful": "認證成功 - 郵箱: {email}",
"missing_authentication_data": "缺少認證數據: {data}",
"failed_to_delete_account": "刪除帳戶失敗: {error}",
"invalid_authentication_type": "無效的認證類型",
"auth_update_success": "認證更新成功",
"browser_closed": "瀏覽器已關閉",
"auth_update_failed": "認證更新失敗",
"google_start": "Google 開始",
"github_start": "Github 開始",
"usage_count": "使用量: {usage}",
"account_has_reached_maximum_usage": "帳戶已達到最大使用量, {deleting}",
"starting_new_authentication_process": "開始新的認證過程...",
"failed_to_delete_expired_account": "刪除過期帳戶失敗",
"could_not_check_usage_count": "無法檢查使用量: {error}",
"found_email": "找到郵箱: {email}",
"could_not_find_email": "未找到郵箱: {error}",
"could_not_find_usage_count": "未找到使用量: {erro r}",
"already_on_settings_page": "已處於設置頁面",
"failed_to_extract_auth_info": "提取認證信息失敗: {error}",
"no_chrome_profiles_found": "未找到 Chrome 配置文件, 使用默認配置文件",
"found_default_chrome_profile": "找到默認 Chrome 配置文件",
"using_first_available_chrome_profile": "使用第一個可用的 Chrome 配置文件: {profile}",
"error_finding_chrome_profile": "找不到 Chrome 配置文件, 使用默認配置文件: {error}",
"initializing_browser_setup": "初始化瀏覽器設置...",
"detected_platform": "檢測平台: {platform}",
"running_as_root_warning": "以 root 運行不推薦用於瀏覽器自動化",
"consider_running_without_sudo": "考慮不使用 sudo 運行腳本",
"no_compatible_browser_found": "未找到兼容的瀏覽器。請安裝 Google Chrome 或 Chromium。",
"supported_browsers": "支持的瀏覽器: {platform}",
"using_browser_profile": "使用瀏覽器配置文件: {profile}",
"starting_browser": "正在啟動瀏覽器: {path}",
"browser_setup_completed": "瀏覽器設置完成成功",
"browser_setup_failed": "瀏覽器設置失敗: {error}",
"try_running_without_sudo_admin": "嘗試不使用 sudo/管理員權限運行",
"redirecting_to_authenticator_cursor_sh": "重定向到 authenticator.cursor.sh...",
"starting_github_authentication": "開始 Github 認證...",
"waiting_for_authentication": "等待認證...",
"page_changed_checking_auth": "頁面改變, 檢查認證...",
"status_check_error": "狀態檢查錯誤: {error}",
"authentication_timeout": "認證超時",
"account_is_still_valid": "帳戶仍然有效 (使用量: {usage})",
"starting_re_authentication_process": "開始重新認證過程...",
"starting_new_google_authentication": "開始新的 Google 認證...",
"failed_to_delete_account_or_re_authenticate": "刪除帳戶或重新認證失敗: {error}",
"navigating_to_authentication_page": "正在導航到認證頁面...",
"please_select_your_google_account_to_continue": "請選擇您的 Google 帳戶以繼續...",
"found_browser_data_directory": "找到瀏覽器數據目錄: {path}",
"authentication_successful_getting_account_info": "認證成功, 獲取帳戶信息...",
"warning_could_not_kill_existing_browser_processes": "警告: 無法殺死現有瀏覽器進程: {error}",
"browser_failed_to_start": "瀏覽器啟動失敗: {error}",
"browser_failed": "瀏覽器啟動失敗: {error}",
"browser_failed_to_start_fallback": "瀏覽器啟動失敗: {error}"
},
"chrome_profile": {
"title": "Chrome配置檔案選擇",
"select_profile": "選擇要使用的Chrome配置檔案",
"profile_list": "可用配置檔案:",
"default_profile": "預設配置檔案",
"profile": "配置檔案 {number}",
"no_profiles": "未找到Chrome配置檔案",
"error_loading": "載入Chrome配置檔案時出錯{error}",
"profile_selected": "已選擇配置檔案:{profile}",
"invalid_selection": "選擇無效。請重試",
"warning_chrome_close": "警告這將關閉所有正在執行的Chrome程序"
},
"account_delete": {
"title": "Cursor Google 帳號刪除工具",
"warning": "警告:這將永久刪除您的 Cursor 帳號。此操作無法撤銷。",
"cancelled": "帳號刪除已取消。",
"starting_process": "開始帳號刪除過程...",
"google_button_not_found": "未找到 Google 登錄按鈕",
"logging_in": "正在使用 Google 登錄...",
"waiting_for_auth": "等待 Google 驗證...",
"login_successful": "登錄成功",
"unexpected_page": "登錄後頁面異常:{url}",
"trying_settings": "嘗試導航到設置頁面...",
"select_google_account": "請選擇您的 Google 帳號...",
"auth_timeout": "認證超時,繼續執行...",
"navigating_to_settings": "正在導航到設置頁面...",
"already_on_settings": "已在設置頁面",
"login_redirect_failed": "登錄重定向失敗,嘗試直接導航...",
"advanced_tab_not_found": "多次嘗試後未找到高級選項卡",
"advanced_tab_retry": "未找到高級選項卡,嘗試 {attempt}/{max_attempts}",
"advanced_tab_error": "查找高級選項卡時出錯:{error}",
"advanced_tab_clicked": "已點擊高級選項卡",
"direct_advanced_navigation": "嘗試直接導航到高級選項卡",
"delete_button_not_found": "多次嘗試後未找到刪除帳號按鈕",
"delete_button_retry": "未找到刪除按鈕,嘗試 {attempt}/{max_attempts}",
"delete_button_error": "查找刪除按鈕時出錯:{error}",
"delete_button_clicked": "已點擊刪除帳號按鈕",
"delete_input_not_found": "多次嘗試後未找到刪除確認輸入框",
"delete_input_retry": "未找到刪除輸入框,嘗試 {attempt}/{max_attempts}",
"delete_input_error": "查找刪除輸入框時出錯:{error}",
"delete_input_not_found_continuing": "未找到刪除確認輸入框,嘗試繼續執行...",
"typed_delete": "已在確認框中輸入\"Delete\"",
"confirm_button_not_found": "多次嘗試後未找到確認按鈕",
"confirm_button_retry": "未找到確認按鈕,嘗試 {attempt}/{max_attempts}",
"confirm_button_error": "查找確認按鈕時出錯:{error}",
"account_deleted": "帳號刪除成功!",
"error": "帳號刪除過程中出錯:{error}",
"success": "您的 Cursor 帳號已成功刪除!",
"failed": "帳號刪除過程失敗或已取消。",
"interrupted": "帳號刪除過程被用戶中斷。",
"unexpected_error": "意外錯誤:{error}",
"found_email": "找到郵箱:{email}",
"email_not_found": "未找到郵箱: {error}",
"found_danger_zone": "已找到危險區域部分",
"confirm_prompt": "您確定要繼續嗎?(y/N): ",
"typed_delete_js": "已使用 JavaScript 輸入\"Delete\""
}
}

View File

@@ -79,7 +79,7 @@ Contributors:
BasaiCorp aliensb handwerk2016 Nigel1992
UntaDotMy RenjiYuusei imbajin ahmed98Osama
bingoohuang mALIk-sHAHId MFaiqKhan httpmerak
muhammedfurkan plamkatawe
muhammedfurkan plamkatawe Lucaszmv
"""
OTHER_INFO_TEXT = f"""{Fore.YELLOW}
Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED}

15
main.py
View File

@@ -284,7 +284,9 @@ def print_menu():
9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}",
10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}",
11: f"{Fore.GREEN}11{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}",
12: f"{Fore.GREEN}12{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}"
12: f"{Fore.GREEN}12{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}",
13: f"{Fore.GREEN}13{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.select_chrome_profile')}",
14: f"{Fore.GREEN}14{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.delete_google_account', fallback='Delete Cursor Google Account')}"
}
# Automatically calculate the number of menu items in the left and right columns
@@ -556,7 +558,7 @@ def main():
while True:
try:
choice_num = 12
choice_num = 14
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}")
if choice == "0":
@@ -613,6 +615,15 @@ def main():
from config import print_config
print_config(get_config(), translator)
print_menu()
elif choice == "13":
from oauth_auth import OAuthHandler
oauth = OAuthHandler(translator)
oauth._select_profile()
print_menu()
elif choice == "14":
import delete_cursor_google
delete_cursor_google.main(translator)
print_menu()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
print_menu()

View File

@@ -106,11 +106,31 @@ class NewTempEmail:
# 创建浏览器选项
co = ChromiumOptions()
co.set_argument("--headless=new")
# Only use headless for non-OAuth operations
if not hasattr(self, 'auth_type') or self.auth_type != 'oauth':
co.set_argument("--headless=new")
if sys.platform == "linux":
co.set_argument("--no-sandbox")
# Check if DISPLAY is set when not in headless mode
if not co.arguments.get("--headless=new") and not os.environ.get('DISPLAY'):
print(f"{Fore.RED}{self.translator.get('email.no_display_found') if self.translator else 'No display found. Make sure X server is running.'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW} {self.translator.get('email.try_export_display') if self.translator else 'Try: export DISPLAY=:0'}{Style.RESET_ALL}")
return False
co.set_argument("--no-sandbox")
co.set_argument("--disable-dev-shm-usage")
co.set_argument("--disable-gpu")
# If running as root, try to use actual user's Chrome profile
if os.geteuid() == 0:
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
actual_home = f"/home/{sudo_user}"
user_data_dir = os.path.join(actual_home, ".config", "google-chrome")
if os.path.exists(user_data_dir):
print(f"{Fore.CYAN} {self.translator.get('email.using_chrome_profile', user_data_dir=user_data_dir) if self.translator else f'Using Chrome profile from: {user_data_dir}'}{Style.RESET_ALL}")
co.set_argument(f"--user-data-dir={user_data_dir}")
co.auto_port() # 自动设置端口
@@ -132,6 +152,10 @@ class NewTempEmail:
print(f"{Fore.RED}{self.translator.get('email.browser_start_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 启动浏览器失败: {str(e)}{Style.RESET_ALL}")
if sys.platform == "linux":
print(f"{Fore.YELLOW} {self.translator.get('email.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW} {self.translator.get('email.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}")
return False
def create_email(self):

View File

@@ -21,7 +21,8 @@ EMOJI = {
'SUCCESS': '',
'ERROR': '',
'WAIT': '',
'INFO': ''
'INFO': '',
'WARNING': '⚠️'
}
class OAuthHandler:
@@ -31,96 +32,129 @@ class OAuthHandler:
self.auth_type = auth_type # make sure the auth_type is not None
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
self.selected_profile = None
def _get_active_profile(self, user_data_dir):
"""Find the existing default/active Chrome profile"""
def _get_available_profiles(self, user_data_dir):
"""Get list of available Chrome profiles with their names"""
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)
profile_names = {}
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
# Read Local State file to get profile names
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]
info_cache = local_state.get('profile', {}).get('info_cache', {})
for profile_dir, info in info_cache.items():
profile_dir = profile_dir.replace('\\', '/')
if profile_dir == 'Default':
profile_names['Default'] = info.get('name', 'Default')
elif profile_dir.startswith('Profile '):
profile_names[profile_dir] = info.get('name', profile_dir)
# Get list of profile directories
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, profile_names.get(item, item)))
return sorted(profiles)
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('chrome_profile.error_loading', error=str(e)) if self.translator else f'Error loading Chrome profiles: {e}'}{Style.RESET_ALL}")
return []
def _select_profile(self):
"""Select a Chrome profile to use"""
try:
# Get available profiles
profiles = self._get_available_profiles(self._get_user_data_directory())
if not profiles:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('chrome_profile.no_profiles') if self.translator else 'No Chrome profiles found'}{Style.RESET_ALL}")
return False
# Display available profiles
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('chrome_profile.select_profile') if self.translator else 'Select a Chrome profile to use:'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{self.translator.get('chrome_profile.profile_list') if self.translator else 'Available profiles:'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}0. {self.translator.get('menu.exit') if self.translator else 'Exit'}{Style.RESET_ALL}")
for i, (dir_name, display_name) in enumerate(profiles, 1):
print(f"{Fore.CYAN}{i}. {display_name} ({dir_name}){Style.RESET_ALL}")
# Get user selection
while True:
try:
choice = int(input(f"\n{Fore.CYAN}{self.translator.get('menu.input_choice', choices=f'0-{len(profiles)}') if self.translator else f'Please enter your choice (0-{len(profiles)}): '}{Style.RESET_ALL}"))
if choice == 0: # Add quit
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('menu.exiting') if self.translator else 'Exiting profile selection...'}{Style.RESET_ALL}")
return False
elif 1 <= choice <= len(profiles):
self.selected_profile = profiles[choice - 1][0]
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('chrome_profile.profile_selected', profile=self.selected_profile) if self.translator else f'Selected profile: {self.selected_profile}'}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('chrome_profile.invalid_selection') if self.translator else 'Invalid selection. Please try again.'}{Style.RESET_ALL}")
except ValueError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('chrome_profile.invalid_selection') if self.translator else 'Invalid selection. Please try again.'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}")
return 'Default'
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('chrome_profile.error_loading', error=str(e)) if self.translator else f'Error loading Chrome profiles: {e}'}{Style.RESET_ALL}")
return False
def setup_browser(self):
"""Setup browser for OAuth flow using active profile"""
"""Setup browser for OAuth flow using selected profile"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.initializing_browser_setup') if self.translator else '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()
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_platform', platform=platform_name) if self.translator else f'Detected platform: {platform_name}'}{Style.RESET_ALL}")
# 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" +
raise Exception(f"{self.translator.get('oauth.no_compatible_browser_found') if self.translator else 'No compatible browser found. Please install Google Chrome or Chromium.'}\n{self.translator.get('oauth.supported_browsers', platform=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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_browser_data_directory', path=user_data_dir) if self.translator else f'Found browser data directory: {user_data_dir}'}{Style.RESET_ALL}")
# Show warning about closing Chrome first
print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('chrome_profile.warning_chrome_close') if self.translator else 'Warning: This will close all running Chrome processes'}{Style.RESET_ALL}")
choice = input(f"{Fore.YELLOW} {self.translator.get('menu.continue_prompt', choices='y/N')} {Style.RESET_ALL}").lower()
if choice != 'y':
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('menu.operation_cancelled_by_user') if self.translator else 'Operation cancelled by user'}{Style.RESET_ALL}")
return False
# Kill existing browser processes
self._kill_browser_processes()
# Let user select a profile
if not self._select_profile():
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('menu.operation_cancelled_by_user') if self.translator else 'Operation cancelled by user'}{Style.RESET_ALL}")
return False
# Configure browser options
co = self._configure_browser_options(chrome_path, user_data_dir, active_profile)
co = self._configure_browser_options(chrome_path, user_data_dir, self.selected_profile)
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_browser', path=chrome_path) if self.translator else f'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")
raise Exception(f"{self.translator.get('oauth.browser_failed_to_start') if self.translator else 'Failed to initialize browser instance'}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.browser_setup_completed') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_setup_failed', error=str(e)) if self.translator else f'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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_running_without_sudo_admin') if self.translator else 'Try running without sudo/administrator 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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.make_sure_chrome_chromium_is_properly_installed') if self.translator else 'Make sure Chrome/Chromium is properly installed'}{Style.RESET_ALL}")
if platform_name == 'linux':
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.try_install_chromium') if self.translator else 'Try: sudo apt install chromium-browser'}{Style.RESET_ALL}")
return False
def _kill_browser_processes(self):
@@ -137,7 +171,7 @@ class OAuthHandler:
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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.warning_could_not_kill_existing_browser_processes', error=str(e)) if self.translator else f'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"""
@@ -163,17 +197,16 @@ class OAuthHandler:
# 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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.creating_temporary_profile', path=temp_profile) if self.translator else f'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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_getting_user_data_directory', error=str(e)) if self.translator else f'Error getting user data directory: {e}'}{Style.RESET_ALL}")
raise
def _get_browser_path(self):
@@ -184,7 +217,7 @@ class OAuthHandler:
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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.searching_for_alternative_browser_installations') if self.translator else 'Searching for alternative browser installations...'}{Style.RESET_ALL}")
# Platform-specific paths
if os.name == 'nt': # Windows
@@ -216,13 +249,13 @@ class OAuthHandler:
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}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.found_browser_at', path=expanded_path) if self.translator else f'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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_finding_browser_path', error=str(e)) if self.translator else f'Error finding browser path: {e}'}{Style.RESET_ALL}")
return None
def _configure_browser_options(self, chrome_path, user_data_dir, active_profile):
@@ -251,22 +284,22 @@ class OAuthHandler:
return co
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_configuring_browser_options', error=str(e)) if self.translator else f'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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start') if self.translator else 'Starting Google OAuth authentication...'}{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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed') if self.translator else 'Browser failed to initialize'}{Style.RESET_ALL}")
return False, None
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else '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'))
@@ -290,16 +323,17 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_google_authentication') if self.translator else '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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue...'}{Style.RESET_ALL}")
alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication'
try:
self.browser.run_js("""
alert('Please select your Google account to continue with Cursor authentication');
self.browser.run_js(f"""
alert('{alert_message}');
""")
except:
pass # Alert is optional
@@ -307,14 +341,14 @@ class OAuthHandler:
# 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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else 'Timeout'}{Style.RESET_ALL}")
return False, None
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
try:
@@ -334,7 +368,7 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.waiting_for_authentication', timeout='5 minutes') if self.translator else 'Waiting for authentication (timeout: 5 minutes)'}{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
@@ -353,7 +387,7 @@ class OAuthHandler:
if token:
# Get email from settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.authentication_successful_getting_account_info') if self.translator else 'Authentication successful, getting account info...'}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3)
@@ -362,7 +396,7 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=email) if self.translator else f'Found email: {email}'}{Style.RESET_ALL}")
except:
email = "user@cursor.sh" # Fallback email
@@ -371,42 +405,51 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'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}")
def check_usage_limits(usage_str):
try:
parts = usage_str.split('/')
if len(parts) != 2:
return False
current = int(parts[0].strip())
limit = int(parts[1].strip())
return (limit == 50 and current >= 50) or (limit == 150 and current >= 150)
except:
return False
if check_usage_limits(usage_text):
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}")
# Delete current account
if self._delete_current_account():
# Start new authentication based on auth type
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_authentication_process') if self.translator else 'Starting new authentication process...'}{Style.RESET_ALL}")
if self.auth_type == "google":
return self.handle_google_auth()
else: # github
else:
return self.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_expired_account') if self.translator else 'Failed to delete expired account'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'Account is still valid (Usage: {usage_text})'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_check_usage_count', error=str(e)) if self.translator else f'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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.detected_successful_login') if self.translator else 'Detected successful login'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication', error=str(e)) if self.translator else f'Waiting for authentication... ({str(e)})'}{Style.RESET_ALL}")
time.sleep(check_interval)
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.error_waiting_for_authentication', error=str(e)) if self.translator else f'Error while waiting for authentication: {str(e)}'}{Style.RESET_ALL}")
return None
def handle_github_auth(self):
@@ -421,7 +464,7 @@ class OAuthHandler:
# Navigate to auth URL
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_authentication_page') if self.translator else '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'))
@@ -445,21 +488,21 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_github_authentication') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_error', error=str(e)) if self.translator else f'Authentication error: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
try:
@@ -521,23 +564,23 @@ class OAuthHandler:
# 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');
"""
alert_message = self.translator.get('oauth.please_select_your_google_account_to_continue') if self.translator else 'Please select your Google account to continue with Cursor authentication'
try:
self.browser.run_js(alert_js)
self.browser.run_js(f"""
alert('{alert_message}');
""")
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.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.alert_display_failed', error=str(e)) if self.translator else f'Alert display failed: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.please_select_your_google_account_manually_to_continue_with_cursor_authentication') if self.translator else '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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.waiting_for_authentication_to_complete') if self.translator else '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}")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('oauth.checking_authentication_status') if self.translator else 'Checking authentication status...'}{Style.RESET_ALL}")
while time.time() - start_time < max_wait:
try:
@@ -553,9 +596,9 @@ class OAuthHandler:
token = value.split("%3A%3A")[-1]
if token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful') if self.translator else 'Authentication successful!'}{Style.RESET_ALL}")
# Navigate to settings page
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.navigating_to_settings_page') if self.translator else 'Navigating to settings page...'}{Style.RESET_ALL}")
self.browser.get("https://www.cursor.com/settings")
time.sleep(3) # Wait for settings page to load
@@ -564,9 +607,9 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
@@ -574,59 +617,34 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'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, 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();
"""
def check_usage_limits(usage_str):
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}")
parts = usage_str.split('/')
if len(parts) != 2:
return False
current = int(parts[0].strip())
limit = int(parts[1].strip())
return (limit == 50 and current >= 50) or (limit == 150 and current >= 150)
except:
return False
if check_usage_limits(usage_text):
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}")
if self._delete_current_account():
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_authentication_process') if self.translator else 'Starting new authentication process...'}{Style.RESET_ALL}")
if self.auth_type == "google":
return self.handle_google_auth()
else:
return self.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_expired_account') if self.translator else 'Failed to delete expired account'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_check_usage_count', error=str(e)) if self.translator else f'Could not check usage count: {str(e)}'}{Style.RESET_ALL}")
# Remove the browser stay open prompt and input wait
return True, {"email": actual_email, "token": token}
@@ -634,7 +652,7 @@ class OAuthHandler:
# 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}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.already_on_settings_page') if self.translator else 'Already on settings page!'}{Style.RESET_ALL}")
time.sleep(1)
cookies = self.browser.cookies()
for cookie in cookies:
@@ -650,9 +668,9 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_email', email=actual_email) if self.translator else f'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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_find_email', error=str(e)) if self.translator else f'Could not find email: {str(e)}'}{Style.RESET_ALL}")
actual_email = "user@cursor.sh"
# Check usage count
@@ -660,80 +678,55 @@ class OAuthHandler:
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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.usage_count', usage=usage_text) if self.translator else f'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, 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();
"""
def check_usage_limits(usage_str):
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}")
parts = usage_str.split('/')
if len(parts) != 2:
return False
current = int(parts[0].strip())
limit = int(parts[1].strip())
return (limit == 50 and current >= 50) or (limit == 150 and current >= 150)
except:
return False
if check_usage_limits(usage_text):
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.account_has_reached_maximum_usage', deleting='deleting') if self.translator else 'Account has reached maximum usage, deleting...'}{Style.RESET_ALL}")
if self._delete_current_account():
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.starting_new_authentication_process') if self.translator else 'Starting new authentication process...'}{Style.RESET_ALL}")
if self.auth_type == "google":
return self.handle_google_auth()
else:
return self.handle_github_auth()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_expired_account') if self.translator else 'Failed to delete expired account'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.account_is_still_valid', usage=usage_text) if self.translator else f'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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.could_not_check_usage_count', error=str(e)) if self.translator else f'Could not check 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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.page_changed_checking_auth') if self.translator else '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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.status_check_error', error=str(e)) if self.translator else f'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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_timeout') if self.translator else 'Authentication timeout'}{Style.RESET_ALL}")
return False, None
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_button_not_found') if self.translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.authentication_failed', error=str(e)) if self.translator else f'Authentication failed: {str(e)}'}{Style.RESET_ALL}")
return False, None
finally:
if self.browser:
@@ -756,7 +749,7 @@ class OAuthHandler:
time.sleep(1)
# Debug cookie information
print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.found_cookies', count=len(cookies)) if self.translator else f'Found {len(cookies)} cookies'}{Style.RESET_ALL}")
email = None
token = None
@@ -771,12 +764,12 @@ class OAuthHandler:
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}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('oauth.token_extraction_error', error=str(e)) if self.translator else f'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}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.authentication_successful', email=email) if self.translator else f'Authentication successful - Email: {email}'}{Style.RESET_ALL}")
return True, {"email": email, "token": token}
else:
missing = []
@@ -784,11 +777,11 @@ class OAuthHandler:
missing.append("email")
if not token:
missing.append("token")
print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.missing_authentication_data', data=', '.join(missing)) if self.translator else f'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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_extract_auth_info', error=str(e)) if self.translator else f'Failed to extract auth info: {str(e)}'}{Style.RESET_ALL}")
return False, None
def _delete_current_account(self):
@@ -823,14 +816,14 @@ class OAuthHandler:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
# Navigate back to auth page
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.redirecting_to_authenticator_cursor_sh') if self.translator else 'Redirecting to authenticator.cursor.sh...'}{Style.RESET_ALL}")
self.browser.get("https://authenticator.cursor.sh/sign-up")
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account: {str(e)}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed_to_delete_account', error=str(e)) if self.translator else f'Failed to delete account: {str(e)}'}{Style.RESET_ALL}")
return False
def main(auth_type, translator=None):
@@ -843,13 +836,13 @@ def main(auth_type, translator=None):
handler = OAuthHandler(translator, auth_type)
if auth_type.lower() == 'google':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start') if translator else '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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start') if translator else '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}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.invalid_authentication_type') if translator else 'Invalid authentication type'}{Style.RESET_ALL}")
return False
if success and auth_info:
@@ -860,13 +853,13 @@ def main(auth_type, translator=None):
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}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success') if translator else '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}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.browser_closed') if translator else 'Browser closed'}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed') if translator else 'Auth update failed'}{Style.RESET_ALL}")
return False

View File

@@ -8,6 +8,7 @@ import sqlite3
import platform
import re
import tempfile
import glob
from colorama import Fore, Style, init
from typing import Tuple
import configparser
@@ -25,7 +26,7 @@ EMOJI = {
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"RESET": "<EFBFBD><EFBFBD>",
"RESET": "🔄",
"WARNING": "⚠️",
}
@@ -46,9 +47,28 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
default_paths = {
"Darwin": "/Applications/Cursor.app/Contents/Resources/app",
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
"Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app")]
"Linux": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", os.path.expanduser("~/.local/share/cursor/resources/app"), "/usr/lib/cursor/app/"]
}
if system == "Linux":
# Look for extracted AppImage with correct usr structure
extracted_usr_paths = glob.glob(os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app"))
# Also check current directory for extraction without home path prefix
current_dir_paths = glob.glob("squashfs-root/usr/share/cursor/resources/app")
# Add any found paths to the Linux paths list
default_paths["Linux"].extend(extracted_usr_paths)
default_paths["Linux"].extend(current_dir_paths)
# Print debug information
print(f"{Fore.CYAN}{EMOJI['INFO']} Available paths found:{Style.RESET_ALL}")
for path in default_paths["Linux"]:
if os.path.exists(path):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {path} (exists){Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {path} (not found){Style.RESET_ALL}")
# If config doesn't exist, create it with default paths
if not os.path.exists(config_file):
for section in ['MacPaths', 'WindowsPaths', 'LinuxPaths']:
@@ -159,6 +179,14 @@ def get_cursor_machine_id_path(translator=None) -> str:
def get_workbench_cursor_path(translator=None) -> str:
"""Get Cursor workbench.desktop.main.js path"""
system = platform.system()
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if os.path.exists(config_file):
config.read(config_file)
paths_map = {
"Darwin": { # macOS
@@ -166,14 +194,19 @@ def get_workbench_cursor_path(translator=None) -> str:
"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"
"main": "out\\vs\\workbench\\workbench.desktop.main.js"
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", "/usr/lib/cursor/app/"],
"main": "out/vs/workbench/workbench.desktop.main.js"
}
}
if system == "Linux":
# Add extracted AppImage with correct usr structure
extracted_usr_paths = glob.glob(os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app"))
paths_map["Linux"]["bases"].extend(extracted_usr_paths)
if system not in paths_map:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
@@ -181,11 +214,15 @@ def get_workbench_cursor_path(translator=None) -> str:
if system == "Linux":
for base in paths_map["Linux"]["bases"]:
main_path = os.path.join(base, paths_map["Linux"]["main"])
print(f"{Fore.CYAN}{EMOJI['INFO']} Checking path: {main_path}{Style.RESET_ALL}")
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"]
if system == "Windows":
base_path = config.get('WindowsPaths', 'cursor_path')
else:
base_path = paths_map[system]["base"]
main_path = os.path.join(base_path, paths_map[system]["main"])
if not os.path.exists(main_path):
@@ -299,11 +336,11 @@ def modify_workbench_js(file_path: str, translator=None) -> bool:
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)'
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)'
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)'
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)'
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)'
@@ -786,4 +823,4 @@ def run(translator=None):
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)
run(main_translator)

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,15 @@ def get_user_documents_path():
def get_default_chrome_path():
"""Get default Chrome path"""
if sys.platform == "win32":
# Trying to find chrome in PATH
try:
import shutil
chrome_in_path = shutil.which("chrome")
if chrome_in_path:
return chrome_in_path
except:
pass
# Going to default path
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
elif sys.platform == "darwin":
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"