forked from mirrors/cursor-free-vip
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5fceb2624 | ||
|
|
45046002df | ||
|
|
6bb33cec4c | ||
|
|
66f6197e6d | ||
|
|
5514e47759 | ||
|
|
3a53d59d52 | ||
|
|
c2e5499f19 | ||
|
|
9a223756c5 | ||
|
|
9b5912357d | ||
|
|
3200271156 | ||
|
|
8401f4718e | ||
|
|
b761bf0b6d | ||
|
|
db95689a8e | ||
|
|
652ffc809a | ||
|
|
a854969682 | ||
|
|
9c5ac85759 | ||
|
|
a67264d5c2 | ||
|
|
68b1dae466 | ||
|
|
0da6f9a1b7 | ||
|
|
59fccecb0f | ||
|
|
1769d245f9 | ||
|
|
b98f094407 | ||
|
|
ff358588bb | ||
|
|
6ca80ccb10 | ||
|
|
2fca5218fb | ||
|
|
71ecf5a201 | ||
|
|
4570b174ab | ||
|
|
f708ce443b | ||
|
|
4f6f3fe814 | ||
|
|
54ecf2d752 | ||
|
|
1f1231d1a9 | ||
|
|
d10999a517 | ||
|
|
fb4be6334a | ||
|
|
786eba5371 | ||
|
|
41ddbf519e | ||
|
|
ffd48201fd |
70
.github/ISSUE_TEMPLATE/cn_bug_report.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/cn_bug_report.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: ❌ 错误报告 [中文]
|
||||
description: 创建一个报告以帮助我们改进
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您花时间填写此错误报告!
|
||||
在提交 Issue 前请确保您已经阅读了[Github Issues](https://github.com/yeongpin/cursor-free-vip/issues)
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: 提交前检查
|
||||
description: |
|
||||
请确保您在提交 Issue 前已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我已经查看了置顶 Issue 并搜索了现有的 [开放 Issue](https://github.com/yeongpin/cursor-free-vip/issues)和[已关闭 Issue](https://github.com/yeongpin/cursor-free-vip/issues?q=is%3Aissue%20state%3Aclosed%20),没有找到类似的问题。
|
||||
required: true
|
||||
- label: 我填写了简短且清晰明确的标题,以便开发者在翻阅 Issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows x32
|
||||
- Windows x64
|
||||
- macOS Intel
|
||||
- macOS ARM64
|
||||
- Linux x64
|
||||
- Linux ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cursor Free Vip 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 错误描述
|
||||
description: 描述问题时请尽可能详细
|
||||
placeholder: 告诉我们发生了什么...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 相关日志输出
|
||||
description: 请复制并粘贴任何相关的日志输出
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 任何能让我们对你所遇到的问题有更多了解的东西
|
||||
75
.github/ISSUE_TEMPLATE/cn_question.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/cn_question.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: ❓ 讨论 & 提问 (中文)
|
||||
description: 寻求帮助、讨论问题、提出疑问等...
|
||||
title: '[讨论]: '
|
||||
labels: ['question']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Issue 检查清单
|
||||
description: |
|
||||
在提交 Issue 前请确保您已经完成了以下所有步骤
|
||||
options:
|
||||
- label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。
|
||||
required: true
|
||||
- label: 我确认自己需要的是提出问题并且讨论问题,而不是 Bug 反馈或需求建议。
|
||||
required: true
|
||||
- label: 我已阅读 [Github Issues](https://github.com/yeongpin/cursor-free-vip/issues) 并搜索了现有的 [开放 Issue](https://github.com/yeongpin/cursor-free-vip/issues) 和 [已关闭 Issue](https://github.com/yeongpin/cursor-free-vip/issues?q=is%3Aissue%20state%3Aclosed%20),没有找到类似的问题。
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 您正在使用哪个平台?
|
||||
options:
|
||||
- Windows x32
|
||||
- Windows x64
|
||||
- macOS Intel
|
||||
- macOS ARM64
|
||||
- Linux x64
|
||||
- Linux ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 您正在运行的 Cursor Free Vip 版本是什么?
|
||||
placeholder: 例如 v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: 您的问题
|
||||
description: 请详细描述您的问题
|
||||
placeholder: 请尽可能清楚地说明您的问题...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 补充信息
|
||||
description: 任何其他相关的信息、截图或代码示例
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: 优先级
|
||||
description: 这个问题对您来说有多紧急?
|
||||
options:
|
||||
- 低 (有空再看)
|
||||
- 中 (希望尽快得到答复)
|
||||
- 高 (阻碍工作进行)
|
||||
validations:
|
||||
required: true
|
||||
70
.github/ISSUE_TEMPLATE/en_bug_report.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/en_bug_report.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: ❌ Bug Report [English]
|
||||
description: Create a report to help us improve
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out this bug report!
|
||||
Before submitting this issue, please ensure that you have read the [github issues](https://github.com/yeongpin/cursor-free-vip/issues)
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Commit before submitting
|
||||
description: |
|
||||
Please ensure that you have completed all of the following steps before submitting an issue
|
||||
options:
|
||||
- label: I understand that Issues are used to provide feedback and solve problems, not to complain in the comments section, and will provide more information to help solve the problem.
|
||||
required: true
|
||||
- label: I have checked the top Issue and searched for existing [open issues](https://github.com/yeongpin/cursor-free-vip/issues) and [closed issues](https://github.com/yeongpin/cursor-free-vip/issues?q=is%3Aissue%20state%3Aclosed%20), and found no similar issues.
|
||||
required: true
|
||||
- label: I have filled out a short and clear title, so that developers can quickly determine the general problem when browsing the Issue list. Not "a suggestion", "stuck", etc.
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Which platform are you using?
|
||||
options:
|
||||
- Windows x32
|
||||
- Windows x64
|
||||
- macOS Intel
|
||||
- macOS ARM64
|
||||
- Linux x64
|
||||
- Linux ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Cursor Free Vip are you running?
|
||||
placeholder: For example v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please describe the problem as detailed as possible
|
||||
placeholder: Tell us what happened...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Related log output
|
||||
description: Please copy and paste any related log output
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Anything that might help us understand the problem better
|
||||
75
.github/ISSUE_TEMPLATE/en_question.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/en_question.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: ❓ Discussion & Question [English]
|
||||
description: Seeking help, discussing problems, asking questions, etc.
|
||||
title: '[Discussion]: '
|
||||
labels: ['question']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for your question! Please describe your problem as detailed as possible so we can help you better.
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: |
|
||||
Please ensure that you have completed all of the following steps before submitting an issue
|
||||
options:
|
||||
- label: I understand that Issues are used to provide feedback and solve problems, not to complain in the comments section, and will provide more information to help solve the problem.
|
||||
required: true
|
||||
- label: I confirm that I need to raise questions and discuss problems, not Bug feedback or demand suggestions.
|
||||
required: true
|
||||
- label: I have read [Github Issues](https://github.com/yeongpin/cursor-free-vip/issues) and searched for existing [open issues](https://github.com/yeongpin/cursor-free-vip/issues) and [closed issues](https://github.com/yeongpin/cursor-free-vip/issues?q=is%3Aissue%20state%3Aclosed%20), and found no similar issues.
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Which platform are you using?
|
||||
options:
|
||||
- Windows x32
|
||||
- Windows x64
|
||||
- macOS Intel
|
||||
- macOS ARM64
|
||||
- Linux x64
|
||||
- Linux ARM64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Cursor Free Vip are you running?
|
||||
placeholder: For example v1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Your question
|
||||
description: Please describe your problem as detailed as possible
|
||||
placeholder: Please explain your question as clearly as possible...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Any other related information, screenshots, or code examples
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Priority
|
||||
description: How urgent is this issue for you?
|
||||
options:
|
||||
- Low (I'll look at it when I have time)
|
||||
- Medium (I hope to get an answer soon)
|
||||
- High (It blocks my work)
|
||||
validations:
|
||||
required: true
|
||||
53
.github/workflows/build.yml
vendored
53
.github/workflows/build.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
version:
|
||||
description: 'Version number (e.g. 1.0.9)'
|
||||
required: true
|
||||
default: '1.7.02'
|
||||
default: '1.7.06'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
|
||||
build-linux:
|
||||
build-linux-x64:
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -120,20 +120,55 @@ jobs:
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Build Linux executable
|
||||
- name: Build Linux x64 executable
|
||||
env:
|
||||
VERSION: ${{ env.VERSION }}
|
||||
run: |
|
||||
pyinstaller build.spec
|
||||
mv "dist/CursorFreeVIP_${{ env.VERSION }}_linux" "dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64"
|
||||
echo "Contents of dist directory:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Upload Linux artifact
|
||||
- name: Upload Linux x64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux_x64
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64
|
||||
|
||||
build-linux-arm64:
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: arm64
|
||||
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build in ARM64 Docker container
|
||||
run: |
|
||||
docker run --rm --platform linux/arm64 -v ${{ github.workspace }}:/app -w /app arm64v8/python:3.9-slim bash -c "
|
||||
apt-get update && apt-get install -y build-essential
|
||||
pip install --upgrade pip
|
||||
pip install pyinstaller
|
||||
pip install -r requirements.txt
|
||||
python -m PyInstaller build.spec
|
||||
mv /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
|
||||
"
|
||||
echo "Contents of dist directory:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Upload Linux ARM64 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_linux_arm64
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
|
||||
|
||||
build-macos-intel:
|
||||
needs: create-tag
|
||||
@@ -171,9 +206,8 @@ jobs:
|
||||
name: CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
|
||||
|
||||
create-release:
|
||||
needs: [build-windows, build-macos-arm64, build-linux, build-macos-intel]
|
||||
needs: [build-windows, build-macos-arm64, build-linux-x64, build-linux-arm64, build-macos-intel]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
@@ -201,7 +235,8 @@ jobs:
|
||||
files: |
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_windows.exe/CursorFreeVIP_${{ env.VERSION }}_windows.exe
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_arm64/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_linux/CursorFreeVIP_${{ env.VERSION }}_linux
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_x64/CursorFreeVIP_${{ env.VERSION }}_linux_x64
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_arm64/CursorFreeVIP_${{ env.VERSION }}_linux_arm64
|
||||
artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
135
CHANGELOG.md
135
CHANGELOG.md
@@ -1,10 +1,53 @@
|
||||
# Change Log
|
||||
|
||||
## v1.7.08
|
||||
1. Add: Google OAuth Authentication | 增加 Google OAuth 認證
|
||||
2. Add: GitHub OAuth Authentication | 增加 GitHub OAuth 認證
|
||||
3. Add: Lifetime Access for OAuth Users | 增加 OAuth 用戶終身訪問權限
|
||||
4. Add: OAuth Authentication Integration | 增加 OAuth 認證集成
|
||||
5. Update: Menu System with OAuth Options | 更新菜單系統,添加 OAuth 選項
|
||||
6. Add: Multi-language Support for OAuth | 增加 OAuth 多語言支持
|
||||
|
||||
## v1.7.07
|
||||
1. Add: Vietnamese Language | 增加越南語言
|
||||
2. Add: Admin Privilege Management for Windows Executable | 增加 Windows 可執行文件管理員權限
|
||||
3. Implement admin privilege detection for Windows platform | 實現 Windows 平台管理員權限檢測
|
||||
4. Add functions to check and request admin rights when running as a frozen executable | 增加檢查和請求管理員權限的功能
|
||||
5. Enhance startup process with admin privilege verification | 增強啟動過程中的管理員權限驗證
|
||||
6. Add new admin-related emoji to the EMOJI dictionary | 增加新的管理員相關表情符號到 EMOJI 字典
|
||||
7. Provide fallback mechanism for non-Windows platforms (macos and linux ) | 提供非 Windows 平台(macos 和 linux)的回退機制
|
||||
These changes make the application more user-friendly by only requesting admin privileges when necessary (when running as an executable). | 這些改進使應用程序更易於使用,只在必要時(當作為可執行文件運行時)請求管理員權限。
|
||||
|
||||
## v1.7.06
|
||||
1. Add: Update Confirm | 增加更新確認
|
||||
2. Add: Update Skipped | 增加更新跳過
|
||||
3. Add: Invalid Choice | 增加無效選擇
|
||||
4. Fix: Cursor Path | 修復 Cursor 路徑
|
||||
5. Fix: Path Encoding | 修復路徑編碼
|
||||
6. Fix: Getting Verification Code | 修復獲取驗證碼
|
||||
7. Fix: Setting Password | 修復設置密碼
|
||||
8. Fix: Disable Auto Update | 修復禁用自動更新
|
||||
9. Add Config.py | 增加 Config.py
|
||||
10. Add utils.py | 增加 utils.py
|
||||
11. Rebuild some logic | 重新構建一些邏輯
|
||||
|
||||
|
||||
## v1.7.05
|
||||
1. Fix: Cursor Version Check | 修復 Cursor 版本檢查
|
||||
2. Fix: Small Problem | 修復一些小問題
|
||||
|
||||
|
||||
## v1.7.04
|
||||
1. Hotfix: Small Problem | 修復一些小問題
|
||||
|
||||
## v1.7.03
|
||||
1. Hotfix: Small Problem | 修復一些小問題
|
||||
|
||||
## v1.7.02
|
||||
1. Fix: Cursor Path | 修復Cursor路徑
|
||||
1. Fix: Cursor Path | 修復 Cursor 路徑
|
||||
2. Add: Config File | 增加配置文件
|
||||
3. Remove: Workbench Cursor Path | 移除Workbench Cursor路徑
|
||||
4. Remove: Cursor Main JS | 移除Cursor main.js
|
||||
3. Remove: Workbench Cursor Path | 移除 Workbench Cursor 路徑
|
||||
4. Remove: Cursor Main JS | 移除 Cursor main.js
|
||||
|
||||
## v1.7.01
|
||||
- Refactoring: Extract configuration-related code from the `setup_driver` function to an independent `setup_config` function
|
||||
@@ -19,10 +62,10 @@
|
||||
2. Add: Test some Bypass Code | 測試一些繞過代碼
|
||||
|
||||
## v1.6.01
|
||||
1. Fix: Cursor Auth | 修復Cursor Auth
|
||||
1. Fix: Cursor Auth | 修復 Cursor Auth
|
||||
2. Add: Create Account Maximum Retry | 增加創建賬號最大重試次數
|
||||
3. Fix: Cursor Auth Error | 修復Cursor Auth錯誤
|
||||
4. Fix: Update Curl Faild | 修復更新Curl失敗
|
||||
3. Fix: Cursor Auth Error | 修復 Cursor Auth 錯誤
|
||||
4. Fix: Update Curl Faild | 修復更新 Curl 失敗
|
||||
|
||||
## v1.5.03
|
||||
1. HOTFIX: Stuck on starting browser | 修復啟動瀏覽器卡住問題
|
||||
@@ -45,8 +88,8 @@
|
||||
1. Add: Print Some Account Info | 增加打印一些賬號信息
|
||||
|
||||
## v1.4.07
|
||||
1. Add Removed break statements after each operation | 修改結束event後的break暫停應用
|
||||
2. Added print_menu() calls to show the menu again | 添加print_menu()調用以再次顯示菜單
|
||||
1. Add Removed break statements after each operation | 修改結束 event 後的 break 暫停應用
|
||||
2. Added print_menu() calls to show the menu again | 添加 print_menu()調用以再次顯示菜單
|
||||
3. Updated error handling to show menu instead of exiting | 更新錯誤處理以顯示菜單而不是退出
|
||||
|
||||
## v1.4.06
|
||||
@@ -61,7 +104,7 @@
|
||||
|
||||
## v1.4.05
|
||||
|
||||
1. Fix: macOS Language Detection | 修復macOS語言檢測
|
||||
1. Fix: macOS Language Detection | 修復 macOS 語言檢測
|
||||
|
||||
|
||||
## v1.4.04
|
||||
@@ -73,46 +116,46 @@
|
||||
|
||||
## v1.4.03
|
||||
|
||||
1. Switch to API-based Registration System | 改用API註冊系統替代瀏覽器操作
|
||||
2. Add Support for Latest Cursor Version | 增加支持最新版本Cursor
|
||||
1. Switch to API-based Registration System | 改用 API 註冊系統替代瀏覽器操作
|
||||
2. Add Support for Latest Cursor Version | 增加支持最新版本 Cursor
|
||||
3. Enhance Translation System | 優化多語言翻譯系統
|
||||
4. Add Database Connection Status Messages | 增加數據庫連接狀態提示
|
||||
5. Improve Error Handling for Database Operations | 改進數據庫操作的錯誤處理
|
||||
6. Add New API Integration | 新增API集成
|
||||
6. Add New API Integration | 新增 API 集成
|
||||
7. Optimize Performance and Stability | 優化性能和穩定性
|
||||
|
||||
## v1.4.01
|
||||
|
||||
1. Add Disable Cursor Auto Upgrade | 增加禁用Cursor自動升級
|
||||
1. Add Disable Cursor Auto Upgrade | 增加禁用 Cursor 自動升級
|
||||
|
||||
## v1.3.02
|
||||
|
||||
1. Add Buy Me a Coffee | 增加請我喝杯咖啡
|
||||
2. Add PayPal | 增加PayPal
|
||||
2. Add PayPal | 增加 PayPal
|
||||
3. Very Small Fix | 非常小的修復
|
||||
4. Fix main.py option number | 修復main.py選項數量
|
||||
4. Fix main.py option number | 修復 main.py 選項數量
|
||||
|
||||
## v1.3.01
|
||||
|
||||
1. Add Manual Email Input | 增加手動輸入郵箱地址
|
||||
2. Add Manual Code Input | 增加手動輸入驗證碼
|
||||
3. Fix Cursor Options | 修復Cursor選項
|
||||
3. Fix Cursor Options | 修復 Cursor 選項
|
||||
|
||||
|
||||
## v1.2.02
|
||||
|
||||
1. Add PBlock | 增加PBlock
|
||||
2. Remove uBlock0.chromium | 移除uBlock0.chromium
|
||||
1. Add PBlock | 增加 PBlock
|
||||
2. Remove uBlock0.chromium | 移除 uBlock0.chromium
|
||||
3. Optimize the logic of the script | 優化腳本邏輯
|
||||
4. Optimize Size | 優化大小
|
||||
|
||||
|
||||
## v1.2.01
|
||||
|
||||
1. Fix Cursor Cloudflare Human Verification Problem | 修復Cursor Cloudflare人機驗證問題
|
||||
1. Fix Cursor Cloudflare Human Verification Problem | 修復 Cursor Cloudflare 人機驗證問題
|
||||
2. Change to automatic registration account | 全面改為自動註冊賬號
|
||||
3. Change Mail Site | 改變郵箱網站
|
||||
4. Fix Cursor Cloudflare Problem | 修復Cursor Cloudflare問題
|
||||
4. Fix Cursor Cloudflare Problem | 修復 Cursor Cloudflare 問題
|
||||
|
||||
|
||||
## v1.1.01
|
||||
@@ -121,15 +164,15 @@
|
||||
<img src="./images/cloudflare_2025-02-12_13-43-21.png" alt="free" width="400"/><br>
|
||||
</p>
|
||||
|
||||
1. Hot Fix Cursor Cloudflare Problem | 修復Cursor Cloudflare問題
|
||||
2. Fix Cursor Cloudflare Human Verification Problem | 修復Cursor Cloudflare人機驗證問題
|
||||
1. Hot Fix Cursor Cloudflare Problem | 修復 Cursor Cloudflare 問題
|
||||
2. Fix Cursor Cloudflare Human Verification Problem | 修復 Cursor Cloudflare 人機驗證問題
|
||||
3. Remake signup logic | 重做註冊邏輯
|
||||
|
||||
|
||||
## v1.0.10
|
||||
|
||||
1. Hot Fix Mac Chrome Problem | 修復Mac Chrome問題
|
||||
2. Fix Linux Start Donet Problem | 修復Linux啟動開發者問題
|
||||
1. Hot Fix Mac Chrome Problem | 修復 Mac Chrome 問題
|
||||
2. Fix Linux Start Donet Problem | 修復 Linux 啟動開發者問題
|
||||
|
||||
|
||||
## v1.0.9
|
||||
@@ -138,15 +181,15 @@
|
||||
<img src="./images/cloudflare_2025-02-12_13-43-21.png" alt="free" width="400"/><br>
|
||||
</p>
|
||||
|
||||
1. Fixed New 0.45.x Version Reset Machine | 修復新0.45版本重置機器
|
||||
1. Fixed New 0.45.x Version Reset Machine | 修復新 0.45 版本重置機器
|
||||
2. Fix Locale Language | 修復多語言
|
||||
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
|
||||
4. Add Remake main.js | 重做main.js
|
||||
4. Add Remake main.js | 重做 main.js
|
||||
|
||||
|
||||
## v1.0.8
|
||||
|
||||
1. Fix New 0.45 Version Reset Machine | 修復新0.45版本重置機器
|
||||
1. Fix New 0.45 Version Reset Machine | 修復新 0.45 版本重置機器
|
||||
2. Fix Locale Language | 修復多語言
|
||||
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
|
||||
|
||||
@@ -167,17 +210,17 @@
|
||||
|
||||
## v1.0.6
|
||||
|
||||
1. Add Quit Cursor Option | 增加退出Cursor選項
|
||||
2. Add Recaptcha Path Patch | 增加Recaptcha路徑修復
|
||||
1. Add Quit Cursor Option | 增加退出 Cursor 選項
|
||||
2. Add Recaptcha Path Patch | 增加 Recaptcha 路徑修復
|
||||
3. Fix Admin Permission | 修復管理員權限問題
|
||||
4. Remove all need admin permission | 移除所有需要管理員權限
|
||||
|
||||
|
||||
## v1.0.5 - HotFix
|
||||
|
||||
1. Fix: Mac Browser Control | 修復Mac瀏覽器控制問題
|
||||
1. Fix: Mac Browser Control | 修復 Mac 瀏覽器控制問題
|
||||
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
|
||||
3. Add Linux Support | 增加Linux支持
|
||||
3. Add Linux Support | 增加 Linux 支持
|
||||
<p align="center">
|
||||
<img src="./images/fix_2025-01-14_21-30-43.png" alt="fix" width="400"/><br>
|
||||
</p>
|
||||
@@ -185,7 +228,7 @@
|
||||
|
||||
## v1.0.5
|
||||
|
||||
1. Remove MachineID | 移除機器碼ID
|
||||
1. Remove MachineID | 移除機器碼 ID
|
||||
2. Change to automatic registration account | 全面改為自動註冊賬號
|
||||
3. Use your own exclusive new account | 使用自己獨享的新賬號
|
||||
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
|
||||
@@ -196,16 +239,16 @@
|
||||
|
||||
## v1.0.4
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
1. Fix: Cursor's configuration | 修復 Cursor 的配置問題
|
||||
2. Fix Cloud Lame | 修復雲端慢速模式
|
||||
|
||||
|
||||
## v1.0.3
|
||||
|
||||
1. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
1. Fix: Cursor's configuration | 修復 Cursor 的配置問題
|
||||
2. Add Manual Reset Machine | 增加手動重置機器
|
||||
3. Add CDN Cloud Control WatchDog | 增加CDN雲端控制WatchDog
|
||||
4. Add Mac OS Support | 增加Mac OS支持
|
||||
3. Add CDN Cloud Control WatchDog | 增加 CDN 雲端控制 WatchDog
|
||||
4. Add Mac OS Support | 增加 Mac OS 支持
|
||||
5. 759 ++ People use , but star only a few | 759 ++人使用,但只有幾個人點贊
|
||||
<p align="center">
|
||||
<img src="./images/what_2025-01-13_13-32-54.png" alt="Why" width="400"/><br>
|
||||
@@ -217,21 +260,21 @@
|
||||
1. Fix: Some known issues | 修復了一些已知問題
|
||||
2. Add cloud control device code | 增加雲端控制設備碼
|
||||
3. Cloud reset device code | 雲端重置設備碼
|
||||
4. Remove official WatchDog monitoring | 移除官方WatchDog監控
|
||||
5. Remove Proxy official prompt | 移除Proxy 官方提示
|
||||
6. Fix: Too Many Computer | 修復Too Many Computer 問題
|
||||
4. Remove official WatchDog monitoring | 移除官方 WatchDog 監控
|
||||
5. Remove Proxy official prompt | 移除 Proxy 官方提示
|
||||
6. Fix: Too Many Computer | 修復 Too Many Computer 問題
|
||||
7. Fix Billing Issue | 修復計費問題
|
||||
8. Fix: Cursor's configuration | 修復Cursor的配置問題
|
||||
9. Fix cursor-slow mode | 修復cursor-slow模式
|
||||
8. Fix: Cursor's configuration | 修復 Cursor 的配置問題
|
||||
9. Fix cursor-slow mode | 修復 cursor-slow 模式
|
||||
|
||||
|
||||
## v1.0.1
|
||||
|
||||
1. Fix: Reset machine ID | 修復了重置機器ID的問題
|
||||
1. Fix: Reset machine ID | 修復了重置機器 ID 的問題
|
||||
2. Fix: Bypass membership check | 修復了 繞過會員檢查的問題
|
||||
3. Fix: Auto upgrade to "pro" membership | 修復了 自動升級為pro會員的問題
|
||||
4. Fix: Real-time send Token request | 修復了 實時發送Token請求的問題
|
||||
5. Fix: Reset Cursor's configuration | 修復了 重置Cursor的配置的問題
|
||||
3. Fix: Auto upgrade to "pro" membership | 修復了 自動升級為 pro 會員的問題
|
||||
4. Fix: Real-time send Token request | 修復了 實時發送 Token 請求的問題
|
||||
5. Fix: Reset Cursor's configuration | 修復了 重置 Cursor 的配置的問題
|
||||
|
||||
|
||||
|
||||
@@ -243,7 +286,7 @@
|
||||
<p align="center">
|
||||
<img src="./images/pro_2025-01-11_00-51-07.png" alt="Cursor Pro Logo" width="400"/><br>
|
||||
</p>
|
||||
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下MachineID 聯繫作者
|
||||
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下 MachineID 聯繫作者
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
|
||||
99
README.md
99
README.md
@@ -1,4 +1,5 @@
|
||||
# ➤ Cursor Free VIP
|
||||
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200" style="border-radius: 6px;"/>
|
||||
@@ -12,45 +13,50 @@
|
||||
[](https://github.com/yeongpin/cursor-free-vip/releases/latest)
|
||||
|
||||
</p>
|
||||
<h4>Support Latest 0.46.9 Version | 支持最新0.46.9本</h4>
|
||||
<h4>Support Latest 0.47.x Version | 支持最新 0.47.x 版本</h4>
|
||||
|
||||
This is a tool to automatically register , support Windows and macOS systems, complete Auth verification, and reset Cursor's configuration.
|
||||
This is a tool to automatically register, support Windows and macOS systems, complete Auth verification, and reset
|
||||
Cursor's configuration.
|
||||
|
||||
這是一個自動化工具,自動註冊 ,支持 Windows 和 macOS 系統,完成Auth驗證,重置Cursor的配置。
|
||||
這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。
|
||||
|
||||
<p align="center">
|
||||
<img src="./images/new_2025-02-27_10-42-44.png" alt="new" width="400" style="border-radius: 6px;"/><br>
|
||||
</p>
|
||||
|
||||
##### If you dont have google chrome , you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
|
||||
##### If you don't have Google Chrome, you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
|
||||
|
||||
##### 如果沒有Google Chrome,可以從[這裡](https://www.google.com/intl/en_pk/chrome/)下載
|
||||
##### 如果沒有 Google Chrome,可以從[這裡](https://www.google.com/intl/en_pk/chrome/)下載
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
## 🔄 Change Log | 更新日志
|
||||
|
||||
[Watch Change Log | 查看更新日志](CHANGELOG.md)
|
||||
|
||||
## ✨ Features | 功能特點
|
||||
|
||||
* Automatically register Cursor membership<br>自動註冊Cursor會員<br>
|
||||
* 🌟 Google OAuth Authentication with Lifetime Access<br>使用 Google OAuth 認證(終身訪問)<br>
|
||||
|
||||
* ⭐ GitHub OAuth Authentication with Lifetime Access<br>使用 GitHub OAuth 認證(終身訪問)<br>
|
||||
|
||||
* Automatically register Cursor membership<br>自動註冊 Cursor 會員<br>
|
||||
|
||||
* Support Windows and macOS systems<br>支持 Windows 和 macOS 系統<br>
|
||||
|
||||
* Complete Auth verification<br>完成Auth驗證<br>
|
||||
* Complete Auth verification<br>完成 Auth 驗證<br>
|
||||
|
||||
* Reset Cursor's configuration<br>重置Cursor的配置<br>
|
||||
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
|
||||
|
||||
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
|
||||
|
||||
## 💻 System Support | 系統支持
|
||||
|
||||
|Windows|x64|✅|macOS|Intel|✅|
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
|Windows|x86|✅|macOS|Apple Silicon|✅|
|
||||
|Linux|x64|✅|Linux|x86|✅|
|
||||
|Linux|ARM64|✅|Linux|ARM64|✅|
|
||||
| Windows | x64 | ✅ | macOS | Intel | ✅ |
|
||||
|:-------:|:-----:|:-:|:-----:|:-------------:|:-:|
|
||||
| Windows | x86 | ✅ | macOS | Apple Silicon | ✅ |
|
||||
| Linux | x64 | ✅ | Linux | x86 | ✅ |
|
||||
| Linux | ARM64 | ✅ | Linux | ARM64 | ✅ |
|
||||
|
||||
## 👀 How to use | 如何使用
|
||||
|
||||
@@ -58,28 +64,34 @@ This is a tool to automatically register , support Windows and macOS systems, co
|
||||
<summary><b>⭐ Auto Run Script | 腳本自動化運行</b></summary>
|
||||
|
||||
**Linux/macOS**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh -o install.sh && chmod +x install.sh && ./install.sh
|
||||
```
|
||||
|
||||
**Windows**
|
||||
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>⭐ Manual Reset Machine | 手動運行重置機器</b></summary>
|
||||
|
||||
**Linux/macOS**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/reset.sh | sudo bash
|
||||
```
|
||||
|
||||
**Windows**
|
||||
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/reset.ps1 | iex
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
2. If you want to stop the script, please press Ctrl+C<br>要停止腳本,請按 Ctrl+C
|
||||
@@ -88,6 +100,8 @@ irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/rese
|
||||
|
||||
📝 Config | 文件配置
|
||||
`Win / Macos / Linux Path | 路徑 [Documents/.cursor-free-vip/config.ini]`
|
||||
<details>
|
||||
<summary><b>⭐ Config | 文件配置</b></summary>
|
||||
|
||||
```
|
||||
[Chrome]
|
||||
@@ -101,11 +115,46 @@ handle_turnstile_time = 2
|
||||
handle_turnstile_random_time = 1-3
|
||||
|
||||
[OSPaths]
|
||||
# Storage Path | 存儲路徑
|
||||
storage_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/storage.json
|
||||
# SQLite Path | SQLite路徑
|
||||
sqlite_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/state.vscdb
|
||||
# Machine ID Path | 機器ID路徑
|
||||
machine_id_path = /Users/username/Library/Application Support/Cursor/machineId
|
||||
|
||||
[Timing]
|
||||
# Min Random Time | 最小隨機時間
|
||||
min_random_time = 0.1
|
||||
# Max Random Time | 最大隨機時間
|
||||
max_random_time = 0.8
|
||||
# Page Load Wait | 頁面加載等待時間
|
||||
page_load_wait = 0.1-0.8
|
||||
# Input Wait | 輸入等待時間
|
||||
input_wait = 0.3-0.8
|
||||
# Submit Wait | 提交等待時間
|
||||
submit_wait = 0.5-1.5
|
||||
# Verification Code Input | 驗證碼輸入等待時間
|
||||
verification_code_input = 0.1-0.3
|
||||
# Verification Success Wait | 驗證成功等待時間
|
||||
verification_success_wait = 2-3
|
||||
# Verification Retry Wait | 驗證重試等待時間
|
||||
verification_retry_wait = 2-3
|
||||
# Email Check Initial Wait | 郵件檢查初始等待時間
|
||||
email_check_initial_wait = 4-6
|
||||
# Email Refresh Wait | 郵件刷新等待時間
|
||||
email_refresh_wait = 2-4
|
||||
# Settings Page Load Wait | 設置頁面加載等待時間
|
||||
settings_page_load_wait = 1-2
|
||||
# Failed Retry Time | 失敗重試時間
|
||||
failed_retry_time = 0.5-1
|
||||
# Retry Interval | 重試間隔
|
||||
retry_interval = 8-12
|
||||
# Max Timeout | 最大超時時間
|
||||
max_timeout = 160
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
* Use administrator to run the script <br>請使用管理員身份運行腳本
|
||||
|
||||
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>
|
||||
@@ -114,15 +163,11 @@ machine_id_path = /Users/username/Library/Application Support/Cursor/machineId
|
||||
|
||||
* Please comply with the relevant software usage terms when using this tool <br>使用本工具時請遵守相關軟件使用條款
|
||||
|
||||
|
||||
|
||||
## 🚨 Common Issues | 常見問題
|
||||
|
||||
|如果遇到權限問題,請確保:| 此腳本以管理員身份運行 |
|
||||
|:---:|:---:|
|
||||
|If you encounter permission issues, please ensure: | This script is run with administrator privileges |
|
||||
|
||||
|
||||
| 如果遇到權限問題,請確保: | 此腳本以管理員身份運行 |
|
||||
|:--------------------------------------------------:|:------------------------------------------------:|
|
||||
| If you encounter permission issues, please ensure: | This script is run with administrator privileges |
|
||||
|
||||
## 🤩 Contribution | 貢獻
|
||||
|
||||
@@ -134,12 +179,12 @@ machine_id_path = /Users/username/Library/Application Support/Cursor/machineId
|
||||
</a>
|
||||
<br /><br />
|
||||
|
||||
|
||||
## 📩 Disclaimer | 免責聲明
|
||||
|
||||
本工具僅供學習和研究使用,使用本工具所產生的任何後果由使用者自行承擔。 <br>
|
||||
|
||||
This tool is only for learning and research purposes, and any consequences arising from the use of this tool are borne by the user.
|
||||
This tool is only for learning and research purposes, and any consequences arising from the use of this tool are borne
|
||||
by the user.
|
||||
|
||||
## 💰 Buy Me a Coffee | 請我喝杯咖啡
|
||||
|
||||
@@ -166,9 +211,5 @@ This tool is only for learning and research purposes, and any consequences arisi
|
||||
|
||||
## 📝 License | 授權
|
||||
|
||||
本項目採用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 授權。
|
||||
本項目採用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 授權。
|
||||
Please refer to the [LICENSE](LICENSE.md) file for details.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
oakon.com
|
||||
famamail.com
|
||||
2925.com
|
||||
indigobook.com
|
||||
teihu.com
|
||||
|
||||
95
browser.py
95
browser.py
@@ -1,95 +0,0 @@
|
||||
from DrissionPage import ChromiumOptions, ChromiumPage
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
|
||||
|
||||
class BrowserManager:
|
||||
def __init__(self, noheader=False):
|
||||
self.browser = None
|
||||
self.noheader = noheader
|
||||
|
||||
def init_browser(self):
|
||||
"""初始化浏览器"""
|
||||
co = self._get_browser_options()
|
||||
|
||||
# 如果设置了 noheader,添加相应的参数
|
||||
if self.noheader:
|
||||
co.set_argument('--headless=new')
|
||||
|
||||
self.browser = ChromiumPage(co)
|
||||
return self.browser
|
||||
|
||||
def _get_browser_options(self):
|
||||
"""获取浏览器配置"""
|
||||
co = ChromiumOptions()
|
||||
try:
|
||||
extension_path = self._get_extension_path()
|
||||
co.add_extension(extension_path)
|
||||
co.set_argument("--allow-extensions-in-incognito")
|
||||
|
||||
extension_block_path = self.get_extension_block()
|
||||
co.add_extension(extension_block_path)
|
||||
co.set_argument("--allow-extensions-in-incognito")
|
||||
except FileNotFoundError as e:
|
||||
logging.warning(f"警告: {e}")
|
||||
|
||||
# 设置更真实的用户代理
|
||||
co.set_user_agent(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
# 基本设置
|
||||
co.set_pref("credentials_enable_service", False)
|
||||
|
||||
# 随机端口
|
||||
co.auto_port()
|
||||
|
||||
# 系统特定设置
|
||||
if sys.platform == "darwin": # macOS
|
||||
co.set_argument("--disable-gpu")
|
||||
co.set_argument("--no-sandbox")
|
||||
elif sys.platform == "win32": # Windows
|
||||
co.set_argument("--disable-software-rasterizer")
|
||||
|
||||
# 设置窗口大小
|
||||
window_width = random.randint(1024, 1920)
|
||||
window_height = random.randint(768, 1080)
|
||||
co.set_argument(f"--window-size={window_width},{window_height}")
|
||||
|
||||
return co
|
||||
|
||||
def _get_extension_path(self):
|
||||
"""获取插件路径"""
|
||||
root_dir = os.getcwd()
|
||||
extension_path = os.path.join(root_dir, "turnstilePatch")
|
||||
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
|
||||
|
||||
if not os.path.exists(extension_path):
|
||||
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||
|
||||
return extension_path
|
||||
|
||||
def get_extension_block(self):
|
||||
"""获取插件路径"""
|
||||
root_dir = os.getcwd()
|
||||
extension_path = os.path.join(root_dir, "PBlock")
|
||||
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
extension_path = os.path.join(sys._MEIPASS, "PBlock")
|
||||
|
||||
if not os.path.exists(extension_path):
|
||||
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||
|
||||
return extension_path
|
||||
|
||||
def quit(self):
|
||||
"""关闭浏览器"""
|
||||
if self.browser:
|
||||
try:
|
||||
self.browser.quit()
|
||||
except:
|
||||
pass
|
||||
12
build.spec
12
build.spec
@@ -29,15 +29,19 @@ a = Analysis(
|
||||
('cursor_auth.py', '.'),
|
||||
('reset_machine_manual.py', '.'),
|
||||
('cursor_register.py', '.'),
|
||||
('browser.py', '.'),
|
||||
('control.py', '.'),
|
||||
('new_signup.py', '.'),
|
||||
('new_tempemail.py', '.'),
|
||||
('quit_cursor.py', '.'),
|
||||
('cursor_register_manual.py', '.'),
|
||||
('.env', '.')
|
||||
],
|
||||
hiddenimports=[
|
||||
'cursor_auth',
|
||||
'reset_machine_manual',
|
||||
'browser',
|
||||
'control'
|
||||
'new_signup',
|
||||
'new_tempemail',
|
||||
'quit_cursor',
|
||||
'cursor_register_manual'
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
|
||||
117
config.py
Normal file
117
config.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import os
|
||||
import sys
|
||||
import configparser
|
||||
from colorama import Fore, Style
|
||||
from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path
|
||||
|
||||
def setup_config(translator=None):
|
||||
"""Setup configuration file and return config object"""
|
||||
try:
|
||||
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
|
||||
config_file = os.path.join(config_dir, "config.ini")
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# Default configuration
|
||||
default_config = {
|
||||
'Chrome': {
|
||||
'chromepath': get_default_chrome_path()
|
||||
},
|
||||
'Turnstile': {
|
||||
'handle_turnstile_time': '2',
|
||||
'handle_turnstile_random_time': '1-3'
|
||||
},
|
||||
'Timing': {
|
||||
'min_random_time': '0.1',
|
||||
'max_random_time': '0.8',
|
||||
'page_load_wait': '0.1-0.8',
|
||||
'input_wait': '0.3-0.8',
|
||||
'submit_wait': '0.5-1.5',
|
||||
'verification_code_input': '0.1-0.3',
|
||||
'verification_success_wait': '2-3',
|
||||
'verification_retry_wait': '2-3',
|
||||
'email_check_initial_wait': '4-6',
|
||||
'email_refresh_wait': '2-4',
|
||||
'settings_page_load_wait': '1-2',
|
||||
'failed_retry_time': '0.5-1',
|
||||
'retry_interval': '8-12',
|
||||
'max_timeout': '160'
|
||||
}
|
||||
}
|
||||
|
||||
# Add system-specific path configuration
|
||||
if sys.platform == "win32":
|
||||
appdata = os.getenv("APPDATA")
|
||||
localappdata = os.getenv("LOCALAPPDATA", "")
|
||||
default_config['WindowsPaths'] = {
|
||||
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
|
||||
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
|
||||
'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")
|
||||
}
|
||||
elif sys.platform == "darwin":
|
||||
default_config['MacPaths'] = {
|
||||
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
|
||||
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
|
||||
'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")
|
||||
}
|
||||
elif sys.platform == "linux":
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
|
||||
|
||||
default_config['LinuxPaths'] = {
|
||||
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")),
|
||||
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")),
|
||||
'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"),
|
||||
'cursor_path': get_linux_cursor_path(),
|
||||
'updater_path': os.path.expanduser("~/.config/cursor-updater")
|
||||
}
|
||||
|
||||
# Read existing configuration and merge
|
||||
if os.path.exists(config_file):
|
||||
config.read(config_file, encoding='utf-8')
|
||||
config_modified = False
|
||||
|
||||
for section, options in default_config.items():
|
||||
if not config.has_section(section):
|
||||
config.add_section(section)
|
||||
config_modified = True
|
||||
for option, value in options.items():
|
||||
if not config.has_option(section, option):
|
||||
config.set(section, option, str(value))
|
||||
config_modified = True
|
||||
if translator:
|
||||
print(f"{Fore.YELLOW}ℹ️ {translator.get('register.config_option_added', option=f'{section}.{option}')}{Style.RESET_ALL}")
|
||||
|
||||
if config_modified:
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
config.write(f)
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.config_updated')}{Style.RESET_ALL}")
|
||||
else:
|
||||
for section, options in default_config.items():
|
||||
config.add_section(section)
|
||||
for option, value in options.items():
|
||||
config.set(section, option, str(value))
|
||||
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
config.write(f)
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}")
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def get_config(translator=None):
|
||||
"""Get existing config or create new one"""
|
||||
return setup_config(translator)
|
||||
181
control.py
181
control.py
@@ -1,181 +0,0 @@
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
# 初始化colorama
|
||||
init()
|
||||
|
||||
# 定义emoji常量
|
||||
EMOJI = {
|
||||
'MAIL': '📧',
|
||||
'REFRESH': '🔄',
|
||||
'SUCCESS': '✅',
|
||||
'ERROR': '❌',
|
||||
'INFO': 'ℹ️',
|
||||
'CODE': '📱'
|
||||
}
|
||||
|
||||
class BrowserControl:
|
||||
def __init__(self, browser, translator=None):
|
||||
self.browser = browser
|
||||
self.translator = translator # 保存translator
|
||||
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
|
||||
self.current_tab = None # 当前标签页
|
||||
self.signup_tab = None # 注册标签页
|
||||
self.email_tab = None # 邮箱标签页
|
||||
|
||||
def create_new_tab(self):
|
||||
"""创建新标签页"""
|
||||
try:
|
||||
# 保存当前标签页
|
||||
self.current_tab = self.browser
|
||||
|
||||
# 创建新的浏览器实例
|
||||
from browser import BrowserManager
|
||||
browser_manager = BrowserManager()
|
||||
new_browser = browser_manager.init_browser()
|
||||
|
||||
# 保存新标签页
|
||||
self.signup_tab = new_browser
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.create_new_tab_success')}{Style.RESET_ALL}")
|
||||
return new_browser
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.create_new_tab_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
|
||||
def fill_verification_code(self, code):
|
||||
"""填写验证码"""
|
||||
try:
|
||||
if not code or len(code) != 6:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.verification_code_format_error')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.fill_verification_code')}...{Style.RESET_ALL}")
|
||||
|
||||
# 记住当前标签页(邮箱页面)
|
||||
email_tab = self.browser
|
||||
|
||||
# 切换回注册页面标签
|
||||
self.switch_to_tab(self.signup_tab)
|
||||
time.sleep(1)
|
||||
|
||||
# 输入验证码
|
||||
for digit in code:
|
||||
self.browser.actions.input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.verification_code_filled')}{Style.RESET_ALL}")
|
||||
|
||||
# 等待页面加载和登录完成
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.wait_for_login')}...{Style.RESET_ALL}")
|
||||
time.sleep(5)
|
||||
|
||||
# 先访问登录页面确保登录状态
|
||||
login_url = "https://authenticator.cursor.sh"
|
||||
self.browser.get(login_url)
|
||||
time.sleep(3) # 增加等待时间
|
||||
|
||||
# 获取cookies(第一次尝试)
|
||||
token = self.get_cursor_session_token()
|
||||
if not token:
|
||||
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_token_failed')}...{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
token = self.get_cursor_session_token()
|
||||
|
||||
if token:
|
||||
self.save_token_to_file(token)
|
||||
|
||||
# 获取到token后再访问设置页面
|
||||
settings_url = "https://www.cursor.com/settings"
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_account_info')}...{Style.RESET_ALL}")
|
||||
self.browser.get(settings_url)
|
||||
time.sleep(2)
|
||||
|
||||
# 获取账户额度信息
|
||||
try:
|
||||
usage_selector = (
|
||||
"css:div.col-span-2 > div > div > div > div > "
|
||||
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
|
||||
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
|
||||
)
|
||||
usage_ele = self.browser.ele(usage_selector)
|
||||
if usage_ele:
|
||||
usage_info = usage_ele.text
|
||||
total_usage = usage_info.split("/")[-1].strip()
|
||||
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('control.account_usage_limit')}: {total_usage}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_account_usage_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
|
||||
# 切换回邮箱页面
|
||||
self.switch_to_tab(email_tab)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.fill_verification_code_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def check_and_click_turnstile(self):
|
||||
"""检查并点击 Turnstile 验证框"""
|
||||
try:
|
||||
# 等待验证框出现
|
||||
time.sleep(1)
|
||||
|
||||
# 查找验证框
|
||||
verify_checkbox = self.browser.ele('xpath://label[contains(@class, "cb-lb")]//input[@type="checkbox"]')
|
||||
if verify_checkbox:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.find_turnstile_verification_box')}...{Style.RESET_ALL}")
|
||||
verify_checkbox.click()
|
||||
time.sleep(2) # 等待验证完成
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.clicked_turnstile_verification_box')}{Style.RESET_ALL}")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.check_and_click_turnstile_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def get_cursor_session_token(self, max_attempts=3, retry_interval=2):
|
||||
"""获取Cursor会话token"""
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_cursor_session_token')}...{Style.RESET_ALL}")
|
||||
attempts = 0
|
||||
|
||||
while attempts < max_attempts:
|
||||
try:
|
||||
# 直接从浏览器对象获取cookies
|
||||
all_cookies = self.browser.get_cookies()
|
||||
|
||||
# 遍历查找目标cookie
|
||||
for cookie in all_cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
token = cookie["value"].split("%3A%3A")[1]
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.get_cursor_session_token_success')}: {token}{Style.RESET_ALL}")
|
||||
return token
|
||||
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_cursor_session_token_failed', attempts=attempts, retry_interval=retry_interval)}...{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.reach_max_attempts', max_attempts=max_attempts)}{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_cookie_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
attempts += 1
|
||||
if attempts < max_attempts:
|
||||
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.will_retry_in', retry_interval=retry_interval)}...{Style.RESET_ALL}")
|
||||
time.sleep(retry_interval)
|
||||
|
||||
return None
|
||||
|
||||
def save_token_to_file(self, token):
|
||||
"""保存token到文件"""
|
||||
try:
|
||||
with open('cursor_tokens.txt', 'a', encoding='utf-8') as f:
|
||||
f.write(f"Token: {token}\n")
|
||||
f.write("-" * 50 + "\n")
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.token_saved_to_file')}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.save_token_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
@@ -2,11 +2,12 @@ import sqlite3
|
||||
import os
|
||||
import sys
|
||||
from colorama import Fore, Style, init
|
||||
from config import get_config
|
||||
|
||||
# 初始化colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# 定义emoji和颜色常量
|
||||
# Define emoji and color constants
|
||||
EMOJI = {
|
||||
'DB': '🗄️',
|
||||
'UPDATE': '🔄',
|
||||
@@ -21,29 +22,48 @@ EMOJI = {
|
||||
class CursorAuth:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
# 判断操作系统
|
||||
if sys.platform == "win32": # Windows
|
||||
self.db_path = os.path.join(
|
||||
os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb"
|
||||
)
|
||||
elif sys.platform == 'linux':
|
||||
self.db_path = os.path.expanduser(
|
||||
"~/.config/Cursor/User/globalStorage/state.vscdb"
|
||||
)
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
self.db_path = os.path.expanduser(
|
||||
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||
)
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform')}{Style.RESET_ALL}")
|
||||
|
||||
# Get configuration
|
||||
config = get_config(translator)
|
||||
if not config:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
# Get path based on operating system
|
||||
try:
|
||||
if sys.platform == "win32": # Windows
|
||||
if not config.has_section('WindowsPaths'):
|
||||
raise ValueError("Windows paths not configured")
|
||||
self.db_path = config.get('WindowsPaths', 'sqlite_path')
|
||||
|
||||
elif sys.platform == 'linux': # Linux
|
||||
if not config.has_section('LinuxPaths'):
|
||||
raise ValueError("Linux paths not configured")
|
||||
self.db_path = config.get('LinuxPaths', 'sqlite_path')
|
||||
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
if not config.has_section('MacPaths'):
|
||||
raise ValueError("macOS paths not configured")
|
||||
self.db_path = config.get('MacPaths', 'sqlite_path')
|
||||
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
# Verify if the path exists
|
||||
if not os.path.exists(os.path.dirname(self.db_path)):
|
||||
raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
# 检查数据库文件是否存在
|
||||
# Check if the database file exists
|
||||
if not os.path.exists(self.db_path):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path)}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
# 检查文件权限
|
||||
# Check file permissions
|
||||
if not os.access(self.db_path, os.R_OK | os.W_OK):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error')}{Style.RESET_ALL}")
|
||||
return
|
||||
@@ -58,12 +78,12 @@ class CursorAuth:
|
||||
def update_auth(self, email=None, access_token=None, refresh_token=None):
|
||||
conn = None
|
||||
try:
|
||||
# 确保目录存在并设置正确权限
|
||||
# Ensure the directory exists and set the correct permissions
|
||||
db_dir = os.path.dirname(self.db_path)
|
||||
if not os.path.exists(db_dir):
|
||||
os.makedirs(db_dir, mode=0o755, exist_ok=True)
|
||||
|
||||
# 如果数据库文件不存在,创建一个新的
|
||||
# If the database file does not exist, create a new one
|
||||
if not os.path.exists(self.db_path):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
@@ -78,17 +98,17 @@ class CursorAuth:
|
||||
os.chmod(self.db_path, 0o644)
|
||||
conn.close()
|
||||
|
||||
# 重新连接数据库
|
||||
# Reconnect to the database
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 增加超时和其他优化设置
|
||||
# Add timeout and other optimization settings
|
||||
conn.execute("PRAGMA busy_timeout = 5000")
|
||||
conn.execute("PRAGMA journal_mode = WAL")
|
||||
conn.execute("PRAGMA synchronous = NORMAL")
|
||||
|
||||
# 设置要更新的键值对
|
||||
# Set the key-value pairs to update
|
||||
updates = []
|
||||
|
||||
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
|
||||
@@ -101,11 +121,11 @@ class CursorAuth:
|
||||
updates.append(("cursorAuth/refreshToken", refresh_token))
|
||||
|
||||
|
||||
# 使用事务来确保数据完整性
|
||||
# Use transactions to ensure data integrity
|
||||
cursor.execute("BEGIN TRANSACTION")
|
||||
try:
|
||||
for key, value in updates:
|
||||
# 检查键是否存在
|
||||
# Check if the key exists
|
||||
cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,))
|
||||
if cursor.fetchone()[0] == 0:
|
||||
cursor.execute("""
|
||||
|
||||
@@ -2,8 +2,6 @@ import os
|
||||
from colorama import Fore, Style, init
|
||||
import time
|
||||
import random
|
||||
from browser import BrowserManager
|
||||
from control import BrowserControl
|
||||
from cursor_auth import CursorAuth
|
||||
from reset_machine_manual import MachineIDResetter
|
||||
|
||||
@@ -35,7 +33,6 @@ class CursorRegistration:
|
||||
self.translator = translator
|
||||
# Set to display mode
|
||||
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||
self.browser_manager = BrowserManager()
|
||||
self.browser = None
|
||||
self.controller = None
|
||||
self.mail_url = "https://yopmail.com/zh/email-generator"
|
||||
@@ -45,7 +42,7 @@ class CursorRegistration:
|
||||
self.signup_tab = None
|
||||
self.email_tab = None
|
||||
|
||||
# 账号信息
|
||||
# Account information
|
||||
self.password = self._generate_password()
|
||||
# Generate first name and last name separately
|
||||
first_name = random.choice([
|
||||
@@ -160,7 +157,7 @@ class CursorRegistration:
|
||||
if usage_ele:
|
||||
total_usage = usage_ele.text.split("/")[-1].strip()
|
||||
|
||||
print(f"Total Usage: {total_usage}\n")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('register.total_usage', usage=total_usage)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
|
||||
max_attempts = 30
|
||||
retry_interval = 2
|
||||
@@ -199,17 +196,17 @@ class CursorRegistration:
|
||||
def _save_account_info(self, token, total_usage):
|
||||
"""Save Account Information to File"""
|
||||
try:
|
||||
# 先更新认证信息
|
||||
# Update authentication information first
|
||||
print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
|
||||
if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}")
|
||||
|
||||
# 重置机器ID
|
||||
# Reset machine ID
|
||||
print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
|
||||
resetter = MachineIDResetter(self.translator) # 创建实例时传入translator
|
||||
if not resetter.reset_machine_ids(): # 直接调用reset_machine_ids方法
|
||||
resetter = MachineIDResetter(self.translator) # Pass translator when creating instance
|
||||
if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly
|
||||
raise Exception("Failed to reset machine ID")
|
||||
|
||||
# Save account information to file
|
||||
|
||||
5
cursor_register_github.py
Normal file
5
cursor_register_github.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from oauth_auth import main as oauth_main
|
||||
|
||||
def main(translator=None):
|
||||
"""Handle GitHub OAuth registration"""
|
||||
oauth_main('github', translator)
|
||||
5
cursor_register_google.py
Normal file
5
cursor_register_google.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from oauth_auth import main as oauth_main
|
||||
|
||||
def main(translator=None):
|
||||
"""Handle Google OAuth registration"""
|
||||
oauth_main('google', translator)
|
||||
@@ -2,8 +2,6 @@ import os
|
||||
from colorama import Fore, Style, init
|
||||
import time
|
||||
import random
|
||||
from browser import BrowserManager
|
||||
from control import BrowserControl
|
||||
from cursor_auth import CursorAuth
|
||||
from reset_machine_manual import MachineIDResetter
|
||||
|
||||
@@ -35,7 +33,6 @@ class CursorRegistration:
|
||||
self.translator = translator
|
||||
# Set to display mode
|
||||
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||
self.browser_manager = BrowserManager()
|
||||
self.browser = None
|
||||
self.controller = None
|
||||
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
|
||||
|
||||
@@ -4,6 +4,7 @@ import platform
|
||||
import shutil
|
||||
from colorama import Fore, Style, init
|
||||
import subprocess
|
||||
from config import get_config
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
@@ -24,11 +25,24 @@ class AutoUpdateDisabler:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
self.system = platform.system()
|
||||
self.updater_paths = {
|
||||
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
|
||||
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
|
||||
"Linux": os.path.expanduser("~/.config/cursor-updater")
|
||||
}
|
||||
|
||||
# Get path from configuration file
|
||||
config = get_config(translator)
|
||||
if config:
|
||||
if self.system == "Windows":
|
||||
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
|
||||
elif self.system == "Darwin":
|
||||
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
|
||||
elif self.system == "Linux":
|
||||
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
|
||||
else:
|
||||
# If configuration loading fails, use default paths
|
||||
self.updater_paths = {
|
||||
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
|
||||
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
|
||||
"Linux": os.path.expanduser("~/.config/cursor-updater")
|
||||
}
|
||||
self.updater_path = self.updater_paths.get(self.system)
|
||||
|
||||
def _kill_cursor_processes(self):
|
||||
"""End all Cursor processes"""
|
||||
@@ -50,7 +64,7 @@ class AutoUpdateDisabler:
|
||||
def _remove_updater_directory(self):
|
||||
"""Delete updater directory"""
|
||||
try:
|
||||
updater_path = self.updater_paths.get(self.system)
|
||||
updater_path = self.updater_path
|
||||
if not updater_path:
|
||||
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||
|
||||
@@ -72,7 +86,7 @@ class AutoUpdateDisabler:
|
||||
def _create_blocking_file(self):
|
||||
"""Create blocking file"""
|
||||
try:
|
||||
updater_path = self.updater_paths.get(self.system)
|
||||
updater_path = self.updater_path
|
||||
if not updater_path:
|
||||
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"exit": "Exit Program",
|
||||
"reset": "Reset Machine ID",
|
||||
"register": "Register New Cursor Account",
|
||||
"register_google": "Register with Google Account",
|
||||
"register_github": "Register with GitHub Account",
|
||||
"register_manual": "Register Cursor with Custom Email",
|
||||
"quit": "Close Cursor Application",
|
||||
"select_language": "Change Language",
|
||||
@@ -17,7 +19,8 @@
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
"zh_tw": "繁體中文",
|
||||
"vi": "Vietnamese"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "Start Quitting Cursor",
|
||||
@@ -68,7 +71,24 @@
|
||||
"version_less_than_0_45": "Cursor Version < 0.45.0, Skip Patching getMachineId",
|
||||
"detecting_version": "Detecting Cursor Version",
|
||||
"patching_getmachineid": "Patching getMachineId",
|
||||
"version_greater_than_0_45": "Cursor Version >= 0.45.0, Patching getMachineId"
|
||||
"version_greater_than_0_45": "Cursor Version >= 0.45.0, Patching getMachineId",
|
||||
"permission_denied": "Permission Denied: {error}",
|
||||
"backup_created": "Backup Created",
|
||||
"update_success": "Update Success",
|
||||
"update_failed": "Update Failed: {error}",
|
||||
"windows_machine_guid_updated": "Windows Machine GUID Updated Successfully",
|
||||
"reading_package_json": "Reading package.json {path}",
|
||||
"invalid_json_object": "Invalid JSON Object",
|
||||
"no_version_field": "No Version Field Found in package.json",
|
||||
"version_field_empty": "Version Field is Empty",
|
||||
"invalid_version_format": "Invalid Version Format: {version}",
|
||||
"found_version": "Found Version: {version}",
|
||||
"version_parse_error": "Version Parse Error: {error}",
|
||||
"package_not_found": "Package.json Not Found: {path}",
|
||||
"check_version_failed": "Check Version Failed: {error}",
|
||||
"stack_trace": "Stack Trace",
|
||||
"version_too_low": "Cursor Version Too Low: {version} < 0.45.0"
|
||||
|
||||
},
|
||||
"register": {
|
||||
"title": "Cursor Registration Tool",
|
||||
@@ -141,7 +161,11 @@
|
||||
"verification_failed": "Verification Failed",
|
||||
"verification_error": "Verification Error: {error}",
|
||||
"config_option_added": "Config Option Added: {option}",
|
||||
"config_updated": "Config Updated"
|
||||
"config_updated": "Config Updated",
|
||||
"password_submitted": "Password Submitted",
|
||||
"total_usage": "Total Usage: {usage}",
|
||||
"setting_on_password": "Setting Password",
|
||||
"getting_code": "Getting Verification Code, Will Try in 60s"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor Auth Manager",
|
||||
@@ -251,6 +275,9 @@
|
||||
"updating": "Updating to the latest version. The program will restart automatically.",
|
||||
"up_to_date": "You are using the latest version.",
|
||||
"check_failed": "Failed to check for updates: {error}",
|
||||
"continue_anyway": "Continuing with current version..."
|
||||
"continue_anyway": "Continuing with current version...",
|
||||
"update_confirm": "Do you want to update to the latest version? (Y/n)",
|
||||
"update_skipped": "Skipping update.",
|
||||
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'."
|
||||
}
|
||||
}
|
||||
281
locales/vi.json
Normal file
281
locales/vi.json
Normal file
@@ -0,0 +1,281 @@
|
||||
{
|
||||
"menu": {
|
||||
"title": "Các Tùy Chọn Khả Dụng",
|
||||
"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_manual": "Đăng Ký Cursor Với Email Tùy Chỉnh",
|
||||
"quit": "Đóng Ứng Dụng Cursor",
|
||||
"select_language": "Thay Đổi Ngôn Ngữ",
|
||||
"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",
|
||||
"error_occurred": "Đã xảy ra lỗi: {error}. Vui lòng thử lại",
|
||||
"press_enter": "Nhấn Enter để Thoát",
|
||||
"disable_auto_update": "Tắt Tự Động Cập Nhật Cursor"
|
||||
},
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文",
|
||||
"vi": "Tiếng Việt"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "Bắt Đầu Thoát Cursor",
|
||||
"no_process": "Không Có Tiến Trình Cursor Đang Chạy",
|
||||
"terminating": "Đang Chấm Dứt Tiến Trình {pid}",
|
||||
"waiting": "Đang Chờ Tiến Trình Thoát",
|
||||
"success": "Tất Cả Tiến Trình Cursor Đã Đóng",
|
||||
"timeout": "Tiến Trình Hết Thời Gian: {pids}",
|
||||
"error": "Đã Xảy Ra Lỗi: {error}"
|
||||
},
|
||||
"reset": {
|
||||
"title": "Công Cụ Đặt Lại ID Máy Cursor",
|
||||
"checking": "Đang Kiểm Tra Tệp Cấu Hình",
|
||||
"not_found": "Không Tìm Thấy Tệp Cấu Hình",
|
||||
"no_permission": "Không Thể Đọc Hoặc Ghi Tệp Cấu Hình, Vui Lòng Kiểm Tra Quyền Tệp",
|
||||
"reading": "Đang Đọc Cấu Hình Hiện Tại",
|
||||
"creating_backup": "Đang Tạo Bản Sao Lưu Cấu Hình",
|
||||
"backup_exists": "Tệp Sao Lưu Đã Tồn Tại, Bỏ Qua Bước Sao Lưu",
|
||||
"generating": "Đang Tạo ID Máy Mới",
|
||||
"saving_json": "Đang Lưu Cấu Hình Mới Vào JSON",
|
||||
"success": "Đặt Lại ID Máy Thành Công",
|
||||
"new_id": "ID Máy Mới",
|
||||
"permission_error": "Lỗi Quyền: {error}",
|
||||
"run_as_admin": "Vui Lòng Thử Chạy Chương Trình Này Với Quyền Quản Trị",
|
||||
"process_error": "Lỗi Quá Trình Đặt Lại: {error}",
|
||||
"updating_sqlite": "Đang Cập Nhật Cơ Sở Dữ Liệu SQLite",
|
||||
"updating_pair": "Đang Cập Nhật Cặp Khóa-Giá Trị",
|
||||
"sqlite_success": "Cập Nhật Cơ Sở Dữ Liệu SQLite Thành Công",
|
||||
"sqlite_error": "Cập Nhật Cơ Sở Dữ Liệu SQLite Thất Bại: {error}",
|
||||
"press_enter": "Nhấn Enter để Thoát",
|
||||
"unsupported_os": "Hệ Điều Hành Không Được Hỗ Trợ: {os}",
|
||||
"linux_path_not_found": "Không Tìm Thấy Đường Dẫn Linux",
|
||||
"updating_system_ids": "Đang Cập Nhật ID Hệ Thống",
|
||||
"system_ids_updated": "Cập Nhật ID Hệ Thống Thành Công",
|
||||
"system_ids_update_failed": "Cập Nhật ID Hệ Thống Thất Bại: {error}",
|
||||
"windows_guid_updated": "Cập Nhật GUID Windows Thành Công",
|
||||
"windows_permission_denied": "Windows Từ Chối Quyền",
|
||||
"windows_guid_update_failed": "Cập Nhật GUID Windows Thất Bại",
|
||||
"macos_uuid_updated": "Cập Nhật UUID macOS Thành Công",
|
||||
"plutil_command_failed": "Lệnh plutil Thất Bại",
|
||||
"start_patching": "Bắt Đầu Vá getMachineId",
|
||||
"macos_uuid_update_failed": "Cập Nhật UUID macOS Thất Bại",
|
||||
"current_version": "Phiên Bản Cursor Hiện Tại: {version}",
|
||||
"patch_completed": "Vá getMachineId Hoàn Tất",
|
||||
"patch_failed": "Vá getMachineId Thất Bại: {error}",
|
||||
"version_check_passed": "Kiểm Tra Phiên Bản Cursor Đã Qua",
|
||||
"file_modified": "Tệp Đã Được Sửa Đổi",
|
||||
"version_less_than_0_45": "Phiên Bản Cursor < 0.45.0, Bỏ Qua Vá getMachineId",
|
||||
"detecting_version": "Đang Phát Hiện Phiên Bản Cursor",
|
||||
"patching_getmachineid": "Đang Vá getMachineId",
|
||||
"version_greater_than_0_45": "Phiên Bản Cursor >= 0.45.0, Đang Vá getMachineId",
|
||||
"permission_denied": "Từ Chối Quyền: {error}",
|
||||
"backup_created": "Đã Tạo Bản Sao Lưu",
|
||||
"update_success": "Cập Nhật Thành Công",
|
||||
"update_failed": "Cập Nhật Thất Bại: {error}",
|
||||
"windows_machine_guid_updated": "Cập Nhật GUID Máy Windows Thành Công",
|
||||
"reading_package_json": "Đang Đọc package.json {path}",
|
||||
"invalid_json_object": "Đối Tượng JSON Không Hợp Lệ",
|
||||
"no_version_field": "Không Tìm Thấy Trường Phiên Bản Trong package.json",
|
||||
"version_field_empty": "Trường Phiên Bản Trống",
|
||||
"invalid_version_format": "Định Dạng Phiên Bản Không Hợp Lệ: {version}",
|
||||
"found_version": "Đã Tìm Thấy Phiên Bản: {version}",
|
||||
"version_parse_error": "Lỗi Phân Tích Phiên Bản: {error}",
|
||||
"package_not_found": "Không Tìm Thấy Package.json: {path}",
|
||||
"check_version_failed": "Kiểm Tra Phiên Bản Thất Bại: {error}",
|
||||
"stack_trace": "Dấu Vết Ngăn Xếp",
|
||||
"version_too_low": "Phiên Bản Cursor Quá Thấp: {version} < 0.45.0"
|
||||
|
||||
},
|
||||
"register": {
|
||||
"title": "Công Cụ Đăng Ký Cursor",
|
||||
"start": "Bắt đầu quá trình đăng ký...",
|
||||
"handling_turnstile": "Đang xử lý xác minh bảo mật...",
|
||||
"retry_verification": "Đang thử lại xác minh...",
|
||||
"detect_turnstile": "Đang kiểm tra xác minh bảo mật...",
|
||||
"verification_success": "Xác minh bảo mật thành công",
|
||||
"starting_browser": "Đang mở trình duyệt...",
|
||||
"form_success": "Biểu mẫu đã được gửi thành công",
|
||||
"browser_started": "Trình duyệt đã được mở thành công",
|
||||
"waiting_for_second_verification": "Đang chờ xác minh email...",
|
||||
"waiting_for_verification_code": "Đang chờ mã xác minh...",
|
||||
"password_success": "Đặt mật khẩu thành công",
|
||||
"password_error": "Không thể đặt mật khẩu: {error}. Vui lòng thử lại",
|
||||
"waiting_for_page_load": "Đang tải trang...",
|
||||
"first_verification_passed": "Xác minh ban đầu thành công",
|
||||
"mailbox": "Đã truy cập hộp thư đến thành công",
|
||||
"register_start": "Bắt Đầu Đăng Ký",
|
||||
"form_submitted": "Biểu Mẫu Đã Gửi, Bắt Đầu Xác Minh...",
|
||||
"filling_form": "Điền Biểu Mẫu",
|
||||
"visiting_url": "Đang Truy Cập URL",
|
||||
"basic_info": "Thông Tin Cơ Bản Đã Gửi",
|
||||
"handle_turnstile": "Xử Lý Turnstile",
|
||||
"no_turnstile": "Không Phát Hiện Turnstile",
|
||||
"turnstile_passed": "Đã Vượt Qua Turnstile",
|
||||
"verification_start": "Bắt Đầu Lấy Mã Xác Minh",
|
||||
"verification_timeout": "Lấy Mã Xác Minh Hết Thời Gian",
|
||||
"verification_not_found": "Không Tìm Thấy Mã Xác Minh",
|
||||
"try_get_code": "Thử | {attempt} Lấy Mã Xác Minh | Thời Gian Còn Lại: {time}s",
|
||||
"get_account": "Đang Lấy Thông Tin Tài Khoản",
|
||||
"get_token": "Lấy Token Phiên Cursor",
|
||||
"token_success": "Lấy Token Thành Công",
|
||||
"token_attempt": "Thử | {attempt} lần để lấy Token | Sẽ thử lại sau {time}s",
|
||||
"token_max_attempts": "Đạt Số Lần Thử Tối Đa ({max}) | Không Thể Lấy Token",
|
||||
"token_failed": "Lấy Token Thất Bại: {error}",
|
||||
"account_error": "Lấy Thông Tin Tài Khoản Thất Bại: {error}",
|
||||
"press_enter": "Nhấn Enter để Thoát",
|
||||
"browser_start": "Đang Khởi Động Trình Duyệt",
|
||||
"open_mailbox": "Đang Mở Trang Hộp Thư",
|
||||
"email_error": "Không Thể Lấy Địa Chỉ Email",
|
||||
"setup_error": "Lỗi Thiết Lập Email: {error}",
|
||||
"start_getting_verification_code": "Bắt Đầu Lấy Mã Xác Minh, Sẽ Thử Trong 60s",
|
||||
"get_verification_code_timeout": "Lấy Mã Xác Minh Hết Thời Gian",
|
||||
"get_verification_code_success": "Lấy Mã Xác Minh Thành Công",
|
||||
"try_get_verification_code": "Thử | {attempt} Lấy Mã Xác Minh | Thời Gian Còn Lại: {remaining_time}s",
|
||||
"verification_code_filled": "Đã Điền Mã Xác Minh",
|
||||
"login_success_and_jump_to_settings_page": "Đăng Nhập Thành Công và Chuyển Đến Trang Cài Đặt",
|
||||
"detect_login_page": "Phát Hiện Trang Đăng Nhập, Bắt Đầu Đăng Nhập...",
|
||||
"cursor_registration_completed": "Đăng Ký Cursor Hoàn Tất!",
|
||||
"set_password": "Đặt Mật Khẩu",
|
||||
"basic_info_submitted": "Thông Tin Cơ Bản Đã Gửi",
|
||||
"cursor_auth_info_updated": "Thông Tin Xác Thực Cursor Đã Cập Nhật",
|
||||
"cursor_auth_info_update_failed": "Cập Nhật Thông Tin Xác Thực Cursor Thất Bại",
|
||||
"reset_machine_id": "Đặt Lại ID Máy",
|
||||
"account_info_saved": "Thông Tin Tài Khoản Đã Lưu",
|
||||
"save_account_info_failed": "Lưu Thông Tin Tài Khoản Thất Bại",
|
||||
"get_email_address": "Lấy Địa Chỉ Email",
|
||||
"update_cursor_auth_info": "Cập Nhật Thông Tin Xác Thực Cursor",
|
||||
"register_process_error": "Lỗi Quá Trình Đăng Ký: {error}",
|
||||
"setting_password": "Đang Đặt Mật Khẩu",
|
||||
"manual_code_input": "Nhập Mã Thủ Công",
|
||||
"manual_email_input": "Nhập Email Thủ Công",
|
||||
"password": "Mật Khẩu",
|
||||
"first_name": "Tên",
|
||||
"last_name": "Họ",
|
||||
"exit_signal": "Tín Hiệu Thoát",
|
||||
"email_address": "Địa Chỉ Email",
|
||||
"config_created": "Đã Tạo Cấu Hình",
|
||||
"verification_failed": "Xác Minh Thất Bại",
|
||||
"verification_error": "Lỗi Xác Minh: {error}",
|
||||
"config_option_added": "Đã Thêm Tùy Chọn Cấu Hình: {option}",
|
||||
"config_updated": "Đã Cập Nhật Cấu Hình",
|
||||
"password_submitted": "Đã Gửi Mật Khẩu",
|
||||
"total_usage": "Tổng Sử Dụng: {usage}",
|
||||
"setting_on_password": "Đang Đặt Mật Khẩu",
|
||||
"getting_code": "Đang Lấy Mã Xác Minh, Sẽ Thử Trong 60s"
|
||||
},
|
||||
"auth": {
|
||||
"title": "Trình Quản Lý Xác Thực Cursor",
|
||||
"checking_auth": "Đang Kiểm Tra Tệp Xác Thực",
|
||||
"auth_not_found": "Không Tìm Thấy Tệp Xác Thực",
|
||||
"auth_file_error": "Lỗi Tệp Xác Thực: {error}",
|
||||
"reading_auth": "Đang Đọc Tệp Xác Thực",
|
||||
"updating_auth": "Đang Cập Nhật Thông Tin Xác Thực",
|
||||
"auth_updated": "Cập Nhật Thông Tin Xác Thực Thành Công",
|
||||
"auth_update_failed": "Cập Nhật Thông Tin Xác Thực Thất Bại: {error}",
|
||||
"auth_file_created": "Đã Tạo Tệp Xác Thực",
|
||||
"auth_file_create_failed": "Tạo Tệp Xác Thực Thất Bại: {error}",
|
||||
"press_enter": "Nhấn Enter để Thoát",
|
||||
"reset_machine_id": "Đặt Lại ID Máy",
|
||||
"database_connection_closed": "Kết Nối Cơ Sở Dữ Liệu Đã Đóng",
|
||||
"database_updated_successfully": "Cập Nhật Cơ Sở Dữ Liệu Thành Công",
|
||||
"connected_to_database": "Đã Kết Nối Đến Cơ Sở Dữ Liệu",
|
||||
"updating_pair": "Đang Cập Nhật Cặp Khóa-Giá Trị",
|
||||
"db_not_found": "Không tìm thấy tệp cơ sở dữ liệu tại: {path}",
|
||||
"db_permission_error": "Không thể truy cập tệp cơ sở dữ liệu. Vui lòng kiểm tra quyền",
|
||||
"db_connection_error": "Không thể kết nối đến cơ sở dữ liệu: {error}"
|
||||
},
|
||||
"control": {
|
||||
"generate_email": "Đang Tạo Email Mới",
|
||||
"blocked_domain": "Tên Miền Bị Chặn",
|
||||
"select_domain": "Đang Chọn Tên Miền Ngẫu Nhiên",
|
||||
"copy_email": "Đang Sao Chép Địa Chỉ Email",
|
||||
"enter_mailbox": "Đang Vào Hộp Thư",
|
||||
"refresh_mailbox": "Đang Làm Mới Hộp Thư",
|
||||
"check_verification": "Đang Kiểm Tra Mã Xác Minh",
|
||||
"verification_found": "Đã Tìm Thấy Mã Xác Minh",
|
||||
"verification_not_found": "Không Tìm Thấy Mã Xác Minh",
|
||||
"browser_error": "Lỗi Điều Khiển Trình Duyệt: {error}",
|
||||
"navigation_error": "Lỗi Điều Hướng: {error}",
|
||||
"email_copy_error": "Lỗi Sao Chép Email: {error}",
|
||||
"mailbox_error": "Lỗi Hộp Thư: {error}",
|
||||
"token_saved_to_file": "Token Đã Lưu Vào cursor_tokens.txt",
|
||||
"navigate_to": "Đang Điều Hướng Đến {url}",
|
||||
"generate_email_success": "Tạo Email Thành Công",
|
||||
"select_email_domain": "Chọn Tên Miền Email",
|
||||
"select_email_domain_success": "Chọn Tên Miền Email Thành Công",
|
||||
"get_email_name": "Lấy Tên Email",
|
||||
"get_email_name_success": "Lấy Tên Email Thành Công",
|
||||
"get_email_address": "Lấy Địa Chỉ Email",
|
||||
"get_email_address_success": "Lấy Địa Chỉ Email Thành Công",
|
||||
"enter_mailbox_success": "Vào Hộp Thư Thành Công",
|
||||
"found_verification_code": "Đã Tìm Thấy Mã Xác Minh",
|
||||
"get_cursor_session_token": "Lấy Token Phiên Cursor",
|
||||
"get_cursor_session_token_success": "Lấy Token Phiên Cursor Thành Công",
|
||||
"get_cursor_session_token_failed": "Lấy Token Phiên Cursor Thất Bại",
|
||||
"save_token_failed": "Lưu Token Thất Bại",
|
||||
"database_updated_successfully": "Cập Nhật Cơ Sở Dữ Liệu Thành Công",
|
||||
"database_connection_closed": "Kết Nối Cơ Sở Dữ Liệu Đã Đóng",
|
||||
"no_valid_verification_code": "Không Có Mã Xác Minh Hợp Lệ"
|
||||
},
|
||||
"email": {
|
||||
"starting_browser": "Đang Khởi Động Trình Duyệt",
|
||||
"visiting_site": "Đang Truy Cập mail.tm",
|
||||
"create_success": "Tạo Email Thành Công",
|
||||
"create_failed": "Tạo Email Thất Bại",
|
||||
"create_error": "Lỗi Tạo Email: {error}",
|
||||
"refreshing": "Đang Làm Mới Email",
|
||||
"refresh_success": "Làm Mới Email Thành Công",
|
||||
"refresh_error": "Lỗi Làm Mới Email: {error}",
|
||||
"refresh_button_not_found": "Không Tìm Thấy Nút Làm Mới",
|
||||
"verification_found": "Đã Tìm Thấy Xác Minh",
|
||||
"verification_not_found": "Không Tìm Thấy Xác Minh",
|
||||
"verification_error": "Lỗi Xác Minh: {error}",
|
||||
"verification_code_found": "Đã Tìm Thấy Mã Xác Minh",
|
||||
"verification_code_not_found": "Không Tìm Thấy Mã Xác Minh",
|
||||
"verification_code_error": "Lỗi Mã Xác Minh: {error}",
|
||||
"address": "Địa Chỉ Email",
|
||||
"all_domains_blocked": "Tất Cả Tên Miền Bị Chặn, Đang Chuyển Dịch Vụ",
|
||||
"no_available_domains_after_filtering": "Không Có Tên Miền Khả Dụng Sau Khi Lọc",
|
||||
"switching_service": "Đang Chuyển Sang Dịch Vụ {service}",
|
||||
"domains_list_error": "Không Thể Lấy Danh Sách Tên Miền: {error}",
|
||||
"failed_to_get_available_domains": "Không Thể Lấy Tên Miền Khả Dụng",
|
||||
"domains_excluded": "Tên Miền Bị Loại Trừ: {domains}",
|
||||
"failed_to_create_account": "Không Thể Tạo Tài Khoản",
|
||||
"account_creation_error": "Lỗi Tạo Tài Khoản: {error}",
|
||||
"blocked_domains": "Tên Miền Bị Chặn: {domains}",
|
||||
"blocked_domains_loaded": "Đã Tải Tên Miền Bị Chặn: {count}",
|
||||
"blocked_domains_loaded_error": "Lỗi Tải Tên Miền Bị Chặn: {error}",
|
||||
"blocked_domains_loaded_success": "Tải Tên Miền Bị Chặn Thành Công",
|
||||
"blocked_domains_loaded_timeout": "Tải Tên Miền Bị Chặn Hết Thời Gian: {timeout}s",
|
||||
"blocked_domains_loaded_timeout_error": "Lỗi Hết Thời Gian Tải Tên Miền Bị Chặn: {error}",
|
||||
"available_domains_loaded": "Đã Tải Tên Miền Khả Dụng: {count}",
|
||||
"domains_filtered": "Tên Miền Đã Lọc: {count}",
|
||||
"trying_to_create_email": "Đang cố gắng tạo email: {email}"
|
||||
},
|
||||
"update": {
|
||||
"title": "Tắt Tự Động Cập Nhật Cursor",
|
||||
"disable_success": "Tắt Tự Động Cập Nhật Thành Công",
|
||||
"disable_failed": "Tắt Tự Động Cập Nhật Thất Bại: {error}",
|
||||
"press_enter": "Nhấn Enter để Thoát",
|
||||
"start_disable": "Bắt Đầu Tắt Tự Động Cập Nhật",
|
||||
"killing_processes": "Đang Kết Thúc Các Tiến Trình",
|
||||
"processes_killed": "Đã Kết Thúc Các Tiến Trình",
|
||||
"removing_directory": "Đang Xóa Thư Mục",
|
||||
"directory_removed": "Đã Xóa Thư Mục",
|
||||
"creating_block_file": "Đang Tạo Tệp Chặn",
|
||||
"block_file_created": "Đã Tạo Tệp Chặn"
|
||||
},
|
||||
"updater": {
|
||||
"checking": "Đang Kiểm Tra Cập Nhật...",
|
||||
"new_version_available": "Có Phiên Bản Mới! (Hiện Tại: {current}, Mới Nhất: {latest})",
|
||||
"updating": "Đang Cập Nhật Lên Phiên Bản Mới Nhất. Chương Trình Sẽ Tự Động Khởi Động Lại.",
|
||||
"up_to_date": "Bạn Đang Sử Dụng Phiên Bản Mới Nhất.",
|
||||
"check_failed": "Không Thể Kiểm Tra Cập Nhật: {error}",
|
||||
"continue_anyway": "Tiếp Tục Với Phiên Bản Hiện Tại...",
|
||||
"update_confirm": "Bạn Có Muốn Cập Nhật Lên Phiên Bản Mới Nhất Không? (Y/n)",
|
||||
"update_skipped": "Bỏ Qua Cập Nhật.",
|
||||
"invalid_choice": "Lựa Chọn Không Hợp Lệ. Vui Lòng Nhập 'Y' Hoặc 'n'."
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
"exit": "退出程序",
|
||||
"reset": "重置机器标识",
|
||||
"register": "注册新 Cursor 账号",
|
||||
"register_google": "使用 Google 账号注册",
|
||||
"register_github": "使用 GitHub 账号注册",
|
||||
"register_manual": "使用自定义邮箱注册",
|
||||
"quit": "关闭 Cursor 应用",
|
||||
"select_language": "更改语言",
|
||||
@@ -17,7 +19,8 @@
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
"zh_tw": "繁體中文",
|
||||
"vi": "Vietnamese"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "开始退出 Cursor",
|
||||
@@ -68,7 +71,23 @@
|
||||
"version_less_than_0_45": "Cursor版本 < 0.45.0,跳过getMachineId修补",
|
||||
"detecting_version": "检测Cursor版本",
|
||||
"patching_getmachineid": "修补getMachineId",
|
||||
"version_greater_than_0_45": "Cursor版本 >= 0.45.0,修补getMachineId"
|
||||
"version_greater_than_0_45": "Cursor版本 >= 0.45.0,修补getMachineId",
|
||||
"permission_denied": "权限拒绝: {error}",
|
||||
"backup_created": "备份已创建",
|
||||
"update_success": "更新成功",
|
||||
"update_failed": "更新失败: {error}",
|
||||
"windows_machine_guid_updated": "Windows机器GUID更新成功",
|
||||
"reading_package_json": "读取package.json {path}",
|
||||
"invalid_json_object": "JSON对象无效",
|
||||
"no_version_field": "package.json中没有版本字段",
|
||||
"version_field_empty": "版本字段为空",
|
||||
"invalid_version_format": "版本格式无效: {version}",
|
||||
"found_version": "找到版本: {version}",
|
||||
"version_parse_error": "版本解析错误: {error}",
|
||||
"package_not_found": "package.json未找到: {path}",
|
||||
"check_version_failed": "检查版本失败: {error}",
|
||||
"stack_trace": "堆栈跟踪",
|
||||
"version_too_low": "Cursor版本太低: {version} < 0.45.0"
|
||||
},
|
||||
"register": {
|
||||
"title": "Cursor 注册工具",
|
||||
@@ -141,7 +160,11 @@
|
||||
"verification_failed": "验证失败",
|
||||
"verification_error": "验证错误: {error}",
|
||||
"config_option_added": "配置项已添加: {option}",
|
||||
"config_updated": "配置已更新"
|
||||
"config_updated": "配置已更新",
|
||||
"password_submitted": "密码已提交",
|
||||
"total_usage": "总使用量: {usage}",
|
||||
"setting_on_password": "设置密码",
|
||||
"getting_code": "获取验证码,将在60秒内尝试..."
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 认证管理器",
|
||||
@@ -248,6 +271,9 @@
|
||||
"updating": "正在更新到最新版本。程序将自动重启。",
|
||||
"up_to_date": "您使用的是最新版本。",
|
||||
"check_failed": "检查更新失败: {error}",
|
||||
"continue_anyway": "继续使用当前版本..."
|
||||
"continue_anyway": "继续使用当前版本...",
|
||||
"update_confirm": "是否要更新到最新版本? (Y/n)",
|
||||
"update_skipped": "跳过更新。",
|
||||
"invalid_choice": "选择无效。请输入 'Y' 或 'n'."
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"zh_cn": "简体中文",
|
||||
"zh_tw": "繁體中文"
|
||||
"zh_tw": "繁體中文",
|
||||
"vi": "Vietnamese"
|
||||
},
|
||||
"quit_cursor": {
|
||||
"start": "開始退出 Cursor",
|
||||
@@ -68,8 +69,25 @@
|
||||
"version_less_than_0_45": "Cursor版本 < 0.45.0,跳过getMachineId修补",
|
||||
"detecting_version": "檢測Cursor版本",
|
||||
"patching_getmachineid": "修補getMachineId",
|
||||
"version_greater_than_0_45": "Cursor版本 >= 0.45.0,修補getMachineId"
|
||||
"version_greater_than_0_45": "Cursor版本 >= 0.45.0,修補getMachineId",
|
||||
"permission_denied": "權限拒絕: {error}",
|
||||
"backup_created": "備份已創建",
|
||||
"update_success": "更新成功",
|
||||
"update_failed": "更新失敗: {error}",
|
||||
"windows_machine_guid_updated": "Windows機器GUID更新成功",
|
||||
"reading_package_json": "讀取package.json {path}",
|
||||
"invalid_json_object": "JSON對象無效",
|
||||
"no_version_field": "package.json中沒有版本字段",
|
||||
"version_field_empty": "版本字段為空",
|
||||
"invalid_version_format": "版本格式無效: {version}",
|
||||
"found_version": "找到版本: {version}",
|
||||
"version_parse_error": "版本解析錯誤: {error}",
|
||||
"package_not_found": "package.json未找到: {path}",
|
||||
"check_version_failed": "檢查版本失敗: {error}",
|
||||
"stack_trace": "堆疊跟踪",
|
||||
"version_too_low": "Cursor版本太低: {version} < 0.45.0"
|
||||
},
|
||||
|
||||
"register": {
|
||||
"title": "Cursor 註冊工具",
|
||||
"start": "正在啟動註冊流程...",
|
||||
@@ -122,7 +140,11 @@
|
||||
"verification_failed": "驗證失敗",
|
||||
"verification_error": "驗證錯誤: {error}",
|
||||
"config_option_added": "配置項已添加: {option}",
|
||||
"config_updated": "配置已更新"
|
||||
"config_updated": "配置已更新",
|
||||
"password_submitted": "密碼已提交",
|
||||
"total_usage": "總使用量: {usage}",
|
||||
"setting_on_password": "設置密碼",
|
||||
"getting_code": "正在獲取驗證碼,將在60秒內嘗試..."
|
||||
},
|
||||
"auth": {
|
||||
"title": "Cursor 認證管理器",
|
||||
@@ -229,6 +251,9 @@
|
||||
"updating": "正在更新到最新版本。程序將自動重啟。",
|
||||
"up_to_date": "您使用的是最新版本。",
|
||||
"check_failed": "檢查更新失敗: {error}",
|
||||
"continue_anyway": "繼續使用當前版本..."
|
||||
"continue_anyway": "繼續使用當前版本...",
|
||||
"update_confirm": "是否要更新到最新版本? (Y/n)",
|
||||
"update_skipped": "跳過更新。",
|
||||
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'."
|
||||
}
|
||||
}
|
||||
114
main.py
114
main.py
@@ -9,17 +9,18 @@ import locale
|
||||
import platform
|
||||
import requests
|
||||
import subprocess
|
||||
from config import get_config
|
||||
|
||||
# 只在 Windows 系统上导入 windll
|
||||
# Only import windll on Windows systems
|
||||
if platform.system() == 'Windows':
|
||||
import ctypes
|
||||
# 只在 Windows 上导入 windll
|
||||
from ctypes import windll
|
||||
|
||||
# 初始化colorama
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# 定义emoji和颜色常量
|
||||
# Define emoji and color constants
|
||||
EMOJI = {
|
||||
"FILE": "📄",
|
||||
"BACKUP": "💾",
|
||||
@@ -30,13 +31,47 @@ EMOJI = {
|
||||
"MENU": "📋",
|
||||
"ARROW": "➜",
|
||||
"LANG": "🌐",
|
||||
"UPDATE": "🔄"
|
||||
"UPDATE": "🔄",
|
||||
"ADMIN": "🔐"
|
||||
}
|
||||
|
||||
# Function to check if running as frozen executable
|
||||
def is_frozen():
|
||||
"""Check if the script is running as a frozen executable."""
|
||||
return getattr(sys, 'frozen', False)
|
||||
|
||||
# Function to check admin privileges (Windows only)
|
||||
def is_admin():
|
||||
"""Check if the script is running with admin privileges (Windows only)."""
|
||||
if platform.system() == 'Windows':
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
except Exception:
|
||||
return False
|
||||
# Always return True for non-Windows to avoid changing behavior
|
||||
return True
|
||||
|
||||
# Function to restart with admin privileges
|
||||
def run_as_admin():
|
||||
"""Restart the current script with admin privileges (Windows only)."""
|
||||
if platform.system() != 'Windows':
|
||||
return False
|
||||
|
||||
try:
|
||||
args = [sys.executable] + sys.argv
|
||||
|
||||
# Request elevation via ShellExecute
|
||||
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} Requesting administrator privileges...{Style.RESET_ALL}")
|
||||
ctypes.windll.shell32.ShellExecuteW(None, "runas", args[0], " ".join('"' + arg + '"' for arg in args[1:]), None, 1)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to restart with admin privileges: {e}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
class Translator:
|
||||
def __init__(self):
|
||||
self.translations = {}
|
||||
self.current_language = self.detect_system_language() # 使用正确的方法名
|
||||
self.current_language = self.detect_system_language() # Use correct method name
|
||||
self.fallback_language = 'en' # Fallback language if translation is missing
|
||||
self.load_translations()
|
||||
|
||||
@@ -57,11 +92,11 @@ class Translator:
|
||||
def _detect_windows_language(self):
|
||||
"""Detect language on Windows systems"""
|
||||
try:
|
||||
# 确保我们在 Windows 上
|
||||
# Ensure we are on Windows
|
||||
if platform.system() != 'Windows':
|
||||
return 'en'
|
||||
|
||||
# 获取键盘布局
|
||||
# Get keyboard layout
|
||||
user32 = ctypes.windll.user32
|
||||
hwnd = user32.GetForegroundWindow()
|
||||
threadid = user32.GetWindowThreadProcessId(hwnd, 0)
|
||||
@@ -72,6 +107,7 @@ class Translator:
|
||||
0x0409: 'en', # English
|
||||
0x0404: 'zh_tw', # Traditional Chinese
|
||||
0x0804: 'zh_cn', # Simplified Chinese
|
||||
0x0422: 'vi', # Vietnamese
|
||||
}
|
||||
|
||||
return language_map.get(layout_id, 'en')
|
||||
@@ -95,14 +131,20 @@ class Translator:
|
||||
return 'zh_cn'
|
||||
elif system_locale.startswith('en'):
|
||||
return 'en'
|
||||
elif system_locale.startswith('vi'):
|
||||
return 'vi'
|
||||
|
||||
|
||||
# Try to get language from LANG environment variable as fallback
|
||||
env_lang = os.getenv('LANG', '').lower()
|
||||
if 'tw' in env_lang or 'hk' in env_lang:
|
||||
return 'zh_tw'
|
||||
elif 'cn' in env_lang:
|
||||
return 'zh_cn'
|
||||
elif 'vi' in env_lang:
|
||||
return 'vi'
|
||||
|
||||
|
||||
return 'en'
|
||||
except:
|
||||
return 'en'
|
||||
@@ -167,20 +209,24 @@ class Translator:
|
||||
"""Get list of available languages"""
|
||||
return list(self.translations.keys())
|
||||
|
||||
# 创建翻译器实例
|
||||
# Create translator instance
|
||||
translator = Translator()
|
||||
|
||||
def print_menu():
|
||||
"""打印菜单选项"""
|
||||
"""Print menu options"""
|
||||
print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
|
||||
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
|
||||
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')}")
|
||||
print(f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
|
||||
print(f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
|
||||
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
|
||||
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
|
||||
print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
|
||||
print(f"{Fore.YELLOW} ┗━━ 🔥 LIFETIME ACCESS ENABLED 🔥{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}")
|
||||
print(f"{Fore.YELLOW} ┗━━ 🚀 LIFETIME ACCESS ENABLED 🚀{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
|
||||
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
|
||||
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
|
||||
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
|
||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||
|
||||
def select_language():
|
||||
@@ -238,6 +284,17 @@ def check_latest_version():
|
||||
if latest_version != version:
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
|
||||
|
||||
# Ask user if they want to update
|
||||
while True:
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('updater.update_confirm', choices='Y/n')}: {Style.RESET_ALL}").lower()
|
||||
if choice in ['', 'y', 'yes']:
|
||||
break
|
||||
elif choice in ['n', 'no']:
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.update_skipped')}{Style.RESET_ALL}")
|
||||
return
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
# Execute update command based on platform
|
||||
if platform.system() == 'Windows':
|
||||
@@ -284,13 +341,28 @@ def check_latest_version():
|
||||
return
|
||||
|
||||
def main():
|
||||
# Check for admin privileges if running as executable on Windows only
|
||||
if platform.system() == 'Windows' and is_frozen() and not is_admin():
|
||||
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} Running as executable, administrator privileges required.{Style.RESET_ALL}")
|
||||
if run_as_admin():
|
||||
sys.exit(0) # Exit after requesting admin privileges
|
||||
else:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Continuing without administrator privileges.{Style.RESET_ALL}")
|
||||
|
||||
print_logo()
|
||||
|
||||
# Initialize configuration
|
||||
config = get_config(translator)
|
||||
if not config:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
check_latest_version() # Add version check before showing menu
|
||||
print_menu()
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-6')}: {Style.RESET_ALL}")
|
||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-8')}: {Style.RESET_ALL}")
|
||||
|
||||
if choice == "0":
|
||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
|
||||
@@ -305,18 +377,26 @@ def main():
|
||||
cursor_register.main(translator)
|
||||
print_menu()
|
||||
elif choice == "3":
|
||||
import cursor_register_google
|
||||
cursor_register_google.main(translator)
|
||||
print_menu()
|
||||
elif choice == "4":
|
||||
import cursor_register_github
|
||||
cursor_register_github.main(translator)
|
||||
print_menu()
|
||||
elif choice == "5":
|
||||
import cursor_register_manual
|
||||
cursor_register_manual.main(translator)
|
||||
print_menu()
|
||||
elif choice == "4":
|
||||
elif choice == "6":
|
||||
import quit_cursor
|
||||
quit_cursor.quit_cursor(translator)
|
||||
print_menu()
|
||||
elif choice == "5":
|
||||
elif choice == "7":
|
||||
if select_language():
|
||||
print_menu()
|
||||
continue
|
||||
elif choice == "6":
|
||||
elif choice == "8":
|
||||
import disable_auto_update
|
||||
disable_auto_update.run(translator)
|
||||
print_menu()
|
||||
|
||||
476
new_signup.py
476
new_signup.py
@@ -7,13 +7,14 @@ from colorama import Fore, Style
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from config import get_config
|
||||
|
||||
# 在文件开头添加全局变量
|
||||
# Add global variable at the beginning of the file
|
||||
_translator = None
|
||||
|
||||
def cleanup_chrome_processes(translator=None):
|
||||
"""清理所有Chrome相关进程"""
|
||||
print("\n正在清理Chrome进程...")
|
||||
"""Clean all Chrome related processes"""
|
||||
print("\nCleaning Chrome processes...")
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
os.system('taskkill /F /IM chrome.exe /T 2>nul')
|
||||
@@ -28,7 +29,7 @@ def cleanup_chrome_processes(translator=None):
|
||||
print(f"清理进程时出错: {e}")
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
"""处理Ctrl+C信号"""
|
||||
"""Handle Ctrl+C signal"""
|
||||
global _translator
|
||||
if _translator:
|
||||
print(f"{Fore.CYAN}{_translator.get('register.exit_signal')}{Style.RESET_ALL}")
|
||||
@@ -37,48 +38,62 @@ def signal_handler(signum, frame):
|
||||
cleanup_chrome_processes(_translator)
|
||||
os._exit(0)
|
||||
|
||||
def simulate_human_input(page, url, translator=None):
|
||||
"""访问网址"""
|
||||
def simulate_human_input(page, url, config, translator=None):
|
||||
"""Visit URL"""
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("正在访问网址...")
|
||||
|
||||
# 先访问空白页面
|
||||
# First visit blank page
|
||||
page.get('about:blank')
|
||||
time.sleep(random.uniform(1.0, 2.0))
|
||||
time.sleep(get_random_wait_time(config, 'page_load_wait'))
|
||||
|
||||
# 访问目标页面
|
||||
# Visit target page
|
||||
page.get(url)
|
||||
time.sleep(random.uniform(2.0, 3.0)) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'page_load_wait'))
|
||||
|
||||
def fill_signup_form(page, first_name, last_name, email, translator=None):
|
||||
"""填写注册表单"""
|
||||
def fill_signup_form(page, first_name, last_name, email, config, translator=None):
|
||||
"""Fill signup form"""
|
||||
try:
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}📧 {translator.get('register.filling_form')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n正在填写注册表单...")
|
||||
|
||||
# Fill first name
|
||||
first_name_input = page.ele("@name=first_name")
|
||||
if first_name_input:
|
||||
first_name_input.input(first_name)
|
||||
time.sleep(get_random_wait_time(config, 'input_wait'))
|
||||
|
||||
# Fill last name
|
||||
last_name_input = page.ele("@name=last_name")
|
||||
if last_name_input:
|
||||
last_name_input.input(last_name)
|
||||
time.sleep(get_random_wait_time(config, 'input_wait'))
|
||||
|
||||
# Fill email
|
||||
email_input = page.ele("@name=email")
|
||||
if email_input:
|
||||
email_input.input(email)
|
||||
time.sleep(get_random_wait_time(config, 'input_wait'))
|
||||
|
||||
# Click submit button
|
||||
submit_button = page.ele("@type=submit")
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(get_random_wait_time(config, 'submit_wait'))
|
||||
|
||||
# 构建带参数的URL
|
||||
encoded_email = email.replace('@', '%40')
|
||||
signup_url = f"https://authenticator.cursor.sh/sign-up/password?first_name={first_name}&last_name={last_name}&email={encoded_email}&redirect_uri=https%3A%2F%2Fcursor.com%2Fapi%2Fauth%2Fcallback"
|
||||
|
||||
# 直接访问URL
|
||||
page.get(signup_url)
|
||||
time.sleep(random.uniform(2.0, 3.0))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.form_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("表单填写完成")
|
||||
print("Form filled successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.form_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"填写表单时出错: {e}")
|
||||
print(f"Error filling form: {e}")
|
||||
return False
|
||||
|
||||
def get_default_chrome_path():
|
||||
@@ -117,113 +132,41 @@ def get_user_documents_path():
|
||||
return os.path.join("/home", sudo_user, "Documents")
|
||||
return os.path.join(os.path.expanduser("~"), "Documents")
|
||||
|
||||
def parse_random_time(time_str):
|
||||
"""解析随机时间范围配置"""
|
||||
def get_random_wait_time(config, timing_type='page_load_wait'):
|
||||
"""
|
||||
Get random wait time from config
|
||||
Args:
|
||||
config: ConfigParser object
|
||||
timing_type: Type of timing to get (page_load_wait, input_wait, submit_wait)
|
||||
Returns:
|
||||
float: Random wait time or fixed time
|
||||
"""
|
||||
try:
|
||||
if '-' in time_str:
|
||||
min_time, max_time = map(float, time_str.split('-'))
|
||||
elif ',' in time_str:
|
||||
min_time, max_time = map(float, time_str.split(','))
|
||||
else:
|
||||
min_time = max_time = float(time_str)
|
||||
return min_time, max_time
|
||||
except:
|
||||
return 1, 3 # 默认值
|
||||
|
||||
def setup_config(translator=None):
|
||||
"""Setup configuration file and return config object"""
|
||||
try:
|
||||
# Set configuration file path
|
||||
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
|
||||
config_file = os.path.join(config_dir, "config.ini")
|
||||
|
||||
# Create config directory (if it doesn't exist)
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
||||
# Read or create configuration file
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
# 默认配置
|
||||
default_config = {
|
||||
'Chrome': {
|
||||
'chromepath': get_default_chrome_path()
|
||||
},
|
||||
'Turnstile': {
|
||||
'handle_turnstile_time': '2',
|
||||
'handle_turnstile_random_time': '1-3'
|
||||
}
|
||||
}
|
||||
|
||||
# Add OS-specific path configurations
|
||||
if sys.platform == "win32":
|
||||
appdata = os.getenv("APPDATA")
|
||||
default_config['WindowsPaths'] = {
|
||||
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
|
||||
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
|
||||
'machine_id_path': os.path.join(os.getenv("APPDATA"), "Cursor", "machineId")
|
||||
}
|
||||
elif sys.platform == "darwin":
|
||||
default_config['MacPaths'] = {
|
||||
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
|
||||
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
|
||||
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId")
|
||||
}
|
||||
elif sys.platform == "linux":
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
|
||||
default_config['LinuxPaths'] = {
|
||||
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")),
|
||||
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")),
|
||||
'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId")
|
||||
}
|
||||
|
||||
if os.path.exists(config_file):
|
||||
config.read(config_file)
|
||||
config_modified = False
|
||||
|
||||
# 检查并添加缺失的配置项
|
||||
for section, options in default_config.items():
|
||||
if not config.has_section(section):
|
||||
config.add_section(section)
|
||||
config_modified = True
|
||||
for option, value in options.items():
|
||||
if not config.has_option(section, option):
|
||||
config.set(section, option, value)
|
||||
config_modified = True
|
||||
if translator:
|
||||
print(f"{Fore.YELLOW}ℹ️ {translator.get('register.config_option_added', option=f'{section}.{option}') if translator else f'添加配置项: {section}.{option}'}{Style.RESET_ALL}")
|
||||
|
||||
# 如果有新增配置项,保存文件
|
||||
if config_modified:
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
config.write(f)
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.config_updated') if translator else '配置文件已更新'}{Style.RESET_ALL}")
|
||||
else:
|
||||
# 创建新配置文件
|
||||
config = configparser.ConfigParser()
|
||||
for section, options in default_config.items():
|
||||
config.add_section(section)
|
||||
for option, value in options.items():
|
||||
config.set(section, option, value)
|
||||
if not config.has_section('Timing'):
|
||||
return random.uniform(0.1, 0.8) # Default value
|
||||
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
config.write(f)
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.config_created') if translator else '已创建配置文件'}: {config_file}{Style.RESET_ALL}")
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.config_setup_error', error=str(e)) if translator else f'配置设置出错: {str(e)}'}{Style.RESET_ALL}")
|
||||
raise
|
||||
if timing_type == 'random':
|
||||
min_time = float(config.get('Timing', 'min_random_time', fallback='0.1'))
|
||||
max_time = float(config.get('Timing', 'max_random_time', fallback='0.8'))
|
||||
return random.uniform(min_time, max_time)
|
||||
|
||||
time_value = config.get('Timing', timing_type, fallback='0.1-0.8')
|
||||
|
||||
# Check if it's a fixed time value
|
||||
if '-' not in time_value and ',' not in time_value:
|
||||
return float(time_value) # Return fixed time
|
||||
|
||||
# Process range time
|
||||
min_time, max_time = map(float, time_value.split('-' if '-' in time_value else ','))
|
||||
return random.uniform(min_time, max_time)
|
||||
except:
|
||||
return random.uniform(0.1, 0.8) # Return default value when error
|
||||
|
||||
def setup_driver(translator=None):
|
||||
"""Setup browser driver"""
|
||||
try:
|
||||
# 获取配置
|
||||
config = setup_config(translator)
|
||||
# Get config
|
||||
config = get_config(translator)
|
||||
|
||||
# Get Chrome path
|
||||
chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path())
|
||||
@@ -242,17 +185,17 @@ def setup_driver(translator=None):
|
||||
# Use incognito mode
|
||||
co.set_argument("--incognito")
|
||||
|
||||
# 设置随机端口
|
||||
# Set random port
|
||||
co.set_argument("--no-sandbox")
|
||||
|
||||
# 设置随机端口
|
||||
# Set random port
|
||||
co.auto_port()
|
||||
|
||||
# 使用有头模式(一定要设置为False,模拟人类操作)
|
||||
# Use headless mode (must be set to False, simulate human operation)
|
||||
co.headless(False)
|
||||
|
||||
try:
|
||||
# 加载插件
|
||||
# Load extension
|
||||
extension_path = os.path.join(os.getcwd(), "turnstilePatch")
|
||||
if os.path.exists(extension_path):
|
||||
co.set_argument("--allow-extensions-in-incognito")
|
||||
@@ -261,12 +204,12 @@ def setup_driver(translator=None):
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.extension_load_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"加载插件失败: {e}")
|
||||
print(f"Error loading extension: {e}")
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.starting_browser')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("正在启动浏览器...")
|
||||
print("Starting browser...")
|
||||
|
||||
page = ChromiumPage(co)
|
||||
return config, page
|
||||
@@ -275,21 +218,26 @@ def setup_driver(translator=None):
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.browser_setup_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"设置浏览器时出错: {e}")
|
||||
print(f"Error setting up browser: {e}")
|
||||
raise
|
||||
|
||||
def handle_turnstile(page, config, translator=None):
|
||||
"""处理 Turnstile 验证"""
|
||||
"""Handle Turnstile verification"""
|
||||
try:
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.handling_turnstile')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("\n正在处理 Turnstile 验证...")
|
||||
print("\nHandling Turnstile verification...")
|
||||
|
||||
# from config
|
||||
turnstile_time = float(config.get('Turnstile', 'handle_turnstile_time', fallback='2'))
|
||||
random_time_str = config.get('Turnstile', 'handle_turnstile_random_time', fallback='1-3')
|
||||
min_random_time, max_random_time = parse_random_time(random_time_str)
|
||||
|
||||
# Parse random time range
|
||||
try:
|
||||
min_time, max_time = map(float, random_time_str.split('-'))
|
||||
except:
|
||||
min_time, max_time = 1, 3 # Default value
|
||||
|
||||
max_retries = 2
|
||||
retry_count = 0
|
||||
@@ -299,14 +247,14 @@ def handle_turnstile(page, config, translator=None):
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.retry_verification', attempt=retry_count)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"第 {retry_count} 次尝试验证...")
|
||||
print(f"Attempt {retry_count} of verification...")
|
||||
|
||||
try:
|
||||
# 尝试重置 turnstile
|
||||
# Try to reset turnstile
|
||||
page.run_js("try { turnstile.reset() } catch(e) { }")
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
# 定位验证框元素
|
||||
# Locate verification box element
|
||||
challenge_check = (
|
||||
page.ele("@id=cf-turnstile", timeout=2)
|
||||
.child()
|
||||
@@ -319,10 +267,10 @@ def handle_turnstile(page, config, translator=None):
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.detect_turnstile')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("检测到验证框...")
|
||||
print("Detected verification box...")
|
||||
|
||||
# from config
|
||||
time.sleep(random.uniform(min_random_time, max_random_time))
|
||||
time.sleep(random.uniform(min_time, max_time))
|
||||
challenge_check.click()
|
||||
time.sleep(turnstile_time) # from config
|
||||
|
||||
@@ -331,49 +279,49 @@ def handle_turnstile(page, config, translator=None):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证通过!")
|
||||
print("Verification successful!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"验证尝试失败: {e}")
|
||||
print(f"Verification attempt failed: {e}")
|
||||
|
||||
# 检查是否已经验证成功
|
||||
# Check if verification has been successful
|
||||
if check_verification_success(page, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("验证通过!")
|
||||
print("Verification successful!")
|
||||
return True
|
||||
|
||||
time.sleep(random.uniform(min_random_time, max_random_time))
|
||||
time.sleep(random.uniform(min_time, max_time))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_failed')}{Style.RESET_ALL}")
|
||||
else:
|
||||
print("超出最大重试次数")
|
||||
print("Exceeded maximum retry attempts")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"验证过程出错: {e}")
|
||||
print(f"Error in verification process: {e}")
|
||||
return False
|
||||
|
||||
def check_verification_success(page, translator=None):
|
||||
"""检查验证是否成功"""
|
||||
"""Check if verification is successful"""
|
||||
try:
|
||||
# 检查是否存在后续表单元素,这表示验证已通过
|
||||
# Check if there is a subsequent form element, indicating verification has passed
|
||||
if (page.ele("@name=password", timeout=0.5) or
|
||||
page.ele("@name=email", timeout=0.5) or
|
||||
page.ele("@data-index=0", timeout=0.5) or
|
||||
page.ele("Account Settings", timeout=0.5)):
|
||||
return True
|
||||
|
||||
# 检查是否出现错误消息
|
||||
# Check if there is an error message
|
||||
error_messages = [
|
||||
'xpath://div[contains(text(), "Can\'t verify the user is human")]',
|
||||
'xpath://div[contains(text(), "Error: 600010")]',
|
||||
@@ -389,120 +337,103 @@ def check_verification_success(page, translator=None):
|
||||
return False
|
||||
|
||||
def generate_password(length=12):
|
||||
"""生成随机密码"""
|
||||
"""Generate random password"""
|
||||
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||||
return ''.join(random.choices(chars, k=length))
|
||||
|
||||
def fill_password(page, password: str, translator=None) -> bool:
|
||||
def fill_password(page, password: str, config, translator=None):
|
||||
"""
|
||||
填写密码表单
|
||||
Fill password form
|
||||
"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else 'Setting password'}{Style.RESET_ALL}")
|
||||
|
||||
# 等待密码框出现并尝试多次
|
||||
max_retries = 5
|
||||
for i in range(max_retries):
|
||||
# 检查是否出现错误信息
|
||||
if page.ele("This email is not available."):
|
||||
print(f"{Fore.RED}❌ {translator.get('register.email_used') if translator else '注册失败:邮箱已被使用'}{Style.RESET_ALL}")
|
||||
return False
|
||||
# Fill password
|
||||
password_input = page.ele("@name=password")
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_on_password')}: {password}{Style.RESET_ALL}")
|
||||
if password_input:
|
||||
password_input.input(password)
|
||||
|
||||
# 查找密码输入框
|
||||
password_input = page.ele("@name=password")
|
||||
if password_input:
|
||||
# 清除可能存在的旧值并输入新密码
|
||||
password_input.click()
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
password_input.input(password)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# 查找并点击提交按钮
|
||||
submit_button = page.ele("@type=submit")
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}")
|
||||
time.sleep(random.uniform(2, 3))
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_submit') if translator else '未找到提交按钮,重试中...'}{Style.RESET_ALL}")
|
||||
# Click submit button
|
||||
submit_button = page.ele("@type=submit")
|
||||
if submit_button:
|
||||
submit_button.click()
|
||||
time.sleep(get_random_wait_time(config, 'submit_wait'))
|
||||
|
||||
# 如果没找到密码框,等待后重试
|
||||
time.sleep(2)
|
||||
if i < max_retries - 1: # 不是最后一次尝试时才打印
|
||||
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_password', attempt=i+1) if translator else f'第 {i+1} 次尝试设置密码...'}{Style.RESET_ALL}")
|
||||
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_set_failed') if translator else '密码设置失败:超过重试次数'}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.password_submitted') if translator else 'Password submitted'}{Style.RESET_ALL}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}❌ {translator.get('register.password_error', error=str(e)) if translator else f'Error setting password: {str(e)}'}{Style.RESET_ALL}")
|
||||
|
||||
return False
|
||||
|
||||
def handle_verification_code(browser_tab, email_tab, controller, email, password, translator=None):
|
||||
"""处理验证码"""
|
||||
def handle_verification_code(browser_tab, email_tab, controller, config, translator=None):
|
||||
"""Handle verification code"""
|
||||
try:
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
|
||||
# 检查是否使用手动输入验证码
|
||||
if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式
|
||||
# Check if using manual input verification code
|
||||
if hasattr(controller, 'get_verification_code') and email_tab is None: # Manual mode
|
||||
verification_code = controller.get_verification_code()
|
||||
if verification_code:
|
||||
# 在注册页面填写验证码
|
||||
# Fill verification code in registration page
|
||||
for i, digit in enumerate(verification_code):
|
||||
browser_tab.ele(f"@data-index={i}").input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
time.sleep(get_random_wait_time(config, 'verification_code_input'))
|
||||
|
||||
print(f"{translator.get('register.verification_success')}")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
# Handle last Turnstile verification
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 访问设置页面
|
||||
print(f"{Fore.CYAN} {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
# Visit settings page
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
browser_tab.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
return True, browser_tab
|
||||
|
||||
return False, None
|
||||
|
||||
# 自动获取验证码逻辑
|
||||
# Automatic verification code logic
|
||||
elif email_tab:
|
||||
print(f"{translator.get('register.waiting_for_verification_code')}")
|
||||
time.sleep(5) # 等待验证码邮件
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
time.sleep(get_random_wait_time(config, 'email_check_initial_wait'))
|
||||
|
||||
# 使用已有的 email_tab 刷新邮箱
|
||||
# Use existing email_tab to refresh email
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'email_refresh_wait'))
|
||||
|
||||
# 检查邮箱是否有验证码邮件
|
||||
# Check if there is a verification code email
|
||||
if email_tab.check_for_cursor_email():
|
||||
verification_code = email_tab.get_verification_code()
|
||||
if verification_code:
|
||||
# 在注册页面填写验证码
|
||||
# Fill verification code in registration page
|
||||
for i, digit in enumerate(verification_code):
|
||||
browser_tab.ele(f"@data-index={i}").input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
time.sleep(get_random_wait_time(config, 'verification_code_input'))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
# Handle last Turnstile verification
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 访问设置页面
|
||||
# Visit settings page
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
browser_tab.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
return True, browser_tab
|
||||
|
||||
else:
|
||||
@@ -512,18 +443,18 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
print("最后一次验证失败")
|
||||
return False, None
|
||||
|
||||
# 获取验证码,设置超时
|
||||
# Get verification code, set timeout
|
||||
verification_code = None
|
||||
max_attempts = 20
|
||||
retry_interval = 10
|
||||
retry_interval = get_random_wait_time(config, 'retry_interval') # Use get_random_wait_time
|
||||
start_time = time.time()
|
||||
timeout = 160
|
||||
timeout = float(config.get('Timing', 'max_timeout', fallback='160')) # This can be kept unchanged because it is a fixed value
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.start_getting_verification_code')}{Style.RESET_ALL}")
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
# 检查是否超时
|
||||
# Check if timeout
|
||||
if time.time() - start_time > timeout:
|
||||
if translator:
|
||||
print(f"{Fore.RED}❌ {translator.get('register.verification_timeout')}{Style.RESET_ALL}")
|
||||
@@ -539,33 +470,33 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.try_get_code', attempt=attempt + 1, time=remaining_time)}{Style.RESET_ALL}")
|
||||
|
||||
# 刷新邮箱
|
||||
# Refresh email
|
||||
email_tab.refresh_inbox()
|
||||
time.sleep(retry_interval)
|
||||
time.sleep(retry_interval) # Use get_random_wait_time
|
||||
|
||||
if verification_code:
|
||||
# 在注册页面填写验证码
|
||||
# Fill verification code in registration page
|
||||
for i, digit in enumerate(verification_code):
|
||||
browser_tab.ele(f"@data-index={i}").input(digit)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
time.sleep(get_random_wait_time(config, 'verification_code_input'))
|
||||
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
# Handle last Turnstile verification
|
||||
if handle_turnstile(browser_tab, config, translator):
|
||||
if translator:
|
||||
print(f"{Fore.GREEN}✅ {translator.get('register.verification_success')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
|
||||
|
||||
# 直接访问设置页面
|
||||
# Visit settings page
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
|
||||
browser_tab.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # 等待页面加载
|
||||
time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
|
||||
|
||||
# 直接返回成功,让 cursor_register.py 处理账户信息获取
|
||||
# Return success directly, let cursor_register.py handle account information acquisition
|
||||
return True, browser_tab
|
||||
|
||||
else:
|
||||
@@ -581,58 +512,58 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
|
||||
return False, None
|
||||
|
||||
def handle_sign_in(browser_tab, email, password, translator=None):
|
||||
"""处理登录流程"""
|
||||
"""Handle login process"""
|
||||
try:
|
||||
# 检查是否在登录页面
|
||||
# Check if on login page
|
||||
sign_in_header = browser_tab.ele('xpath://h1[contains(text(), "Sign in")]')
|
||||
if not sign_in_header:
|
||||
return True # 如果不是登录页面,说明已经登录成功
|
||||
return True # If not on login page, it means login is successful
|
||||
|
||||
print(f"{Fore.CYAN}检测到登录页面,开始登录...{Style.RESET_ALL}")
|
||||
|
||||
# 填写邮箱
|
||||
# Fill email
|
||||
email_input = browser_tab.ele('@name=email')
|
||||
if email_input:
|
||||
email_input.input(email)
|
||||
time.sleep(1)
|
||||
|
||||
# 点击 Continue
|
||||
# Click Continue
|
||||
continue_button = browser_tab.ele('xpath://button[contains(@class, "BrandedButton") and text()="Continue"]')
|
||||
if continue_button:
|
||||
continue_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理 Turnstile 验证
|
||||
# Handle Turnstile verification
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
# 填写密码
|
||||
# Fill password
|
||||
password_input = browser_tab.ele('@name=password')
|
||||
if password_input:
|
||||
password_input.input(password)
|
||||
time.sleep(1)
|
||||
|
||||
# 点击 Sign in
|
||||
# Click Sign in
|
||||
sign_in_button = browser_tab.ele('xpath://button[@name="intent" and @value="password"]')
|
||||
if sign_in_button:
|
||||
sign_in_button.click()
|
||||
time.sleep(2)
|
||||
|
||||
# 处理最后一次 Turnstile 验证
|
||||
# Handle last Turnstile verification
|
||||
if handle_turnstile(browser_tab, translator):
|
||||
print(f"{Fore.GREEN}登录成功!{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}Login successful!{Style.RESET_ALL}")
|
||||
time.sleep(3)
|
||||
return True
|
||||
|
||||
print(f"{Fore.RED}登录失败{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}Login failed{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}登录过程出错: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.RED}Login process error: {str(e)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def main(email=None, password=None, first_name=None, last_name=None, email_tab=None, controller=None, translator=None):
|
||||
"""主函数,可以接收账号信息、邮箱标签页和翻译器"""
|
||||
"""Main function, can receive account information, email tab, and translator"""
|
||||
global _translator
|
||||
_translator = translator # 保存到全局变量
|
||||
_translator = translator # Save to global variable
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
@@ -644,62 +575,59 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}")
|
||||
|
||||
# 访问注册页面
|
||||
# Visit registration page
|
||||
url = "https://authenticator.cursor.sh/sign-up"
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
|
||||
|
||||
# 访问页面
|
||||
simulate_human_input(page, url, translator)
|
||||
# Visit page
|
||||
simulate_human_input(page, url, config, translator)
|
||||
if translator:
|
||||
print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
|
||||
time.sleep(5)
|
||||
print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
|
||||
time.sleep(get_random_wait_time(config, 'page_load_wait'))
|
||||
|
||||
# 如果没有提供账号信息,则生成随机信息
|
||||
# If account information is not provided, generate random information
|
||||
if not all([email, password, first_name, last_name]):
|
||||
first_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize()
|
||||
last_name = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=6)).capitalize()
|
||||
email = f"{first_name.lower()}{random.randint(100,999)}@example.com"
|
||||
password = generate_password()
|
||||
|
||||
# 保存账号信息
|
||||
# Save account information
|
||||
with open('test_accounts.txt', 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n{'='*50}\n")
|
||||
f.write(f"Email: {email}\n")
|
||||
f.write(f"Password: {password}\n")
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
# 填写表单
|
||||
if fill_signup_form(page, first_name, last_name, email, translator):
|
||||
# Fill form
|
||||
if fill_signup_form(page, first_name, last_name, email, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.GREEN}✅ {translator.get('register.form_submitted')}{Style.RESET_ALL}")
|
||||
|
||||
# 处理第一次 Turnstile 验证
|
||||
# Handle first Turnstile verification
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.GREEN}✅ {translator.get('register.first_verification_passed')}{Style.RESET_ALL}")
|
||||
|
||||
# 填写密码
|
||||
if fill_password(page, password, translator):
|
||||
# Fill password
|
||||
if fill_password(page, password, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
|
||||
time.sleep(2)
|
||||
|
||||
# 处理第二次 Turnstile 验证
|
||||
print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
|
||||
|
||||
# Handle second Turnstile verification
|
||||
if handle_turnstile(page, config, translator):
|
||||
if translator:
|
||||
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
if handle_verification_code(page, email_tab, controller, email, password, translator):
|
||||
print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
|
||||
if handle_verification_code(page, email_tab, controller, config, translator):
|
||||
success = True
|
||||
return True, page # 返回浏览器实例
|
||||
return True, page
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.RED}❌ {translator.get('register.verification_code_processing_failed') if translator else 'Verification code processing failed'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else 'Second verification failed'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.RED}❌ {translator.get('register.second_verification_failed') if translator else 'Second verification failed'}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.RED}❌ {translator.get('register.first_verification_failed') if translator else 'First verification failed'}{Style.RESET_ALL}")
|
||||
|
||||
return False, None
|
||||
|
||||
@@ -707,7 +635,7 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
|
||||
print(f"发生错误: {e}")
|
||||
return False, None
|
||||
finally:
|
||||
if page and not success: # 只在失败时清理
|
||||
if page and not success: # Only clean up when failed
|
||||
try:
|
||||
page.quit()
|
||||
except:
|
||||
@@ -715,4 +643,4 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
|
||||
cleanup_chrome_processes(translator)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # 直接运行时不传参数,使用随机生成的信息
|
||||
main() # Run without parameters, use randomly generated information
|
||||
@@ -263,7 +263,7 @@ class NewTempEmail:
|
||||
return False
|
||||
|
||||
def check_for_cursor_email(self):
|
||||
"""检查是否有 Cursor 的验证邮件"""
|
||||
"""Check if there is a Cursor verification email"""
|
||||
try:
|
||||
# Use API to get email list
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
|
||||
826
oauth_auth.py
Normal file
826
oauth_auth.py
Normal file
@@ -0,0 +1,826 @@
|
||||
import os
|
||||
from colorama import Fore, Style, init
|
||||
import time
|
||||
import random
|
||||
import webbrowser
|
||||
import sys
|
||||
import json
|
||||
from DrissionPage import ChromiumPage, ChromiumOptions
|
||||
from cursor_auth import CursorAuth
|
||||
from utils import get_random_wait_time, get_default_chrome_path
|
||||
from config import get_config
|
||||
import platform
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
|
||||
# Define emoji constants
|
||||
EMOJI = {
|
||||
'START': '🚀',
|
||||
'OAUTH': '🔑',
|
||||
'SUCCESS': '✅',
|
||||
'ERROR': '❌',
|
||||
'WAIT': '⏳',
|
||||
'INFO': 'ℹ️'
|
||||
}
|
||||
|
||||
class OAuthHandler:
|
||||
def __init__(self, translator=None):
|
||||
self.translator = translator
|
||||
self.config = get_config(translator)
|
||||
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||
self.browser = None
|
||||
|
||||
def _get_active_profile(self, user_data_dir):
|
||||
"""Find the existing default/active Chrome profile"""
|
||||
try:
|
||||
# List all profile directories
|
||||
profiles = []
|
||||
for item in os.listdir(user_data_dir):
|
||||
if item == 'Default' or (item.startswith('Profile ') and os.path.isdir(os.path.join(user_data_dir, item))):
|
||||
profiles.append(item)
|
||||
|
||||
if not profiles:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} No Chrome profiles found, using Default{Style.RESET_ALL}")
|
||||
return 'Default'
|
||||
|
||||
# First check if Default profile exists
|
||||
if 'Default' in profiles:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found Default Chrome profile{Style.RESET_ALL}")
|
||||
return 'Default'
|
||||
|
||||
# If no Default profile, check Local State for last used profile
|
||||
local_state_path = os.path.join(user_data_dir, 'Local State')
|
||||
if os.path.exists(local_state_path):
|
||||
with open(local_state_path, 'r', encoding='utf-8') as f:
|
||||
local_state = json.load(f)
|
||||
|
||||
# Get info about last used profile
|
||||
profile_info = local_state.get('profile', {})
|
||||
last_used = profile_info.get('last_used', '')
|
||||
info_cache = profile_info.get('info_cache', {})
|
||||
|
||||
# Try to find an active profile
|
||||
for profile in profiles:
|
||||
profile_path = profile.replace('\\', '/')
|
||||
if profile_path in info_cache:
|
||||
#print(f"{Fore.CYAN}{EMOJI['INFO']} Using existing Chrome profile: {profile}{Style.RESET_ALL}")
|
||||
return profile
|
||||
|
||||
# If no profile found in Local State, use the first available profile
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Using first available Chrome profile: {profiles[0]}{Style.RESET_ALL}")
|
||||
return profiles[0]
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}")
|
||||
return 'Default'
|
||||
|
||||
def setup_browser(self):
|
||||
"""Setup browser for OAuth flow using active profile"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}")
|
||||
|
||||
# Platform-specific initialization
|
||||
platform_name = platform.system().lower()
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}")
|
||||
|
||||
# Kill existing browser processes
|
||||
self._kill_browser_processes()
|
||||
|
||||
# Get browser paths and user data directory
|
||||
user_data_dir = self._get_user_data_directory()
|
||||
chrome_path = self._get_browser_path()
|
||||
|
||||
if not chrome_path:
|
||||
raise Exception(f"No compatible browser found. Please install Google Chrome or Chromium.\nSupported browsers for {platform_name}:\n" +
|
||||
"- Windows: Google Chrome, Chromium\n" +
|
||||
"- macOS: Google Chrome, Chromium\n" +
|
||||
"- Linux: Google Chrome, Chromium, chromium-browser")
|
||||
|
||||
# Get active profile
|
||||
active_profile = self._get_active_profile(user_data_dir)
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Using browser profile: {active_profile}{Style.RESET_ALL}")
|
||||
|
||||
# Configure browser options
|
||||
co = self._configure_browser_options(chrome_path, user_data_dir, active_profile)
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}")
|
||||
self.browser = ChromiumPage(co)
|
||||
|
||||
# Verify browser launched successfully
|
||||
if not self.browser:
|
||||
raise Exception("Failed to initialize browser instance")
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Browser setup failed: {str(e)}{Style.RESET_ALL}")
|
||||
if "DevToolsActivePort file doesn't exist" in str(e):
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running with administrator/root privileges{Style.RESET_ALL}")
|
||||
elif "Chrome failed to start" in str(e):
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def _kill_browser_processes(self):
|
||||
"""Kill existing browser processes based on platform"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
processes = ['chrome.exe', 'chromium.exe']
|
||||
for proc in processes:
|
||||
os.system(f'taskkill /f /im {proc} >nul 2>&1')
|
||||
else: # Linux/Mac
|
||||
processes = ['chrome', 'chromium', 'chromium-browser']
|
||||
for proc in processes:
|
||||
os.system(f'pkill -f {proc} >/dev/null 2>&1')
|
||||
|
||||
time.sleep(1) # Wait for processes to close
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Warning: Could not kill existing browser processes: {e}{Style.RESET_ALL}")
|
||||
|
||||
def _get_user_data_directory(self):
|
||||
"""Get the appropriate user data directory based on platform"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
possible_paths = [
|
||||
os.path.expandvars(r'%LOCALAPPDATA%\Google\Chrome\User Data'),
|
||||
os.path.expandvars(r'%LOCALAPPDATA%\Chromium\User Data')
|
||||
]
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
possible_paths = [
|
||||
os.path.expanduser('~/Library/Application Support/Google/Chrome'),
|
||||
os.path.expanduser('~/Library/Application Support/Chromium')
|
||||
]
|
||||
else: # Linux
|
||||
possible_paths = [
|
||||
os.path.expanduser('~/.config/google-chrome'),
|
||||
os.path.expanduser('~/.config/chromium'),
|
||||
'/usr/bin/google-chrome',
|
||||
'/usr/bin/chromium-browser'
|
||||
]
|
||||
|
||||
# Try each possible path
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found browser data directory: {path}{Style.RESET_ALL}")
|
||||
return path
|
||||
|
||||
# Create temporary profile if no existing profile found
|
||||
temp_profile = os.path.join(os.path.expanduser('~'), '.cursor_temp_profile')
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Creating temporary profile at: {temp_profile}{Style.RESET_ALL}")
|
||||
os.makedirs(temp_profile, exist_ok=True)
|
||||
return temp_profile
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error getting user data directory: {e}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
def _get_browser_path(self):
|
||||
"""Get the browser executable path based on platform"""
|
||||
try:
|
||||
# Try default path first
|
||||
chrome_path = get_default_chrome_path()
|
||||
if chrome_path and os.path.exists(chrome_path):
|
||||
return chrome_path
|
||||
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Searching for alternative browser installations...{Style.RESET_ALL}")
|
||||
|
||||
# Platform-specific paths
|
||||
if os.name == 'nt': # Windows
|
||||
alt_paths = [
|
||||
r'C:\Program Files\Google\Chrome\Application\chrome.exe',
|
||||
r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
|
||||
r'C:\Program Files\Chromium\Application\chrome.exe',
|
||||
os.path.expandvars(r'%ProgramFiles%\Google\Chrome\Application\chrome.exe'),
|
||||
os.path.expandvars(r'%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe')
|
||||
]
|
||||
elif sys.platform == 'darwin': # macOS
|
||||
alt_paths = [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||
'~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'~/Applications/Chromium.app/Contents/MacOS/Chromium'
|
||||
]
|
||||
else: # Linux
|
||||
alt_paths = [
|
||||
'/usr/bin/google-chrome',
|
||||
'/usr/bin/chromium-browser',
|
||||
'/usr/bin/chromium',
|
||||
'/snap/bin/chromium',
|
||||
'/usr/local/bin/chrome',
|
||||
'/usr/local/bin/chromium'
|
||||
]
|
||||
|
||||
# Try each alternative path
|
||||
for path in alt_paths:
|
||||
expanded_path = os.path.expanduser(path)
|
||||
if os.path.exists(expanded_path):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found browser at: {expanded_path}{Style.RESET_ALL}")
|
||||
return expanded_path
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error finding browser path: {e}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def _configure_browser_options(self, chrome_path, user_data_dir, active_profile):
|
||||
"""Configure browser options based on platform"""
|
||||
try:
|
||||
co = ChromiumOptions()
|
||||
co.set_paths(browser_path=chrome_path, user_data_path=user_data_dir)
|
||||
co.set_argument(f'--profile-directory={active_profile}')
|
||||
|
||||
# Basic options
|
||||
co.set_argument('--no-first-run')
|
||||
co.set_argument('--no-default-browser-check')
|
||||
co.set_argument('--disable-gpu')
|
||||
|
||||
# Platform-specific options
|
||||
if sys.platform.startswith('linux'):
|
||||
co.set_argument('--no-sandbox')
|
||||
co.set_argument('--disable-dev-shm-usage')
|
||||
co.set_argument('--disable-setuid-sandbox')
|
||||
elif sys.platform == 'darwin':
|
||||
co.set_argument('--disable-gpu-compositing')
|
||||
elif os.name == 'nt':
|
||||
co.set_argument('--disable-features=TranslateUI')
|
||||
co.set_argument('--disable-features=RendererCodeIntegrity')
|
||||
|
||||
return co
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}")
|
||||
raise
|
||||
|
||||
def handle_google_auth(self):
|
||||
"""Handle Google OAuth authentication"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start')}{Style.RESET_ALL}")
|
||||
|
||||
# Setup browser
|
||||
if not self.setup_browser():
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
# Navigate to auth URL
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
|
||||
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Look for Google auth button
|
||||
selectors = [
|
||||
"//a[contains(@href,'GoogleOAuth')]",
|
||||
"//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]",
|
||||
"(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback
|
||||
]
|
||||
|
||||
auth_btn = None
|
||||
for selector in selectors:
|
||||
try:
|
||||
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
|
||||
if auth_btn and auth_btn.is_displayed():
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not auth_btn:
|
||||
raise Exception("Could not find Google authentication button")
|
||||
|
||||
# Click the button and wait for page load
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting Google authentication...{Style.RESET_ALL}")
|
||||
auth_btn.click()
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Check if we're on account selection page
|
||||
if "accounts.google.com" in self.browser.url:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account to continue...{Style.RESET_ALL}")
|
||||
try:
|
||||
self.browser.run_js("""
|
||||
alert('Please select your Google account to continue with Cursor authentication');
|
||||
""")
|
||||
except:
|
||||
pass # Alert is optional
|
||||
|
||||
# Wait for authentication to complete
|
||||
auth_info = self._wait_for_auth()
|
||||
if not auth_info:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
|
||||
return True, auth_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
finally:
|
||||
try:
|
||||
if self.browser:
|
||||
self.browser.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
def _wait_for_auth(self):
|
||||
"""Wait for authentication to complete and extract auth info"""
|
||||
try:
|
||||
max_wait = 300 # 5 minutes
|
||||
start_time = time.time()
|
||||
check_interval = 2 # Check every 2 seconds
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for authentication (timeout: 5 minutes)...{Style.RESET_ALL}")
|
||||
|
||||
while time.time() - start_time < max_wait:
|
||||
try:
|
||||
# Check for authentication cookies
|
||||
cookies = self.browser.cookies()
|
||||
|
||||
for cookie in cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
value = cookie.get("value", "")
|
||||
token = None
|
||||
|
||||
if "::" in value:
|
||||
token = value.split("::")[-1]
|
||||
elif "%3A%3A" in value:
|
||||
token = value.split("%3A%3A")[-1]
|
||||
|
||||
if token:
|
||||
# Get email from settings page
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}")
|
||||
self.browser.get("https://www.cursor.com/settings")
|
||||
time.sleep(3)
|
||||
|
||||
email = None
|
||||
try:
|
||||
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||
if email_element:
|
||||
email = email_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {email}{Style.RESET_ALL}")
|
||||
except:
|
||||
email = "user@cursor.sh" # Fallback email
|
||||
|
||||
# Check usage count
|
||||
try:
|
||||
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||
if usage_element:
|
||||
usage_text = usage_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||
|
||||
# Check if account is expired
|
||||
if usage_text.strip() == "150 / 150":
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, creating new account...{Style.RESET_ALL}")
|
||||
|
||||
# Delete current account
|
||||
if self._delete_current_account():
|
||||
# Start new authentication
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
|
||||
return self.handle_google_auth()
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}")
|
||||
|
||||
return {"email": email, "token": token}
|
||||
|
||||
# Also check URL as backup
|
||||
if "cursor.com/settings" in self.browser.url:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected successful login{Style.RESET_ALL}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}")
|
||||
|
||||
time.sleep(check_interval)
|
||||
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Error while waiting for authentication: {str(e)}{Style.RESET_ALL}")
|
||||
return None
|
||||
|
||||
def handle_github_auth(self):
|
||||
"""Handle GitHub OAuth authentication"""
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.github_start')}{Style.RESET_ALL}")
|
||||
|
||||
# Setup browser
|
||||
if not self.setup_browser():
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
# Navigate to auth URL
|
||||
try:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
|
||||
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Look for GitHub auth button
|
||||
selectors = [
|
||||
"//a[contains(@href,'GitHubOAuth')]",
|
||||
"//a[contains(@class,'auth-method-button') and contains(@href,'GitHubOAuth')]",
|
||||
"(//a[contains(@class,'auth-method-button')])[2]" # Second auth button as fallback
|
||||
]
|
||||
|
||||
auth_btn = None
|
||||
for selector in selectors:
|
||||
try:
|
||||
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
|
||||
if auth_btn and auth_btn.is_displayed():
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not auth_btn:
|
||||
raise Exception("Could not find GitHub authentication button")
|
||||
|
||||
# Click the button and wait for page load
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting GitHub authentication...{Style.RESET_ALL}")
|
||||
auth_btn.click()
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Wait for authentication to complete
|
||||
auth_info = self._wait_for_auth()
|
||||
if not auth_info:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
|
||||
return True, auth_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
finally:
|
||||
try:
|
||||
if self.browser:
|
||||
self.browser.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
def _handle_oauth(self, auth_type):
|
||||
"""Handle OAuth authentication for both Google and GitHub
|
||||
|
||||
Args:
|
||||
auth_type (str): Type of authentication ('google' or 'github')
|
||||
"""
|
||||
try:
|
||||
if not self.setup_browser():
|
||||
return False, None
|
||||
|
||||
# Navigate to auth URL
|
||||
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Set selectors based on auth type
|
||||
if auth_type == "google":
|
||||
selectors = [
|
||||
"//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'][contains(@href,'GoogleOAuth')]",
|
||||
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[1]"
|
||||
]
|
||||
else: # github
|
||||
selectors = [
|
||||
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[2]"
|
||||
]
|
||||
|
||||
# Wait for the button to be available
|
||||
auth_btn = None
|
||||
max_button_wait = 30 # 30 seconds
|
||||
button_start_time = time.time()
|
||||
|
||||
while time.time() - button_start_time < max_button_wait:
|
||||
for selector in selectors:
|
||||
try:
|
||||
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=1)
|
||||
if auth_btn and auth_btn.is_displayed():
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if auth_btn:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if auth_btn:
|
||||
# Click the button and wait for page load
|
||||
auth_btn.click()
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Check if we're on account selection page
|
||||
if auth_type == "google" and "accounts.google.com" in self.browser.url:
|
||||
alert_js = """
|
||||
alert('Please select your Google account manually to continue with Cursor authentication');
|
||||
"""
|
||||
try:
|
||||
self.browser.run_js(alert_js)
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Alert display failed: {str(e)}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account manually to continue with Cursor authentication...{Style.RESET_ALL}")
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Waiting for authentication to complete...{Style.RESET_ALL}")
|
||||
|
||||
# Wait for authentication to complete
|
||||
max_wait = 300 # 5 minutes
|
||||
start_time = time.time()
|
||||
last_url = self.browser.url
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['WAIT']} Checking authentication status...{Style.RESET_ALL}")
|
||||
|
||||
while time.time() - start_time < max_wait:
|
||||
try:
|
||||
# Check for authentication cookies
|
||||
cookies = self.browser.cookies()
|
||||
|
||||
for cookie in cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
value = cookie.get("value", "")
|
||||
if "::" in value:
|
||||
token = value.split("::")[-1]
|
||||
elif "%3A%3A" in value:
|
||||
token = value.split("%3A%3A")[-1]
|
||||
|
||||
if token:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}")
|
||||
# Navigate to settings page
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}")
|
||||
self.browser.get("https://www.cursor.com/settings")
|
||||
time.sleep(3) # Wait for settings page to load
|
||||
|
||||
# Get email from settings page
|
||||
try:
|
||||
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||
if email_element:
|
||||
actual_email = email_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
|
||||
actual_email = "user@cursor.sh"
|
||||
|
||||
# Check usage count
|
||||
try:
|
||||
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||
if usage_element:
|
||||
usage_text = usage_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||
|
||||
# Check if account is expired
|
||||
if usage_text.strip() == "150 / 150": # Changed back to actual condition
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
|
||||
|
||||
delete_js = """
|
||||
function deleteAccount() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://www.cursor.com/api/dashboard/delete-account', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
resolve('Account deleted successfully');
|
||||
} else {
|
||||
reject('Failed to delete account: ' + response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject('Error: ' + error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return deleteAccount();
|
||||
"""
|
||||
|
||||
try:
|
||||
result = self.browser.run_js(delete_js)
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
|
||||
|
||||
# Navigate back to auth page and repeat authentication
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
|
||||
|
||||
# Explicitly navigate to the authentication page
|
||||
#self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||
# time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Call handle_google_auth again to repeat the entire process
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
|
||||
return self.handle_google_auth()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
|
||||
|
||||
# Remove the browser stay open prompt and input wait
|
||||
return True, {"email": actual_email, "token": token}
|
||||
|
||||
# Also check URL as backup
|
||||
current_url = self.browser.url
|
||||
if "cursor.com/settings" in current_url:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Already on settings page!{Style.RESET_ALL}")
|
||||
time.sleep(1)
|
||||
cookies = self.browser.cookies()
|
||||
for cookie in cookies:
|
||||
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||
value = cookie.get("value", "")
|
||||
if "::" in value:
|
||||
token = value.split("::")[-1]
|
||||
elif "%3A%3A" in value:
|
||||
token = value.split("%3A%3A")[-1]
|
||||
if token:
|
||||
# Get email and check usage here too
|
||||
try:
|
||||
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||
if email_element:
|
||||
actual_email = email_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
|
||||
actual_email = "user@cursor.sh"
|
||||
|
||||
# Check usage count
|
||||
try:
|
||||
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||
if usage_element:
|
||||
usage_text = usage_element.text
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||
|
||||
# Check if account is expired
|
||||
if usage_text.strip() == "150 / 150": # Changed back to actual condition
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
|
||||
|
||||
delete_js = """
|
||||
function deleteAccount() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://www.cursor.com/api/dashboard/delete-account', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
resolve('Account deleted successfully');
|
||||
} else {
|
||||
reject('Failed to delete account: ' + response.status);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject('Error: ' + error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return deleteAccount();
|
||||
"""
|
||||
|
||||
try:
|
||||
result = self.browser.run_js(delete_js)
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
|
||||
|
||||
# Navigate back to auth page and repeat authentication
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
|
||||
|
||||
# Explicitly navigate to the authentication page
|
||||
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
|
||||
# Call handle_google_auth again to repeat the entire process
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
|
||||
return self.handle_google_auth()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
|
||||
else:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
|
||||
|
||||
# Remove the browser stay open prompt and input wait
|
||||
return True, {"email": actual_email, "token": token}
|
||||
elif current_url != last_url:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Page changed, checking auth...{Style.RESET_ALL}")
|
||||
last_url = current_url
|
||||
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Status check error: {str(e)}{Style.RESET_ALL}")
|
||||
time.sleep(1)
|
||||
continue
|
||||
time.sleep(1)
|
||||
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication failed: {str(e)}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
finally:
|
||||
if self.browser:
|
||||
self.browser.quit()
|
||||
|
||||
def _extract_auth_info(self):
|
||||
"""Extract authentication information after successful OAuth"""
|
||||
try:
|
||||
# Get cookies with retry
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
cookies = self.browser.cookies()
|
||||
if cookies:
|
||||
break
|
||||
time.sleep(1)
|
||||
except:
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
time.sleep(1)
|
||||
|
||||
# Debug cookie information
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}")
|
||||
|
||||
email = None
|
||||
token = None
|
||||
|
||||
for cookie in cookies:
|
||||
name = cookie.get("name", "")
|
||||
if name == "WorkosCursorSessionToken":
|
||||
try:
|
||||
value = cookie.get("value", "")
|
||||
if "::" in value:
|
||||
token = value.split("::")[-1]
|
||||
elif "%3A%3A" in value:
|
||||
token = value.split("%3A%3A")[-1]
|
||||
except Exception as e:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} Token extraction error: {str(e)}{Style.RESET_ALL}")
|
||||
elif name == "cursor_email":
|
||||
email = cookie.get("value")
|
||||
|
||||
if email and token:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful - Email: {email}{Style.RESET_ALL}")
|
||||
return True, {"email": email, "token": token}
|
||||
else:
|
||||
missing = []
|
||||
if not email:
|
||||
missing.append("email")
|
||||
if not token:
|
||||
missing.append("token")
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to extract auth info: {str(e)}{Style.RESET_ALL}")
|
||||
return False, None
|
||||
|
||||
def main(auth_type, translator=None):
|
||||
"""Main function to handle OAuth authentication
|
||||
|
||||
Args:
|
||||
auth_type (str): Type of authentication ('google' or 'github')
|
||||
translator: Translator instance for internationalization
|
||||
"""
|
||||
handler = OAuthHandler(translator)
|
||||
|
||||
if auth_type.lower() == 'google':
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")
|
||||
success, auth_info = handler.handle_google_auth()
|
||||
elif auth_type.lower() == 'github':
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start')}{Style.RESET_ALL}")
|
||||
success, auth_info = handler.handle_github_auth()
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} Invalid authentication type{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
if success and auth_info:
|
||||
# Update Cursor authentication
|
||||
auth_manager = CursorAuth(translator)
|
||||
if auth_manager.update_auth(
|
||||
email=auth_info["email"],
|
||||
access_token=auth_info["token"],
|
||||
refresh_token=auth_info["token"]
|
||||
):
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success')}{Style.RESET_ALL}")
|
||||
# Close the browser after successful authentication
|
||||
if handler.browser:
|
||||
handler.browser.quit()
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} Browser closed{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}")
|
||||
|
||||
return False
|
||||
@@ -12,6 +12,8 @@ from colorama import Fore, Style, init
|
||||
from typing import Tuple
|
||||
import configparser
|
||||
from new_signup import get_user_documents_path
|
||||
import traceback
|
||||
from config import get_config
|
||||
|
||||
# Initialize colorama
|
||||
init()
|
||||
@@ -29,42 +31,45 @@ EMOJI = {
|
||||
def get_cursor_paths(translator=None) -> Tuple[str, str]:
|
||||
""" Get Cursor related paths"""
|
||||
system = platform.system()
|
||||
|
||||
paths_map = {
|
||||
"Darwin": {
|
||||
"base": "/Applications/Cursor.app/Contents/Resources/app",
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
"Windows": {
|
||||
"base": os.path.join(
|
||||
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"
|
||||
),
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
"Linux": {
|
||||
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
|
||||
"package": "package.json",
|
||||
"main": "out/main.js",
|
||||
},
|
||||
}
|
||||
|
||||
if system not in paths_map:
|
||||
|
||||
# Read config file
|
||||
config = configparser.ConfigParser()
|
||||
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
|
||||
config_file = os.path.join(config_dir, "config.ini")
|
||||
|
||||
if not os.path.exists(config_file):
|
||||
raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件")
|
||||
|
||||
config.read(config_file, encoding='utf-8') # Specify encoding
|
||||
|
||||
# Get path based on system
|
||||
if system == "Darwin":
|
||||
section = 'MacPaths'
|
||||
elif system == "Windows":
|
||||
section = 'WindowsPaths'
|
||||
elif system == "Linux":
|
||||
section = 'LinuxPaths'
|
||||
else:
|
||||
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
|
||||
|
||||
if system == "Linux":
|
||||
for base in paths_map["Linux"]["bases"]:
|
||||
pkg_path = os.path.join(base, paths_map["Linux"]["package"])
|
||||
if os.path.exists(pkg_path):
|
||||
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"]))
|
||||
raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径")
|
||||
|
||||
base_path = paths_map[system]["base"]
|
||||
return (
|
||||
os.path.join(base_path, paths_map[system]["package"]),
|
||||
os.path.join(base_path, paths_map[system]["main"]),
|
||||
)
|
||||
|
||||
if not config.has_section(section) or not config.has_option(section, 'cursor_path'):
|
||||
raise OSError(translator.get('reset.path_not_configured') if translator else "未配置 Cursor 路徑")
|
||||
|
||||
base_path = config.get(section, 'cursor_path')
|
||||
|
||||
if not os.path.exists(base_path):
|
||||
raise OSError(translator.get('reset.path_not_found', path=base_path) if translator else f"找不到 Cursor 路徑: {base_path}")
|
||||
|
||||
pkg_path = os.path.join(base_path, "package.json")
|
||||
main_path = os.path.join(base_path, "out/main.js")
|
||||
|
||||
# Check if files exist
|
||||
if not os.path.exists(pkg_path):
|
||||
raise OSError(translator.get('reset.package_not_found', path=pkg_path) if translator else f"找不到 package.json: {pkg_path}")
|
||||
if not os.path.exists(main_path):
|
||||
raise OSError(translator.get('reset.main_not_found', path=main_path) if translator else f"找不到 main.js: {main_path}")
|
||||
|
||||
return (pkg_path, main_path)
|
||||
|
||||
def get_cursor_machine_id_path(translator=None) -> str:
|
||||
"""
|
||||
@@ -176,11 +181,60 @@ def check_cursor_version(translator) -> bool:
|
||||
"""Check Cursor version"""
|
||||
try:
|
||||
pkg_path, _ = get_cursor_paths(translator)
|
||||
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||
version = json.load(f)["version"]
|
||||
return version_check(version, min_version="0.45.0", translator=translator)
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.reading_package_json', path=pkg_path)}{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except UnicodeDecodeError:
|
||||
# If UTF-8 reading fails, try other encodings
|
||||
with open(pkg_path, "r", encoding="latin-1") as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
if "version" not in data:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_version_field')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
version = str(data["version"]).strip()
|
||||
if not version:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_field_empty')}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.found_version', version=version)}{Style.RESET_ALL}")
|
||||
|
||||
# Check version format
|
||||
if not re.match(r"^\d+\.\d+\.\d+$", version):
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
# Compare versions
|
||||
try:
|
||||
current = tuple(map(int, version.split(".")))
|
||||
min_ver = (0, 45, 0) # Use tuple directly instead of string
|
||||
|
||||
if current >= min_ver:
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.version_check_passed', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.version_too_low', version=version, min_version='0.45.0')}{Style.RESET_ALL}")
|
||||
return False
|
||||
except ValueError as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_parse_error', error=str(e))}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.package_not_found', path=pkg_path)}{Style.RESET_ALL}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_json_object')}{Style.RESET_ALL}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.check_version_failed', error=str(e))}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('reset.stack_trace')}: {traceback.format_exc()}{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
def modify_workbench_js(file_path: str, translator=None) -> bool:
|
||||
@@ -355,7 +409,7 @@ class MachineIDResetter:
|
||||
if not os.path.exists(config_file):
|
||||
raise FileNotFoundError(f"Config file not found: {config_file}")
|
||||
|
||||
config.read(config_file)
|
||||
config.read(config_file, encoding='utf-8')
|
||||
|
||||
# Check operating system
|
||||
if sys.platform == "win32": # Windows
|
||||
@@ -391,7 +445,7 @@ class MachineIDResetter:
|
||||
elif sys.platform == "linux": # Linux
|
||||
if not config.has_section('LinuxPaths'):
|
||||
config.add_section('LinuxPaths')
|
||||
# 获取实际用户的主目录
|
||||
# Get actual user's home directory
|
||||
sudo_user = os.environ.get('SUDO_USER')
|
||||
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
|
||||
|
||||
@@ -576,12 +630,12 @@ class MachineIDResetter:
|
||||
### Remove In v1.7.02
|
||||
# Check Cursor version and perform corresponding actions
|
||||
|
||||
# greater_than_0_45 = check_cursor_version(self.translator)
|
||||
# if greater_than_0_45:
|
||||
# print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0,{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}")
|
||||
# patch_cursor_get_machine_id(self.translator)
|
||||
# else:
|
||||
# print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}")
|
||||
greater_than_0_45 = check_cursor_version(self.translator)
|
||||
if greater_than_0_45:
|
||||
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.detecting_version')} >= 0.45.0,{self.translator.get('reset.patching_getmachineid')}{Style.RESET_ALL}")
|
||||
patch_cursor_get_machine_id(self.translator)
|
||||
else:
|
||||
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.version_less_than_0_45')}{Style.RESET_ALL}")
|
||||
|
||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.success')}{Style.RESET_ALL}")
|
||||
print(f"\n{Fore.CYAN}{self.translator.get('reset.new_id')}:{Style.RESET_ALL}")
|
||||
@@ -637,7 +691,9 @@ class MachineIDResetter:
|
||||
return False
|
||||
|
||||
def run(translator=None):
|
||||
"""Convenient function for directly calling the reset function"""
|
||||
config = get_config(translator)
|
||||
if not config:
|
||||
return False
|
||||
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}")
|
||||
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
|
||||
|
||||
36
scripts/install.sh
Normal file → Executable file
36
scripts/install.sh
Normal file → Executable file
@@ -39,14 +39,17 @@ get_downloads_dir() {
|
||||
# Get latest version
|
||||
get_latest_version() {
|
||||
echo -e "${CYAN}ℹ️ Checking latest version...${NC}"
|
||||
local latest_release
|
||||
latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest)
|
||||
if [ $? -ne 0 ]; then
|
||||
latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest) || {
|
||||
echo -e "${RED}❌ Cannot get latest version information${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
VERSION=$(echo "$latest_release" | grep -o '"tag_name": ".*"' | cut -d'"' -f4 | tr -d 'v')
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo -e "${RED}❌ Failed to parse version from GitHub API response:\n${latest_release}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Found latest version: ${VERSION}${NC}"
|
||||
}
|
||||
|
||||
@@ -63,8 +66,15 @@ detect_os() {
|
||||
echo -e "${CYAN}ℹ️ Detected macOS Intel architecture${NC}"
|
||||
fi
|
||||
elif [[ "$(uname)" == "Linux" ]]; then
|
||||
OS="linux"
|
||||
echo -e "${CYAN}ℹ️ Detected Linux system${NC}"
|
||||
# Detect Linux architecture
|
||||
ARCH=$(uname -m)
|
||||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||||
OS="linux_arm64"
|
||||
echo -e "${CYAN}ℹ️ Detected Linux ARM64 architecture${NC}"
|
||||
else
|
||||
OS="linux_x64"
|
||||
echo -e "${CYAN}ℹ️ Detected Linux x64 architecture${NC}"
|
||||
fi
|
||||
else
|
||||
# Assume Windows
|
||||
OS="windows"
|
||||
@@ -123,6 +133,16 @@ install_cursor_free_vip() {
|
||||
download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
|
||||
echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}"
|
||||
|
||||
if ! curl --output /dev/null --silent --head --fail "$download_url"; then
|
||||
echo -e "${RED}❌ New download link does not exist${NC}"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "$OS" == "linux_x64" || "$OS" == "linux_arm64" ]]; then
|
||||
OS="linux"
|
||||
binary_name="CursorFreeVIP_${VERSION}_${OS}"
|
||||
download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
|
||||
echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}"
|
||||
|
||||
if ! curl --output /dev/null --silent --head --fail "$download_url"; then
|
||||
echo -e "${RED}❌ New download link does not exist${NC}"
|
||||
exit 1
|
||||
@@ -153,9 +173,7 @@ install_cursor_free_vip() {
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}ℹ️ Setting executable permissions...${NC}"
|
||||
chmod +x "${binary_path}"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
if chmod +x "${binary_path}"; then
|
||||
echo -e "${GREEN}✅ Installation completed!${NC}"
|
||||
echo -e "${CYAN}ℹ️ Program downloaded to: ${binary_path}${NC}"
|
||||
echo -e "${CYAN}ℹ️ Starting program...${NC}"
|
||||
|
||||
69
utils.py
Normal file
69
utils.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import random
|
||||
|
||||
def get_user_documents_path():
|
||||
"""Get user documents path"""
|
||||
if platform.system() == "Windows":
|
||||
return os.path.expanduser("~\\Documents")
|
||||
else:
|
||||
return os.path.expanduser("~/Documents")
|
||||
|
||||
def get_default_chrome_path():
|
||||
"""Get default Chrome path"""
|
||||
if sys.platform == "win32":
|
||||
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||
elif sys.platform == "darwin":
|
||||
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
else:
|
||||
return "/usr/bin/google-chrome"
|
||||
|
||||
def get_linux_cursor_path():
|
||||
"""Get Linux Cursor path"""
|
||||
possible_paths = [
|
||||
"/opt/Cursor/resources/app",
|
||||
"/usr/share/cursor/resources/app",
|
||||
"/opt/cursor-bin/resources/app",
|
||||
"/usr/lib/cursor/resources/app",
|
||||
os.path.expanduser("~/.local/share/cursor/resources/app")
|
||||
]
|
||||
|
||||
# return the first path that exists
|
||||
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
|
||||
|
||||
def get_random_wait_time(config, timing_key):
|
||||
"""Get random wait time based on configuration timing settings
|
||||
|
||||
Args:
|
||||
config (dict): Configuration dictionary containing timing settings
|
||||
timing_key (str): Key to look up in the timing settings
|
||||
|
||||
Returns:
|
||||
float: Random wait time in seconds
|
||||
"""
|
||||
try:
|
||||
# Get timing value from config
|
||||
timing = config.get('Timing', {}).get(timing_key)
|
||||
if not timing:
|
||||
# Default to 0.5-1.5 seconds if timing not found
|
||||
return random.uniform(0.5, 1.5)
|
||||
|
||||
# Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5")
|
||||
if isinstance(timing, str):
|
||||
if '-' in timing:
|
||||
min_time, max_time = map(float, timing.split('-'))
|
||||
elif ',' in timing:
|
||||
min_time, max_time = map(float, timing.split(','))
|
||||
else:
|
||||
# Single value, use it as both min and max
|
||||
min_time = max_time = float(timing)
|
||||
else:
|
||||
# If timing is a number, use it as both min and max
|
||||
min_time = max_time = float(timing)
|
||||
|
||||
return random.uniform(min_time, max_time)
|
||||
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
# Return default value if any error occurs
|
||||
return random.uniform(0.5, 1.5)
|
||||
Reference in New Issue
Block a user