Compare commits

...

50 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
31 changed files with 2385 additions and 855 deletions

4
.env
View File

@@ -1,2 +1,2 @@
version=1.8.03
VERSION=1.8.03
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,28 @@
# 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 | 修復一些問題

View File

@@ -27,7 +27,7 @@ Always clean your browser's cache and cookies. If possible, use a VPN to create
這是一個自動化工具,自動註冊,支持 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/)
@@ -54,6 +54,8 @@ Always clean your browser's cache and cookies. If possible, use a VPN to create
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
* Delete Cursor Google Account<br>删除 Cursor Google 账号<br>
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
## 💻 System Support | 系統支持

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={},

104
config.py
View File

@@ -66,7 +66,8 @@ 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)
@@ -78,35 +79,53 @@ 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"
}
# 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
current_user = os.getenv('USER') or os.getenv('USERNAME') or os.getenv('SUDO_USER')
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]
actual_home = f"/home/{current_user}"
# 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
# Try both "Cursor" and "cursor" directory names in both user and root locations
cursor_dir = None
for dir_name in ["Cursor", "cursor"]:
test_dir = os.path.join(config_base, dir_name)
if os.path.exists(test_dir):
cursor_dir = test_dir
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)}{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')}{Style.RESET_ALL}")
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 ""
@@ -116,46 +135,50 @@ def setup_config(translator=None):
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)}{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')}{Style.RESET_ALL}")
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)}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', stat.st_size)}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', oct(stat.st_mode & 0o777))}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', stat.st_uid)}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', stat.st_gid)}{Style.RESET_ALL}")
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))}{Style.RESET_ALL}")
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)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', current_user=current_user, storage_path=storage_path)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and', storage_path=storage_path)}{Style.RESET_ALL}")
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)}{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')}{Style.RESET_ALL}")
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')}{Style.RESET_ALL}")
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))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor')}{Style.RESET_ALL}")
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)}{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')}{Style.RESET_ALL}")
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))}{Style.RESET_ALL}")
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'] = {
@@ -164,7 +187,8 @@ def setup_config(translator=None):
'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "",
'cursor_path': get_linux_cursor_path(),
'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 ""
'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
@@ -181,13 +205,13 @@ def setup_config(translator=None):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {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}{EMOJI['SUCCESS']} {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)
@@ -197,31 +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}{EMOJI['SUCCESS']} {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}{EMOJI['ERROR']} {translator.get('register.config_setup_error', error=str(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
@@ -229,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",
@@ -498,14 +535,14 @@
"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}",
"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 'cursor --help' to check if it's installed",
"and": "and",
"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}",
@@ -513,7 +550,124 @@
"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}"
"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": "检查更新...",
@@ -499,7 +519,7 @@
"file_group": "文件组",
"error_getting_file_stats": "获取文件统计信息时出错",
"permission_denied": "权限拒绝",
"try_running": "尝试运行 'cursor --help' 检查是否已安装",
"try_running": "尝试运行: {command}",
"and": "和",
"storage_file_is_empty": "存储文件为空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已损坏,请重新安装 Cursor",
@@ -508,7 +528,124 @@
"config_option_added": "添加配置选项",
"config_updated": "配置更新",
"config_created": "配置已创建",
"config_setup_error": "配置设置错误"
"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": "檢查更新...",
@@ -478,7 +501,7 @@
"file_group": "文件組",
"error_getting_file_stats": "獲取文件統計信息時出錯",
"permission_denied": "權限拒絕",
"try_running": "嘗試運行 'cursor --help' 檢查是否已安裝",
"try_running": "嘗試運行: {command}",
"and": "和",
"storage_file_is_empty": "儲存文件為空",
"the_file_might_be_corrupted_please_reinstall_cursor": "文件可能已損壞,請重新安裝 Cursor",
@@ -487,7 +510,124 @@
"config_option_added": "添加配置選項",
"config_updated": "配置更新",
"config_created": "配置已創建",
"config_setup_error": "配置設置錯誤"
"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"