mirror of
https://git.axenov.dev/mirrors/cursor-free-vip.git
synced 2025-12-26 21:50:39 +03:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74be8a0a77 | ||
|
|
bce205b252 | ||
|
|
8fcf3b62cf | ||
|
|
eeaee073cf | ||
|
|
2f353da381 | ||
|
|
efd1417407 | ||
|
|
2cc7cce283 | ||
|
|
4c30afcd7f | ||
|
|
bfb2f5e95a | ||
|
|
35e01edf9c | ||
|
|
df9e88131b | ||
|
|
8a4d22103e | ||
|
|
1244d86994 | ||
|
|
a2833fcbd4 | ||
|
|
b3e7c101d3 | ||
|
|
0a779063bf | ||
|
|
c96433e446 | ||
|
|
5604f5073d | ||
|
|
3cad68be36 | ||
|
|
d8799cdf2e | ||
|
|
3e5c22b816 | ||
|
|
893c3f8a52 | ||
|
|
edb74e21be | ||
|
|
be52ce98a6 | ||
|
|
163748fd96 | ||
|
|
f888b8c90c | ||
|
|
3c12561997 | ||
|
|
80aab8741f | ||
|
|
2595c4c95d | ||
|
|
f18e65f391 | ||
|
|
671307b53b | ||
|
|
f58bb70d3a | ||
|
|
5bfe653a92 | ||
|
|
a66a0e5395 | ||
|
|
ea44218a8a | ||
|
|
fdc8317380 | ||
|
|
523daea54e | ||
|
|
5a4e32f3f4 | ||
|
|
d91485ec75 | ||
|
|
51cef9c2b2 | ||
|
|
6e6180e331 | ||
|
|
e2d7c1c496 | ||
|
|
a56b978669 | ||
|
|
9793b91bc7 | ||
|
|
735dd8c1eb | ||
|
|
e5d192efcb | ||
|
|
a4d5b0e467 | ||
|
|
d7b056b339 | ||
|
|
bd152be4e8 | ||
|
|
172d9fb4bb |
75
.github/ISSUE_TEMPLATE/cn_feature_request.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/cn_feature_request.yml
vendored
Normal 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
|
||||
75
.github/ISSUE_TEMPLATE/en_feature_request.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/en_feature_request.yml
vendored
Normal 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
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -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 | 修復一些問題
|
||||
|
||||
@@ -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 | 系統支持
|
||||
|
||||
@@ -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
104
config.py
@@ -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
386
delete_cursor_google.py
Normal 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()
|
||||
@@ -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}")
|
||||
|
||||
BIN
images/pro_2025-04-05_18-47-56.png
Normal file
BIN
images/pro_2025-04-05_18-47-56.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -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 процеси"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
172
locales/en.json
172
locales/en.json
@@ -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): "
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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): "
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\""
|
||||
}
|
||||
}
|
||||
2
logo.py
2
logo.py
@@ -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
15
main.py
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
441
oauth_auth.py
441
oauth_auth.py
@@ -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
|
||||
@@ -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
9
utils.py
9
utils.py
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user