Compare commits

...

155 Commits

Author SHA1 Message Date
Pin Studios
2cb788ddc1 Merge pull request #253 from BasaiCorp/main
Totally Reset Cusror
2025-03-16 12:48:02 +08:00
yeongpin
60ff9897f3 Update version to 1.7.10 and enhance totally_reset_cursor.py with multi-language support and new features. Update CHANGELOG.md to reflect changes. 2025-03-16 12:45:27 +08:00
BasaiCorp
21ca0a6b2d CLI Interface update 2025-03-16 09:44:31 +05:30
BasaiCorp
1dba533e93 COmmit v2.1 with machine id reset and all this one file will only do all for you but for account emails and registration please use https://github.com/yeongpin/cursor-free-vip 2025-03-16 09:28:53 +05:30
BasaiCorp
9146677bc4 update v2 of totally_reset_cursor.py 2025-03-16 09:18:54 +05:30
BasaiCorp
50366e319a Added totally_reset_cursor.py to fully reset Cursor AI 2025-03-16 09:05:17 +05:30
yeongpin
ad98bed98d Refactor: Remove GitHub trial reset feature and add development version check. Update translations for lifetime access and development version messages. 2025-03-16 10:54:43 +08:00
Pin Studios
6f2ec1b373 Merge pull request #250 from yeongpin/revert-248-feature/github-reset-trial
Revert "feat: Add GitHub-based trial reset"
2025-03-16 10:45:52 +08:00
Pin Studios
5574091273 Revert "feat: Add GitHub-based trial reset" 2025-03-16 10:44:40 +08:00
Pin Studios
0226c9e735 Update CHANGELOG.md 2025-03-16 10:18:35 +08:00
Pin Studios
375347a7a5 Update .env 2025-03-16 10:05:57 +08:00
Pin Studios
8301ea74d5 Delete pr_description.md 2025-03-16 10:05:41 +08:00
Pin Studios
96981a2c37 Merge pull request #248 from Nigel1992/feature/github-reset-trial
feat: Add GitHub-based trial reset
2025-03-16 10:04:44 +08:00
Nigel1992
975647765d feat: Add JavaScript trial reset code and update PR description 2025-03-15 23:52:39 +01:00
Nigel1992
808b1ba3dc feat: Add JavaScript trial reset code for automatic account deletion 2025-03-15 23:48:55 +01:00
Nigel1992
9595a8792f docs: Update PR description to include JavaScript trial reset code 2025-03-15 23:45:15 +01:00
Nigel1992
6b5dfab362 feat: Add GitHub-based trial reset with improved organization 2025-03-15 23:39:02 +01:00
Pin Studios
d5fceb2624 Update .env 2025-03-15 23:46:50 +08:00
Pin Studios
45046002df Merge pull request #243 from mALIk-sHAHId/feature/google-github-auth
feat: Add Google and GitHub OAuth Authentication with Lifetime Access…
2025-03-15 23:46:28 +08:00
mALIk-sHAHId
6bb33cec4c feat: Add Google and GitHub OAuth Authentication with Lifetime Access - Add OAuth integrations, update menu system, add multi-language support, and update documentation 2025-03-15 19:39:30 +05:30
Pin Studios
66f6197e6d Update block_domain.txt 2025-03-15 11:10:30 +08:00
Pin Studios
5514e47759 Update block_domain.txt 2025-03-15 10:29:03 +08:00
Pin Studios
3a53d59d52 Merge pull request #227 from distributedStorage/enhance-readme
doc: unify the README & CHANGELOG style
2025-03-14 23:02:31 +08:00
imbajin
c2e5499f19 doc: unify the README & CHANGELOG 2025-03-14 15:29:49 +08:00
Pin Studios
9a223756c5 Merge pull request #226 from distributedStorage/fix-download
fix(script): avoid empty version when call release api
2025-03-14 15:26:39 +08:00
imbajin
9b5912357d fix(script): avoid empty version when call release api
Also enhance the way for judging $?
2025-03-14 15:11:57 +08:00
Pin Studios
3200271156 Merge pull request #224 from RenjiYuusei/main
Add Vietnamese Language Support
2025-03-14 12:15:53 +08:00
yeongpin
8401f4718e Update CHANGELOG.md for version 1.7.07
- Add Vietnamese language support and admin privilege management features for Windows executables.
- Implement admin privilege detection and enhance startup process with verification.
- Introduce functions to check and request admin rights when running as a frozen executable.
- Include new admin-related emoji in the EMOJI dictionary and provide fallback for non-Windows platforms.
2025-03-14 12:15:37 +08:00
yeongpin
b761bf0b6d Update localization and versioning for Vietnamese support
- Increment version to 1.7.07 in .env file.
- Add Vietnamese language support in main.py and localization files (vi.json, zh_cn.json, zh_tw.json).
- Translate Chinese language options to English in localization files.
2025-03-14 12:12:58 +08:00
Renji Yuusei
db95689a8e Remove Vietnamese Language Option from Chinese Localization Files
- Updated zh_cn.json and zh_tw.json to remove the Vietnamese language entry while retaining other language options.
2025-03-14 06:54:12 +07:00
Renji Yuusei
652ffc809a Add Vietnamese Language Support
- Update localization files to include Vietnamese language option
2025-03-14 00:25:20 +07:00
Pin Studios
a854969682 Update README.md 2025-03-13 12:09:56 +08:00
Pin Studios
9c5ac85759 Merge pull request #199 from ahmed98Osama/feature/admin-privileges
feat: Add Admin Privilege Management for Windows Executable
2025-03-13 12:08:36 +08:00
Ahemd Farouk
a67264d5c2 refactor: Simplify Admin Privilege Request Logic
- Consolidate admin privilege request logic for Windows by removing redundant checks for frozen executables
2025-03-12 16:51:56 +02:00
Ahemd Farouk
68b1dae466 feat: Add Admin Privilege Management for Windows Executable
- Implement admin privilege detection for Windows platform
- Add functions to check and request admin rights when running as a frozen executable
- Enhance startup process with admin privilege verification
- Add new admin-related emoji to the EMOJI dictionary
- Provide fallback mechanism for non-Windows platforms
2025-03-11 14:56:47 +02:00
yeongpin
0da6f9a1b7 feat: Add Chinese and English Question Issue Templates
- Create bilingual issue templates for discussions and questions
- Add comprehensive sections for platform, version, and problem description
- Include checklist to guide users in providing clear and helpful issue details
- Support both Chinese and English language issue submissions
2025-03-11 12:53:58 +08:00
yeongpin
59fccecb0f feat: Add English Bug Report Issue Template
- Create a new issue template for English-language bug reports
- Mirror the structure of the Chinese bug report template
- Include comprehensive sections for platform, version, and error description
- Add checklist to guide users in providing clear and helpful issue details
2025-03-11 12:47:33 +08:00
yeongpin
1769d245f9 feat: Add Chinese Bug Report Issue Template
- Create a new issue template for Chinese-language bug reports
- Include comprehensive sections for platform, version, and error description
- Add checklist to guide users in providing clear and helpful issue details
- Enhance issue submission process with specific guidelines
2025-03-11 12:45:17 +08:00
yeongpin
b98f094407 refactor: Internationalize and Clean Up Code Comments
- Translate Chinese comments to English across multiple files
- Improve code readability by using consistent, clear comments
- Remove redundant comments and simplify language-specific annotations
- Maintain existing code structure while enhancing internationalization
2025-03-11 12:09:07 +08:00
yeongpin
ff358588bb feat: Refactor Project Structure and Add Configuration Management
- Add `config.py` for centralized configuration management
- Add `utils.py` for cross-platform utility functions
- Remove `browser.py` and `control.py` to simplify project structure
- Update version to 1.7.06
- Modify build specification to reflect new file structure
- Update localization files with new update confirmation messages
- Enhance configuration loading and path detection across different platforms
2025-03-11 11:49:17 +08:00
yeongpin
6ca80ccb10 Merge branch 'main' of https://github.com/yeongpin/cursor-free-vip 2025-03-11 10:12:57 +08:00
yeongpin
2fca5218fb remove md 2025-03-11 10:12:54 +08:00
Pin Studios
71ecf5a201 Update issue templates 2025-03-11 10:12:41 +08:00
Pin Studios
4570b174ab Update issue templates 2025-03-10 18:36:36 +08:00
yeongpin
f708ce443b fix: Correct Syntax in Linux Installation Script
- Fix syntax error in install script's conditional statements
- Remove unnecessary braces and correct shell script syntax
- Ensure proper handling of Linux architecture detection
2025-03-10 17:54:09 +08:00
yeongpin
4f6f3fe814 fix: Improve Cursor Version Check and Configuration Handling
- Enhance version checking in `reset_machine_manual.py` with more robust error handling
- Add detailed error messages and logging for version detection
- Update configuration paths in `new_signup.py` to include Cursor application path
- Add configuration initialization in `main.py`
- Update localization files with new error and version-related messages
2025-03-10 17:41:07 +08:00
yeongpin
54ecf2d752 feat: Add Linux ARM64 Support and Update Build Workflow
- Extend GitHub Actions workflow to build Linux x64 and ARM64 executables
- Update install script to detect Linux architecture (x64 or ARM64)
- Modify release process to include both Linux architecture artifacts
- Rename Linux build job to clarify x64 architecture
2025-03-10 16:28:26 +08:00
yeongpin
1f1231d1a9 remove br in readme 2025-03-10 14:43:35 +08:00
yeongpin
d10999a517 Update Readme Doc 2025-03-10 14:43:09 +08:00
yeongpin
fb4be6334a docs: Add Detailed Comments and Localization for Configuration Settings
- Add bilingual comments (English and Chinese) for OSPaths section
- Provide clear descriptions for timing-related configuration parameters
- Enhance readability of README.md configuration settings
- Improve documentation for storage, SQLite, and machine ID paths
2025-03-10 13:42:18 +08:00
Pin Studios
786eba5371 Update README.md 2025-03-10 13:41:05 +08:00
yeongpin
41ddbf519e hotfix: Optimize Verification Code Handling and Timing Configuration
- Refactor verification code retrieval with dynamic wait time generation
- Use `get_random_wait_time()` for more flexible retry intervals
- Update version to 1.7.04 across project files
- Minor improvements to timing and retry logic in signup process
2025-03-10 13:27:41 +08:00
yeongpin
ffd48201fd hotfix: Improve Signup Flow and Timing Configuration
- Add comprehensive timing configuration for signup process
- Refactor random wait time generation with more flexible config options
- Update form filling and verification code handling with configurable wait times
- Enhance localization support for new timing-related messages
- Update version to 1.7.03 across project files
2025-03-10 12:24:43 +08:00
yeongpin
cd4f36725c refactor: Optimize Signup Flow and Error Handling
- Implement direct URL-based signup with pre-filled parameters
- Enhance password setting process with improved error detection
- Add more robust retry mechanism for password input
- Improve error handling for email availability and submission
- Refactor signup and password setting methods for better reliability
2025-03-10 11:44:37 +08:00
yeongpin
dccb524bd7 Update Readme add some doc 2025-03-10 11:21:42 +08:00
yeongpin
90e9a5b287 feat: Enhance Configuration Management for Cursor Paths
- Add OS-specific path configurations for Cursor storage, SQLite, and machine ID files
- Integrate configuration file management in `new_signup.py` and `reset_machine_manual.py`
- Remove Workbench Cursor path and main.js modification steps
- Improve cross-platform path detection and configuration handling
- Update version to 1.7.02
2025-03-10 11:18:21 +08:00
Pin Studios
66a67fce8b Update README.md 2025-03-07 11:20:04 +08:00
Pin Studios
0981f00b9c Update build.yml 2025-03-07 11:19:49 +08:00
yeongpin
02851c9a09 feat: Implement Configuration Management and Enhance Browser Setup
- Add `setup_config` function to manage configuration file across platforms
- Extract configuration-related code from `setup_driver` into a separate function
- Implement dynamic Chrome path detection for Windows, macOS, and Linux
- Add configurable Turnstile verification settings
- Update README.md with configuration file details
- Enhance localization support for configuration-related messages
- Improve code maintainability and platform compatibility
2025-03-07 11:18:43 +08:00
yeongpin
6312d66813 chore: Bump version to 1.6.03
- Update version in .env file
- Update CHANGELOG.md with version 1.6.03 details
- Minor hotfix and maintenance release
2025-03-06 18:37:04 +08:00
yeongpin
1b1a21f3d7 feat: Add Machine ID File Update Functionality
- Implement `update_machine_id_file` method in `MachineIDResetter` class
- Add new `get_cursor_machine_id_path` function to detect machineId file path across platforms
- Enhance machine ID reset process with file-level machine ID update
- Implement backup mechanism for existing machineId file
- Add cross-platform support for Linux, macOS, and Windows
- Improve error handling and logging with colorful console output
2025-03-06 18:36:00 +08:00
yeongpin
c94bd605e5 fix: Update GitHub Link in Workbench JS Modification
- Correct GitHub repository link to point to specific project repository
- Modify onClick handler to open the correct GitHub page for the project
2025-03-06 18:14:34 +08:00
yeongpin
9f6eee77e0 feat: Add Workbench JS Modification and Cursor Path Detection
- Update `MachineIDResetter` to include workbench.desktop.main.js modification step
- Enhance platform support for Linux, macOS, and Windows
- Update CHANGELOG.md to version 1.6.02
2025-03-06 18:11:40 +08:00
Pin Studios
2bdcc2f633 chore: Bump version to 1.6.01 2025-03-06 12:19:26 +08:00
Pin Studios
4aabe2e403 chore: Bump version to 1.5.04 and improve system compatibility
- Update version in .env file to 1.5.04
- Add Mac-specific run_venv script to .gitignore
- Enhance Cursor Auth platform detection with more precise sys.platform checks
- Add maximum retry mechanism for email creation
- Improve error handling and platform support in cursor_auth.py
2025-03-06 12:18:55 +08:00
yeongpin
005aa2cd95 feat: Improve Version Update Mechanism with Robust Error Handling
- Enhance GitHub API version check with proper headers and timeout
- Add comprehensive error handling for version retrieval
- Implement more reliable update script download and execution
- Improve cross-platform update process with better error reporting
- Add fallback mechanisms for network and update failures
2025-03-06 11:18:21 +08:00
yeongpin
14f6dfc29d fix: Improve Browser Startup and Error Handling
- Add `--no-sandbox` flag to resolve browser startup issues
- Enhance error handling in temp email creation
- Update localization files with new email-related messages
- Improve translation support for email creation process
2025-03-06 11:01:34 +08:00
Pin Studios
2e9bd269ad Update README.md 2025-03-03 15:24:36 +08:00
Pin Studios
f9b7e23253 Update README.md 2025-03-03 15:19:44 +08:00
yeongpin
0d979f7543 chore: Bump default version to 1.5.03 in build workflow 2025-03-03 11:59:15 +08:00
yeongpin
cce3025f7f feat: Enhance Name Generation and Improve Account Registration Process
- Implement realistic name generation with predefined name lists
- Modify first name letter for uniqueness
- Add more descriptive console output with emojis and translations
- Update localization files with new registration-related keys
- Optimize random name generation in registration modules
2025-03-03 11:58:28 +08:00
Pin Studios
fe3e27561b Update .env 2025-03-03 11:17:35 +08:00
yeongpin
bf2bea71eb Merge branch 'main' of https://github.com/yeongpin/cursor-free-vip 2025-03-03 11:16:45 +08:00
yeongpin
77a61647dd docs: Update CHANGELOG with version 1.5.01 release notes 2025-03-03 11:10:10 +08:00
yeongpin
813dd4431e feat: Add version update checker with GitHub release support 2025-03-03 11:09:11 +08:00
Pin Studios
d7116b8cf3 Update .env 2025-03-02 13:55:25 +08:00
Pin Studios
f5a7acc4e3 Merge pull request #122 from bingoohuang/main
print account info as early as possible
2025-03-02 13:54:58 +08:00
bingoohuang
479933844a print account info as early as possible 2025-02-28 21:20:12 +08:00
yeongpin
93046d7f03 Update README.md with improved image styling and new screenshot 2025-02-27 10:44:18 +08:00
Pin Studios
e7ca31b710 Update build.yml 2025-02-27 10:33:53 +08:00
Pin Studios
87b99b0d16 Update CHANGELOG.md 2025-02-27 10:29:10 +08:00
Pin Studios
e983b6f560 Update .env 2025-02-27 10:27:18 +08:00
Pin Studios
21ca7ab24f Merge pull request #111 from ahmed98Osama/feature/return-to-menu
Return to main menu after each operation
2025-02-27 10:27:00 +08:00
Ahmed Osama
e7468644a4 feat: return to main menu after each operation instead of exiting 2025-02-26 19:07:14 +02:00
Pin Studios
2bca7d7d14 Update install.sh 2025-02-26 22:34:38 +08:00
Pin Studios
040c5f5836 Update install.ps1 2025-02-26 22:30:56 +08:00
Pin Studios
4a86bbeeb4 Update block_domain.txt 2025-02-26 22:01:53 +08:00
Pin Studios
68b7888a83 Merge pull request #101 from aliensb/main
add support for linux
2025-02-25 23:56:29 +08:00
yeongpin
78dee025a7 feat: Enhance Temporary Email Service with Domain Blocking and Improved Error Handling
- Add domain blocking mechanism to filter out problematic email domains
- Implement dynamic domain list retrieval from external source
- Improve error handling and logging in email creation process
- Add fallback service switching when domains are blocked
- Update localization files with new error and blocking-related messages
- Increment version to 1.4.06
2025-02-25 23:53:55 +08:00
Pin Studios
17c2b4b243 Create block_domain.txt 2025-02-25 23:30:53 +08:00
tom
2acb271f19 add support for linux 2025-02-25 16:52:32 +08:00
Pin Studios
b688b67c26 Update .gitignore 2025-02-25 12:23:45 +08:00
yeongpin
3543615a69 fix: Improve Windows Language Detection and Platform Compatibility
- Correct Windows language detection method in Translator class
- Update platform system check for Windows-specific imports
- Refactor language detection to use platform-specific checks
- Ensure more robust cross-platform language detection logic
2025-02-25 12:16:55 +08:00
yeongpin
24b0a5c09e refactor: Streamline System Language Detection Logic
- Consolidate language detection methods into a single unified approach
- Improve cross-platform language detection for Windows and Unix-like systems
- Simplify error handling and add more robust language code mapping
- Remove platform-specific detection methods in favor of a more generic approach
- Enhance language fallback mechanism with better default handling
2025-02-25 12:14:54 +08:00
yeongpin
5bbba05f69 feat: Improve System Language Detection in Translator Class
- Refactor language detection method with separate methods for Windows, macOS, and Linux
- Add more robust language detection for different platforms
- Improve error handling and fallback mechanisms
- Ensure consistent language code formatting (zh_CN, zh_TW)
- Update Windows language detection using windll with additional error checks
2025-02-25 12:09:13 +08:00
yeongpin
72c95e4b4f refactor: Simplify OS Detection and Download Process
- Streamline OS detection logic by removing detailed architecture checks
- Reduce complexity in download URL generation
- Remove verbose download verification steps
- Simplify error handling during binary download
2025-02-25 12:00:59 +08:00
yeongpin
8afd5df4ea fix: Correct Syntax Error in Installation Script Conditional Logic
- Fix incorrect brace placement in install_cursor_free_vip function
- Remove unnecessary closing brace that was causing syntax error
- Ensure proper flow control in installation script
2025-02-25 11:55:38 +08:00
yeongpin
a7a97b5621 feat: Enhance OS Detection and Download Resilience in Installation Script
- Add Windows system detection
- Implement fallback mechanism for macOS binary downloads
- Improve download link verification with HEAD request
- Add graceful handling for missing architecture-specific binaries
- Provide more informative error messages during download process
2025-02-25 11:54:45 +08:00
yeongpin
e2e2ebc12e feat: Enhance Download Verification in Installation Script
- Add verbose curl download logging
- Implement file size check for downloaded binaries
- Provide detailed error messages for download failures
- Improve download error handling and diagnostics
2025-02-25 11:53:37 +08:00
yeongpin
d131bccac0 feat: Improve macOS Architecture Detection in Installation Script
- Add detailed macOS architecture detection (ARM64 and Intel)
- Enhance system type logging with informative messages
- Provide more precise OS and architecture identification
2025-02-25 11:50:39 +08:00
yeongpin
d852bcff50 feat: Enhance Multilingual Support and System Language Detection
- Add automatic system language detection for Windows and Unix-like systems
- Update localization files with new translation keys
- Improve language handling in various modules
- Translate more UI messages to English
- Add GitHub link to logo display
- Bump version to 1.4.04
2025-02-25 10:46:36 +08:00
Pin Studios
4c91525082 Merge pull request #94 from UntaDotMy/main
feat: Add support for Cursor v0.46.X and improve email service
2025-02-25 10:35:34 +08:00
BilaUnta
b565232ba6 bump version 2025-02-25 09:26:10 +08:00
BilaUnta
5725a2b2f7 improve for 1.4.03 2025-02-25 09:22:22 +08:00
yeongpin
6ec6f8bdbf Add license section to README.md 2025-02-24 12:11:20 +08:00
yeongpin
5c1bcff55a Update Readme 2025-02-24 11:43:54 +08:00
yeongpin
025c4e2ef3 Update readme 2025-02-24 11:30:34 +08:00
yeongpin
6ac1294bee Add Disable Cursor Auto Update Feature 2025-02-24 11:18:57 +08:00
Pin Studios
fb443592d3 Update README.md 2025-02-21 18:02:22 +08:00
Pin Studios
c30a62b072 Update README.md 2025-02-17 16:30:55 +08:00
Pin Studios
8bc509cccf Update README.md 2025-02-17 16:29:16 +08:00
yeongpin
9f814708d1 Update CHANGELOG.md for v1.3.02 release 2025-02-13 15:33:09 +08:00
Pin Studios
1889f9827a Update README.md 2025-02-13 15:28:00 +08:00
yeongpin
57d2d40e67 update coffee 2025-02-13 15:24:40 +08:00
yeongpin
04fa6ee935 update 2025-02-13 15:07:29 +08:00
yeongpin
ed1e0f787e fixed some env 2025-02-13 15:05:45 +08:00
yeongpin
240716e45f fixed small update 2025-02-13 15:05:20 +08:00
yeongpin
35bbe6c93c update cl 2025-02-13 13:25:32 +08:00
yeongpin
fed50a31cc Add manual Cursor registration with manual email input 2025-02-13 13:24:32 +08:00
yeongpin
57ea4dd25a update changelog 2025-02-13 12:16:54 +08:00
yeongpin
72eadfaf74 update 2025-02-13 12:13:43 +08:00
yeongpin
9521c0ce75 remove ublock 2025-02-13 12:13:06 +08:00
yeongpin
637f923c16 change changelog 2025-02-13 11:42:48 +08:00
yeongpin
1cc93ffc22 fix & optimize 2025-02-13 11:33:20 +08:00
yeongpin
887239d80c change other mail site 2025-02-13 11:11:08 +08:00
yeongpin
5d944fa3b1 update readme 2025-02-12 15:45:32 +08:00
yeongpin
654157b371 fix update readme 2025-02-12 14:36:54 +08:00
yeongpin
f4314cc6da Big Update - Fixed Human Verify Problem 2025-02-12 14:31:11 +08:00
yeongpin
1f29b2dbbe update readme 2025-02-11 16:58:20 +08:00
yeongpin
9c2b3f2fc8 update 2025-02-11 16:53:18 +08:00
yeongpin
8e20a0ed3d update env 2025-02-11 16:50:52 +08:00
yeongpin
4122701468 test2 2025-02-08 23:07:23 +08:00
yeongpin
1ee9813155 test1 2025-02-08 23:05:50 +08:00
yeongpin
a9e4b3a5c6 test 2025-02-08 23:02:22 +08:00
yeongpin
a83851d441 update new build.yml 2025-02-08 22:54:02 +08:00
yeongpin
1e290d0417 build.yml update 2025-02-08 22:50:05 +08:00
yeongpin
53ab15604e update build 2025-02-08 22:45:32 +08:00
yeongpin
638916e5ef update github 2025-02-08 22:38:11 +08:00
yeongpin
9500ce1249 update new build 2025-02-08 22:22:19 +08:00
yeongpin
721c13cb2f update build 2025-02-08 22:16:02 +08:00
yeongpin
a04eed2c6b update build.yml 2025-02-08 22:12:17 +08:00
yeongpin
8107bede63 update remake 2025-02-08 22:10:00 +08:00
yeongpin
4166bc5135 update reissue 2025-02-08 22:04:14 +08:00
yeongpin
69994d2486 update v4 2025-02-08 22:02:01 +08:00
yeongpin
ffcb8ec140 update 2025-02-08 22:00:19 +08:00
yeongpin
b61b61c607 add workflow 2025-02-08 21:57:44 +08:00
yeongpin
7326d0eeb0 newest reset machine 2025-02-08 21:52:16 +08:00
yeongpin
371f00645d update readme 2025-02-05 19:33:42 +08:00
yeongpin
a873291481 update env 2025-02-05 19:22:01 +08:00
yeongpin
7710ea4bb3 Update support 0.45 2025-02-05 19:21:34 +08:00
yeongpin
13483eed77 update auth message & reset message 2025-01-17 10:12:18 +08:00
yeongpin
eb69f933af Update & Fix reset machine problem 2025-01-15 17:14:05 +08:00
yeongpin
54cd8cf323 Update 107 2025-01-15 13:54:38 +08:00
yeongpin
4e0289c86c Update New107 images 2025-01-15 13:46:54 +08:00
yeongpin
af4a1c4065 Update Readme 2025-01-15 13:44:22 +08:00
678 changed files with 6719 additions and 354334 deletions

4
.env
View File

@@ -1,2 +1,2 @@
version=1.0.7
VERSION=1.0.7
version=1.7.10
VERSION=1.7.10

View 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
View 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

View 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
View 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

244
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,244 @@
name: Build Executables
on:
workflow_dispatch:
inputs:
version:
description: 'Version number (e.g. 1.0.9)'
required: true
default: '1.7.06'
permissions:
contents: write
actions: write
packages: write
jobs:
create-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # 获取所有标签
- name: Delete existing tag if exists
run: |
if git ls-remote --tags origin | grep -q "refs/tags/v${{ github.event.inputs.version }}"; then
git push origin --delete "v${{ github.event.inputs.version }}" || true
git tag -d "v${{ github.event.inputs.version }}" || true
fi
- name: Create Tag
run: |
git tag "v${{ github.event.inputs.version }}"
git push origin "v${{ github.event.inputs.version }}"
build-windows:
needs: create-tag
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt
- name: Build EXE
run: |
pyinstaller build.spec
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: CursorFreeVIP_${{ env.VERSION }}_windows.exe
path: dist/CursorFreeVIP_${{ env.VERSION }}_windows.exe
build-macos-arm64:
needs: create-tag
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt
- name: Build MacOS ARM executable
run: |
pyinstaller build.spec
mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64"
- name: Upload MacOS ARM artifact
uses: actions/upload-artifact@v4
with:
name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64
build-linux-x64:
needs: create-tag
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt
- 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 x64 artifact
uses: actions/upload-artifact@v4
with:
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
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Install dependencies
run: |
arch -x86_64 pip3 install --upgrade pip
arch -x86_64 pip3 install pyinstaller
arch -x86_64 pip3 install -r requirements.txt
- name: Build MacOS Intel executable
env:
TARGET_ARCH: 'x86_64'
VERSION: ${{ env.VERSION }}
run: |
arch -x86_64 python3 -m PyInstaller build.spec
mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel"
- name: Upload MacOS Intel artifact
uses: actions/upload-artifact@v4
with:
name: CursorFreeVIP_${{ env.VERSION }}_mac_intel
path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel
create-release:
needs: [build-windows, build-macos-arm64, build-linux-x64, build-linux-arm64, build-macos-intel]
runs-on: ubuntu-22.04
steps:
- name: Get version
shell: bash
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release files
run: |
cd artifacts
echo "Contents of artifacts directory:"
ls -la
echo "Contents of subdirectories:"
ls -la */
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ env.VERSION }}
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_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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

51
.gitignore vendored Normal file
View File

@@ -0,0 +1,51 @@
__pycache__
server/
venv/
check_license.py
cursor_modifier.py
reset_machine.py
Run_Venv.bat
token_monitor.py
get_mac.py
.gitignore
build.bat
build.mac.command
build.py
build.sh
ENV/
test.py
install.bat
run.bat
temp_account_info.txt
.env copy
# PyInstaller
build/
dist/
*.spec2
credentials.txt
cursor_accounts.txt
recaptcha.py
install_requirements.bat
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Project specific
*.log
*.db
*.sqlite3
# Mac
run_venv.mac.command

303
CHANGELOG.md Normal file
View File

@@ -0,0 +1,303 @@
# Change Log
## v1.7.10 ( Pre - Release 版本 )
1. Add: Totally Reset Cursor | 增加完全重置 Cursor
2. Add: Multi-language Support for Totally Reset | 增加完全重置多語言支持
## v1.7.09
1. Add: Development Version Check | 增加開發版本檢查
2. Remove: Github Trial Reset | 移除 Github 試用重置
3. Fixed: Some Issues | 修復一些問題
## v1.7.08
1. Add: Google OAuth Authentication | 增加 Google OAuth 認證
2. Add: GitHub OAuth Authentication | 增加 GitHub OAuth 認證
3. Add: Lifetime Access for OAuth Users | 增加 OAuth 用戶終身訪問權限
4. Add: OAuth Authentication Integration | 增加 OAuth 認證集成
5. Update: Menu System with OAuth Options | 更新菜單系統,添加 OAuth 選項
6. Add: Multi-language Support for OAuth | 增加 OAuth 多語言支持
## v1.7.07
1. Add: Vietnamese Language | 增加越南語言
2. Add: Admin Privilege Management for Windows Executable | 增加 Windows 可執行文件管理員權限
3. Implement admin privilege detection for Windows platform | 實現 Windows 平台管理員權限檢測
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 路徑
2. Add: Config File | 增加配置文件
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
- Optimization: Improve code maintainability and make configuration management and browser settings more clear
- Improvement: The creation and update logic of the configuration file is clearer and more independent
## v1.6.03
1. Hotfix: Small Problem | 修復一些問題
## v1.6.02
1. Hotfix: Small Problem | 修復一些問題
2. Add: Test some Bypass Code | 測試一些繞過代碼
## v1.6.01
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 失敗
## v1.5.03
1. HOTFIX: Stuck on starting browser | 修復啟動瀏覽器卡住問題
2. Small Fix: Error Handling | 小修錯誤處理
3. Small Fix: Translation | 小修翻譯
4. Small Fix: Performance | 小修性能
## v1.5.02
1. Add: Generate Random Name Alias | 增加生成隨機真實姓名
2. Add: Realistic Name Input | 增加真實姓名輸入
3. Optimize: Error Handling | 優化錯誤處理
4. Optimize: Translation | 優化翻譯
5. Optimize: Performance | 優化性能
## v1.5.01
1. Add: Check Latest Version | 增加檢查最新版本
2. Add: Update Command | 增加更新命令
## v1.4.08
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調用以再次顯示菜單
3. Updated error handling to show menu instead of exiting | 更新錯誤處理以顯示菜單而不是退出
## v1.4.06
1. Add: Blocked Domains Loaded | 增加被屏蔽的域名加載
2. Fix: Cleanup Error | 修復清理進程時出錯
3. Fix: Blocked Domains Loaded Error | 修復被屏蔽的域名加載錯誤
4. Fix: Available Domains Loaded Error | 修復可用域名加載錯誤
5. Fix: Domains Filtered Error | 修復過濾後剩餘域名錯誤
6. Fix: Domains Excluded Error | 修復排除域名錯誤
## v1.4.05
1. Fix: macOS Language Detection | 修復 macOS 語言檢測
## v1.4.04
1. Change Some Language Info to English | 更改一些語言信息為英文
2. Add Auto Detect System Language | 增加自動檢測系統語言
3. Fixed Some Issues | 修復一些問題
## v1.4.03
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 集成
7. Optimize Performance and Stability | 優化性能和穩定性
## v1.4.01
1. Add Disable Cursor Auto Upgrade | 增加禁用 Cursor 自動升級
## v1.3.02
1. Add Buy Me a Coffee | 增加請我喝杯咖啡
2. Add PayPal | 增加 PayPal
3. Very Small Fix | 非常小的修復
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 選項
## v1.2.02
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 人機驗證問題
2. Change to automatic registration account | 全面改為自動註冊賬號
3. Change Mail Site | 改變郵箱網站
4. Fix Cursor Cloudflare Problem | 修復 Cursor Cloudflare 問題
## v1.1.01
<p align="center">
<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 人機驗證問題
3. Remake signup logic | 重做註冊邏輯
## v1.0.10
1. Hot Fix Mac Chrome Problem | 修復 Mac Chrome 問題
2. Fix Linux Start Donet Problem | 修復 Linux 啟動開發者問題
## v1.0.9
<p align="center">
<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 版本重置機器
2. Fix Locale Language | 修復多語言
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
4. Add Remake main.js | 重做 main.js
## v1.0.8
1. Fix New 0.45 Version Reset Machine | 修復新 0.45 版本重置機器
2. Fix Locale Language | 修復多語言
3. Add Support Crypto Machine Regedit | 增加支持加密機器註冊
## v1.0.7 - HotFix
1. Fix Reset Machine | 修復重置機器
2. Fix Locale Language | 修復多語言
## v1.0.7
1. Add Locale Language Support | 增加多語言支持
<p align="center">
<img src="./images/locale_2025-01-15_13-40-08.png" alt="locale" width="400"/><br>
</p>
## v1.0.6
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 瀏覽器控制問題
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
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>
## v1.0.5
1. Remove MachineID | 移除機器碼 ID
2. Change to automatic registration account | 全面改為自動註冊賬號
3. Use your own exclusive new account | 使用自己獨享的新賬號
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
<p align="center">
<img src="./images/pro_2025-01-14_14-40-37.png" alt="Why" width="400"/><br>
</p>
## v1.0.4
1. Fix: Cursor's configuration | 修復 Cursor 的配置問題
2. Fix Cloud Lame | 修復雲端慢速模式
## v1.0.3
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 支持
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>
</p>
## v1.0.2
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 問題
7. Fix Billing Issue | 修復計費問題
8. Fix: Cursor's configuration | 修復 Cursor 的配置問題
9. Fix cursor-slow mode | 修復 cursor-slow 模式
## v1.0.1
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 的配置的問題
## v1.0
1. Preview Image | 預覽圖<br>
<p align="center">
<img src="./images/pro_2025-01-11_00-50-40.png" alt="Cursor Pro Logo" width="400"/><br>
</p>
<p align="center">
<img src="./images/pro_2025-01-11_00-51-07.png" alt="Cursor Pro Logo" width="400"/><br>
</p>
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加,但可以通過留下 MachineID 聯繫作者
<br>
<p align="center">
<img src="./images/pro_2025-01-11_16-24-03.png" alt="Cursor Pro Logo" width="400"/><br>
</p>

5
PBlock/background.js Normal file
View File

@@ -0,0 +1,5 @@
// Import the contents of your other files
importScripts('default_filters.js', 'block.js');
// Your service worker initialization code can go here
console.log('Service worker initialized');

129
PBlock/block.js Normal file
View File

@@ -0,0 +1,129 @@
/**
* All the actual functionality of the extension; loads as part of the background page.
*
* Active ingredient is enable(), which sets up the webRequest callbacks.
*
* */
let blockingEnabled = false;
let allFilters = null;
let webRTCPrivacy = null;
function setFilters(newFilters) {
allFilters = newFilters;
chrome.storage.local.set({"filters": newFilters});
if (blockingEnabled) {
refreshFilters();
}
}
// Convert URL patterns to declarativeNetRequest rule format
function createRules(filters) {
return filters.map((filter, index) => ({
id: index + 1,
priority: 1,
action: {
type: "block"
},
condition: {
urlFilter: filter.replace("*://", "*"),
resourceTypes: [
"main_frame", "sub_frame", "stylesheet", "script", "image",
"font", "object", "xmlhttprequest", "ping", "media", "websocket"
]
}
}));
}
async function enable(icon = true) {
if (blockingEnabled) return;
if (allFilters && allFilters.length > 0) {
const rules = createRules(allFilters);
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map(rule => rule.id),
addRules: rules
});
}
blockingEnabled = true;
if (icon) {
chrome.action.setIcon({
path: {
"16": "enabled16.png",
"48": "enabled48.png"
}
});
}
}
async function disable(icon = true) {
if (!blockingEnabled) return;
const rules = await chrome.declarativeNetRequest.getDynamicRules();
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map(rule => rule.id),
addRules: []
});
blockingEnabled = false;
if (icon) {
chrome.action.setIcon({
path: {
"16": "disabled.png",
"32": "disabled.png"
}
});
}
}
async function refreshFilters() {
await disable(false);
await enable(true);
}
async function toggleEnabled() {
if (blockingEnabled) {
await disable();
} else {
await enable();
}
}
function setWebRTCPrivacy(flag, store = true) {
webRTCPrivacy = flag;
const privacySetting = flag ? "default_public_interface_only" : "default";
chrome.privacy.network.webRTCIPHandlingPolicy.set({value: privacySetting});
if (store) {
chrome.storage.local.set({"webrtc_privacy": flag});
}
}
// Initialization
chrome.storage.local.get("filters",
function(result) {
if (result["filters"] == undefined) {
console.log("Initializing filters to defaults.");
setFilters(defaultFilters);
} else {
setFilters(result["filters"]);
allFilters = result["filters"];
}
// toggle blocking on-off via the extension icon
chrome.action.onClicked.addListener(toggleEnabled);
// initialize blocking
enable();
}
);
chrome.storage.local.get("webrtc_privacy",
function(result) {
if (result["webrtc_privacy"] == undefined) {
console.log("Initializing WebRTC privacy to default.");
setWebRTCPrivacy(false, true);
} else {
setWebRTCPrivacy(result["webrtc_privacy"], false);
}
}
);

14
PBlock/default_filters.js Normal file
View File

@@ -0,0 +1,14 @@
defaultFilters = [
// personally, I can't stand the like box
//"http://www.facebook.com/plugins/likebox.php?*",
"*://*.doubleclick.net/*",
"*://partner.googleadservices.com/*",
"*://*.googlesyndication.com/*",
"*://*.google-analytics.com/*",
"*://creative.ak.fbcdn.net/*",
"*://*.adbrite.com/*",
"*://*.exponential.com/*",
"*://*.quantserve.com/*",
"*://*.scorecardresearch.com/*",
"*://*.zedo.com/*",
]

BIN
PBlock/enabled16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

BIN
PBlock/enabled48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

29
PBlock/manifest.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "PBlock",
"version": "0.1",
"manifest_version": 3,
"author": "yeongpin",
"url": "https://github.com/yeongpin/PBlock",
"description": "Just a simple blocker.",
"action": {
"default_icon": "enabled48.png"
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "enabled16.png",
"48": "enabled48.png"
},
"permissions": [
"declarativeNetRequest",
"storage",
"privacy"
],
"host_permissions": [
"http://*/*",
"https://*/*",
"ws://*/*",
"wss://*/*"
]
}

5
PBlock/rules.json Normal file
View File

@@ -0,0 +1,5 @@
{
"id": "ruleset_1",
"version": "1.0",
"rules": []
}

258
README.md
View File

@@ -1,7 +1,8 @@
# ➤ Cursor Free VIP
<div align="center">
<p align="center">
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200"/>
<img src="./images/logo.png" alt="Cursor Pro Logo" width="200" style="border-radius: 6px;"/>
</p>
<p align="center">
@@ -9,213 +10,206 @@
[![Release](https://img.shields.io/github/v/release/yeongpin/cursor-free-vip?style=flat-square&logo=github&color=blue)](https://github.com/yeongpin/cursor-free-vip/releases/latest)
[![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC_BY--NC--ND_4.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-nd/4.0/)
[![Stars](https://img.shields.io/github/stars/yeongpin/cursor-free-vip?style=flat-square&logo=github)](https://github.com/yeongpin/cursor-free-vip/stargazers)
[![Download](https://img.shields.io/github/downloads/yeongpin/cursor-free-vip/total?style=flat-square&logo=github&color=52c41a)](https://github.com/yeongpin/cursor-free-vip/releases/latest)
</p>
<h4>Support Latest 0.47.x Version | 支持最新 0.47.x 版本</h4>
This is a tool to automatically register (except for Google verification code), 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.
這是一個自動化工具,自動註冊除了Google驗證碼),支持 Windows 和 macOS 系統完成Auth驗證重置Cursor的配置。
這是一個自動化工具,自動註冊,支持 Windows 和 macOS 系統,完成 Auth 驗證,重置 Cursor 的配置。
<p align="center">
<img src="./images/pro_2025-01-14_14-40-37.png" alt="new" width="400"/><br>
<img src="./images/new_2025-02-27_10-42-44.png" alt="new" width="400" style="border-radius: 6px;"/><br>
</p>
<br>
<p align="center">
<img src="./images/free_2025-01-14_14-59-15.png" alt="free" width="400"/><br>
</p>
##### If you don't have Google Chrome, you can download it from [here](https://www.google.com/intl/en_pk/chrome/)
## ⚠️ Google Recaptcha need to be manually verified, don't be lazy, move your fingers, verify it, otherwise it will keep prompting you to verify ⚠️
### If you dont 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 | 更新日志
## 🔄 更新日志
<details open>
<summary>v1.0.7</summary>
1. Add Locale Language Support | 增加多語言支持
<p align="center">
<img src="./images/locale_2025-01-15_13-40-08.png" alt="locale" width="400"/><br>
</p>
</details>
<details>
<summary>v1.0.6</summary>
1. Add Quit Cursor Option | 增加退出Cursor選項
2. Add Recaptcha Path Patch | 增加Recaptcha路徑修復
3. Fix Admin Permission | 修復管理員權限問題
4. Remove all need admin permission | 移除所有需要管理員權限
</details>
<details>
<summary>v1.0.5 - HotFix</summary>
1. Fix: Mac Browser Control | 修復Mac瀏覽器控制問題
2. Fix: Verification Code Cant Patch | 修復驗證碼無法修復問題
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>
</details>
<details>
<summary>v1.0.5</summary>
1. Remove MachineID | 移除機器碼ID
2. Change to automatic registration account | 全面改為自動註冊賬號
3. Use your own exclusive new account | 使用自己獨享的新賬號
4. Fully automatic reset machine configuration | 全面自動化重置機器配置
<p align="center">
<img src="./images/pro_2025-01-14_14-40-37.png" alt="Why" width="400"/><br>
</p>
</details>
<details>
<summary>v1.0.4</summary>
1. Fix: Cursor's configuration | 修復Cursor的配置問題
2. Fix Cloud Lame | 修復雲端慢速模式
</details>
<details>
<summary>v1.0.3</summary>
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支持
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>
</p>
</details>
<details>
<summary>v1.0.2</summary>
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 問題
7. Fix Billing Issue | 修復計費問題
8. Fix: Cursor's configuration | 修復Cursor的配置問題
9. Fix cursor-slow mode | 修復cursor-slow模式
</details>
<details>
<summary>v1.0.1</summary>
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的配置的問題
</details>
<details>
<summary>v1.0</summary>
1. Preview Image | 預覽圖<br>
<p align="center">
<img src="./images/pro_2025-01-11_00-50-40.png" alt="Cursor Pro Logo" width="400"/><br>
</p>
<p align="center">
<img src="./images/pro_2025-01-11_00-51-07.png" alt="Cursor Pro Logo" width="400"/><br>
</p>
2. Add usage period,but can be contacted by leaving MachineID | 不得已才添加但可以通過留下MachineID 聯繫作者
<br>
<p align="center">
<img src="./images/pro_2025-01-11_16-24-03.png" alt="Cursor Pro Logo" width="400"/><br>
</p>
</details>
[Watch Change Log | 查看更新日志](CHANGELOG.md)
## ✨ Features | 功能特點
* Automatically register Cursor membership<br>自動註冊Cursor會員<br>
* 🌟 Google OAuth Authentication with Lifetime Access<br>使用 Google OAuth 認證(終身訪問)<br>
* Except for Google verification code<br>除了Google驗證碼<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 | 如何使用
|⚠Must logout your account before running the script⚠|⚠️必須先登出你的帳戶再運行腳本⚠️ |
|:---:|:---:|
<br>
<details open>
<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
## ❗ Note | 注意事項
* Confirm that Cursor is closed before running the script <br>請確保在運行腳本前已經關閉 Cursor<br>
📝 Config | 文件配置
`Win / Macos / Linux Path | 路徑 [Documents/.cursor-free-vip/config.ini]`
<details>
<summary><b>⭐ Config | 文件配置</b></summary>
* Do not close this script when using Cursor <br>使用Cursor時請勿關閉此腳本<br>
```
[Chrome]
# Default Google Chrome Path | 默認Google Chrome 遊覽器路徑
chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe
[Turnstile]
# Handle Tuenstile Wait Time | 等待人機驗證時間
handle_turnstile_time = 2
# Handle Tuenstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合)
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>
* This tool is only for learning and research purposes <br>此工具僅供學習和研究使用<br>
* 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 | 貢獻
歡迎提交 Issue 和 Pull Request
<a href="https://github.com/yeongpin/cursor-free-vip/graphs/contributors">
<img src="https://contrib.rocks/image?repo=yeongpin/cursor-free-vip" />
</a>
<br /><br />
## 📩 Disclaimer | 免責聲明
本工具僅供學習和研究使用,使用本工具所產生的任何後果由使用者自行承擔。 <br>
源代碼靈感來之 | Original code inspiration from [Here](https://github.com/hmhm2022/gpt-cursor-auto)
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 | 請我喝杯咖啡
<div align="center">
<table>
<tr>
<td>
<img src="./images/provi-code.jpg" alt="buy_me_a_coffee" width="280"/><br>
</td>
<td>
<img src="./images/paypal.png" alt="buy_me_a_coffee" width="280"/><br>
</td>
</tr>
</table>
</div>
## ⭐ Star History | 星星數
<div align="center">
[![Star History Chart](https://api.star-history.com/svg?repos=yeongpin/cursor-free-vip&type=Date)](https://star-history.com/#yeongpin/cursor-free-vip&Date)
</div>
## 📝 License | 授權
本項目採用 [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.

5
block_domain.txt Normal file
View File

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

View File

@@ -1,98 +0,0 @@
from DrissionPage import ChromiumOptions, ChromiumPage
import sys
import os
import logging
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)
extension_block_path = self.get_extension_block()
co.add_extension(extension_block_path)
extension_recaptcha_path = self.get_extension_recaptcha()
co.add_extension(extension_recaptcha_path)
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/130.0.6723.92 Safari/537.36"
)
co.set_pref("credentials_enable_service", False)
co.set_argument("--hide-crash-restore-bubble")
co.auto_port()
# Mac 系统特殊处理
if sys.platform == "darwin":
co.set_argument("--no-sandbox")
co.set_argument("--disable-gpu")
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, "uBlock0.chromium")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "uBlock0.chromium")
if not os.path.exists(extension_path):
raise FileNotFoundError(f"插件不存在: {extension_path}")
return extension_path
def get_extension_recaptcha(self):
"""获取插件路径"""
root_dir = os.getcwd()
extension_path = os.path.join(root_dir, "recaptchaPatch")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "recaptchaPatch")
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

View File

@@ -2,28 +2,28 @@
chcp 65001 > nul
cls
:: 檢查是否以管理員權限運行
:: Check if running with administrator privileges
net session >nul 2>&1
if %errorLevel% == 0 (
:: 如果是管理員權限,只創建虛擬環境後就降權運行
:: If running with administrator privileges, create virtual environment and then run with normal user privileges
if not exist venv (
echo 正在創建虛擬環境...
python -m venv venv
)
:: 降權運行剩餘的步驟
:: Run remaining steps with normal user privileges
echo 以普通用戶權限繼續...
powershell -Command "Start-Process -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0 run' -Verb RunAs:NO"
exit /b
) else (
:: 檢查是否是第二階段運行
:: Check if running in second stage
if "%1"=="run" (
goto RUN_BUILD
) else (
:: 如果是普通權限且需要創建虛擬環境,請求管理員權限
:: If running with normal privileges and creating virtual environment is required, request administrator privileges
if not exist venv (
echo ⚠️ 需要管理員權限來創建虛擬環境
echo 正在請求管理員權限...
echo ⚠️ Requires administrator privileges to create virtual environment
echo Requesting administrator privileges...
powershell -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0'"
exit /b
) else (
@@ -33,30 +33,30 @@ if %errorLevel% == 0 (
)
:RUN_BUILD
echo 啟動虛擬環境...
echo Starting virtual environment...
call venv\Scripts\activate.bat
if errorlevel 1 (
echo啟動虛擬環境失敗
echoFailed to start virtual environment
pause
exit /b 1
)
:: 檢查並安裝缺失的依賴
echo 檢查依賴...
:: Check and install missing dependencies
echo Checking dependencies...
for /f "tokens=1" %%i in (requirements.txt) do (
pip show %%i >nul 2>&1 || (
echo 安裝 %%i...
echo Installing %%i...
pip install %%i
)
)
echo 開始構建...
echo Starting build...
python build.py
if errorlevel 1 (
echo構建失敗
echoBuild failed
pause
exit /b 1
)
echo完成!
echoCompleted!
pause

View File

@@ -8,7 +8,7 @@ import shutil
from logo import print_logo
from dotenv import load_dotenv
# 忽略特定警告
# Ignore specific warnings
warnings.filterwarnings("ignore", category=SyntaxWarning)
class LoadingAnimation:
@@ -50,21 +50,21 @@ def simulate_progress(message, duration=1.0, steps=20):
progress_bar(i, steps, prefix="Progress:", length=40)
def build():
# 清理屏幕
# Clean screen
os.system("cls" if platform.system().lower() == "windows" else "clear")
# 顯示 logo
# Display logo
print_logo()
# 清理 PyInstaller 緩存
print("\033[93m🧹 清理構建緩存...\033[0m")
# Clean PyInstaller cache
print("\033[93m🧹 Cleaning build cache...\033[0m")
if os.path.exists('build'):
shutil.rmtree('build')
# 重新加載環境變量以確保獲取最新版本
# Reload environment variables to ensure getting the latest version
load_dotenv(override=True)
version = os.getenv('VERSION', '1.0.0')
print(f"\033[93m📦 正在構建版本: v{version}\033[0m")
print(f"\033[93m📦 Building version: v{version}\033[0m")
try:
simulate_progress("Preparing build environment...", 0.5)
@@ -72,7 +72,7 @@ def build():
loading = LoadingAnimation()
loading.start("Building in progress")
# 根据系统类型设置输出名称
# Set output name based on system type
system = platform.system().lower()
if system == "windows":
os_type = "windows"
@@ -86,7 +86,7 @@ def build():
output_name = f"CursorFreeVIP_{version}_{os_type}"
# 构建命令
# Build command
build_command = f'pyinstaller --clean --noconfirm build.spec'
output_path = os.path.join('dist', f'{output_name}{ext}')
@@ -95,16 +95,16 @@ def build():
loading.stop()
if os.path.exists(output_path):
print(f"\n\033[92m✅ 構建完成!")
print(f"📦 可執行文件位於: {output_path}\033[0m")
print(f"\n\033[92m✅ Build completed!")
print(f"📦 Executable file located: {output_path}\033[0m")
else:
print("\n\033[91m❌ 構建失敗:未找到輸出文件\033[0m")
print("\n\033[91m❌ Build failed: Output file not found\033[0m")
return False
except Exception as e:
if loading:
loading.stop()
print(f"\n\033[91m❌ 構建過程出錯: {str(e)}\033[0m")
print(f"\n\033[91m❌ Build process error: {str(e)}\033[0m")
return False
return True

View File

@@ -8,7 +8,7 @@ NC='\033[0m' # No Color
# 检查并安装必要的依赖
check_dependencies() {
echo -e "${YELLOW}检查系统依赖...${NC}"
echo -e "${YELLOW}Checking system dependencies...${NC}"
# 检查是否为 Ubuntu/Debian
if [ -f /etc/debian_version ]; then
@@ -16,42 +16,42 @@ check_dependencies() {
PACKAGES="python3 python3-pip python3-venv"
for pkg in $PACKAGES; do
if ! dpkg -l | grep -q "^ii $pkg "; then
echo -e "${YELLOW}安装 $pkg...${NC}"
echo -e "${YELLOW}Installing $pkg...${NC}"
sudo apt-get update
sudo apt-get install -y $pkg
fi
done
else
echo -e "${RED}不支持的系统,请手动安装 python3, pip3 python3-venv${NC}"
echo -e "${RED}Unsupported system, please install python3, pip3 and python3-venv manually${NC}"
exit 1
fi
}
# 创建并激活虚拟环境
setup_venv() {
echo -e "${GREEN}正在创建虚拟环境...${NC}"
echo -e "${GREEN}Creating virtual environment...${NC}"
python3 -m venv venv
echo -e "${GREEN}启动虚拟环境...${NC}"
echo -e "${GREEN}Starting virtual environment...${NC}"
. ./venv/bin/activate || source ./venv/bin/activate
}
# 安装依赖
install_dependencies() {
echo -e "${GREEN}安装依赖...${NC}"
echo -e "${GREEN}Installing dependencies...${NC}"
python3 -m pip install --upgrade pip
pip3 install -r requirements.txt
}
# 构建程序
build_program() {
echo -e "${GREEN}开始构建...${NC}"
echo -e "${GREEN}Starting build...${NC}"
python3 build.py
}
# 清理
cleanup() {
echo -e "${GREEN}清理虚拟环境...${NC}"
echo -e "${GREEN}Cleaning virtual environment...${NC}"
deactivate 2>/dev/null || true
rm -rf venv
}
@@ -73,8 +73,8 @@ main() {
# 清理
cleanup
echo -e "${GREEN}完成!${NC}"
echo "按任意键退出..."
echo -e "${GREEN}Completed!${NC}"
echo "Press any key to exit..."
# 使用兼容的方式读取输入
if [ "$(uname)" = "Linux" ]; then
read dummy

View File

@@ -24,20 +24,24 @@ a = Analysis(
binaries=[],
datas=[
('turnstilePatch', 'turnstilePatch'),
('recaptchaPatch', 'recaptchaPatch'),
('uBlock0.chromium', 'uBlock0.chromium'),
('PBlock', 'PBlock'),
('locales', 'locales'),
('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
View 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)

View File

@@ -1,374 +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 switch_to_tab(self, browser):
"""切换到指定浏览器窗口"""
try:
self.browser = browser
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.switch_tab_success')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.switch_tab_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_current_tab(self):
"""获取当前标签页"""
return self.browser
def generate_new_email(self):
"""点击新的按钮生成新邮箱"""
try:
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.generate_email')}...{Style.RESET_ALL}")
new_button = self.browser.ele('xpath://button[contains(@class, "egenbut")]')
if new_button:
new_button.click()
time.sleep(1) # 等待生成
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.generate_email_success')}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.generate_email_failed')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.browser_error', error=str(e))}{Style.RESET_ALL}")
return False
def select_email_domain(self, domain_index=None):
"""选择邮箱域名如果不指定index则随机选择"""
try:
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.select_email_domain')}...{Style.RESET_ALL}")
# 找到下拉框
select_element = self.browser.ele('xpath://select[@id="seldom"]')
if select_element:
# 获取所有选项,包括两个 optgroup 下的所有 option
all_options = []
# 获取 "新的" 组下的选项
new_options = self.browser.eles('xpath://select[@id="seldom"]/optgroup[@label="-- 新的 --"]/option')
all_options.extend(new_options)
# 获取 "其他" 组下的选项
other_options = self.browser.eles('xpath://select[@id="seldom"]/optgroup[@label="-- 其他 --"]/option')
all_options.extend(other_options)
if all_options:
# 如果没有指定索引,随机选择一个
if domain_index is None:
domain_index = random.randint(0, len(all_options) - 1)
if domain_index < len(all_options):
# 获取选中选项的文本
selected_domain = all_options[domain_index].text
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.select_email_domain')}: {selected_domain}{Style.RESET_ALL}")
# 点击选择
all_options[domain_index].click()
time.sleep(1)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.select_email_domain_success')}{Style.RESET_ALL}")
return True
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.no_available_domain_options', count=len(all_options))}{Style.RESET_ALL}")
return False
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.no_domain_select_box')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.select_email_domain_failed', error=str(e))}{Style.RESET_ALL}")
return False
def wait_for_page_load(self, seconds=2):
"""等待页面加载"""
time.sleep(seconds)
def navigate_to(self, url):
"""导航到指定URL"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.navigate_to', url=url)}...{Style.RESET_ALL}")
self.browser.get(url)
self.wait_for_page_load()
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.browser_error', error=str(e))}{Style.RESET_ALL}")
return False
def copy_and_get_email(self):
"""获取邮箱地址"""
try:
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.generate_email')}...{Style.RESET_ALL}")
# 等待元素加载
time.sleep(1)
# 获取邮箱名称
try:
email_div = self.browser.ele('xpath://div[@class="segen"]//div[contains(@style, "color: #e5e5e5")]')
if email_div:
email_name = email_div.text.split()[0]
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.get_email_name')}: {email_name}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_email_name_failed')}{Style.RESET_ALL}")
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_email_name_failed', error=str(e))}{Style.RESET_ALL}")
return None
# 直接使用上一步选择的域名
try:
domain = self.browser.ele('xpath://select[@id="seldom"]').value
if not domain: # 如果获取不到value尝试获取选中的选项文本
selected_option = self.browser.ele('xpath://select[@id="seldom"]/option[1]')
domain = selected_option.text if selected_option else "@yopmail.com" # 使用默认域名作为后备
except:
domain = "@yopmail.com" # 如果出错,使用默认域名
# 组合完整邮箱地址
full_email = f"{email_name}{domain}"
print(f"{Fore.GREEN}{EMOJI['MAIL']} {self.translator.get('control.get_email_address')}: {full_email}{Style.RESET_ALL}")
return full_email
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_email_address_failed', error=str(e))}{Style.RESET_ALL}")
return None
def view_mailbox(self):
"""点击查看邮箱按钮"""
try:
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.enter_mailbox')}...{Style.RESET_ALL}")
view_button = self.browser.ele('xpath://button[contains(@class, "egenbut") and contains(.//span, "查看邮箱")]')
if view_button:
view_button.click()
time.sleep(2) # 等待页面加载
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.enter_mailbox_success')}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.no_view_mailbox_button')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.enter_mailbox_failed', error=str(e))}{Style.RESET_ALL}")
return False
def refresh_mailbox(self):
"""刷新邮箱获取最新信息"""
try:
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('control.refresh_mailbox')}...{Style.RESET_ALL}")
refresh_button = self.browser.ele('xpath://button[@id="refresh"]')
if refresh_button:
refresh_button.click()
time.sleep(2) # 等待刷新完成
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.refresh_mailbox_success')}{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.no_refresh_button')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.refresh_mailbox_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_verification_code(self):
"""从邮件中获取验证码"""
try:
# 尝试所有可能的样式组合
selectors = [
# 新样式
'xpath://div[contains(@style, "font-family:-apple-system") and contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px") and contains(@style, "color:#202020")]',
# 带行高的样式
'xpath://div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px") and contains(@style, "line-height:30px")]',
# rgba 颜色样式
'xpath://div[contains(@style, "font-size: 28px") and contains(@style, "letter-spacing: 2px") and contains(@style, "color: rgba(32, 32, 32, 1)")]',
# 宽松样式
'xpath://div[contains(@style, "font-size:28px") and contains(@style, "letter-spacing:2px")]'
]
# 依次尝试每个选择器
for selector in selectors:
code_div = self.browser.ele(selector)
if code_div:
verification_code = code_div.text.strip()
if verification_code.isdigit() and len(verification_code) == 6:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.found_verification_code')}: {verification_code}{Style.RESET_ALL}")
return verification_code
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.no_valid_verification_code')}{Style.RESET_ALL}")
return None
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_verification_code_error', 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}")

View File

@@ -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,25 +22,68 @@ EMOJI = {
class CursorAuth:
def __init__(self, translator=None):
self.translator = translator
# 判断操作系统
if os.name == "nt": # Windows
self.db_path = os.path.join(
os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb"
)
else: # macOS
self.db_path = os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
)
# 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
try:
self.conn = sqlite3.connect(self.db_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
except sqlite3.Error as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e))}{Style.RESET_ALL}")
return
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()
@@ -54,31 +98,34 @@ 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}")
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"))
if email is not None:
updates.append(("cursorAuth/cachedEmail", email))
if access_token is not None:
updates.append(("cursorAuth/accessToken", access_token))
if refresh_token is not None:
updates.append(("cursorAuth/refreshToken", refresh_token))
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
# 使用事务来确保数据完整性
# 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("""
@@ -90,7 +137,7 @@ class CursorAuth:
UPDATE ItemTable SET value = ?
WHERE key = ?
""", (value, key))
print(f"{EMOJI['INFO']} {Fore.CYAN}Updating {key.split('/')[-1]}...{Style.RESET_ALL}")
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair')} {key.split('/')[-1]}...{Style.RESET_ALL}")
cursor.execute("COMMIT")
print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully')}{Style.RESET_ALL}")
@@ -101,14 +148,12 @@ class CursorAuth:
raise e
except sqlite3.Error as e:
print(f"\n{EMOJI['ERROR']} {Fore.RED}{self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}")
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"\n{EMOJI['ERROR']} {Fore.RED}{self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}")
print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}")
return False
finally:
if conn:
conn.close()
print(f"{EMOJI['DB']} {Fore.CYAN}{self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}")
print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}")

View File

@@ -2,18 +2,16 @@ 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
os.environ["PYTHONVERBOSE"] = "0"
os.environ["PYINSTALLER_VERBOSE"] = "0"
# 初始化colorama
# Initialize colorama
init()
# 定义emoji常量
# Define emoji constants
EMOJI = {
'START': '🚀',
'FORM': '📝',
@@ -33,9 +31,8 @@ EMOJI = {
class CursorRegistration:
def __init__(self, translator=None):
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,260 +42,107 @@ class CursorRegistration:
self.signup_tab = None
self.email_tab = None
# 账号信息
# Account information
self.password = self._generate_password()
self.first_name = self._generate_name()
self.last_name = self._generate_name()
# Generate first name and last name separately
first_name = random.choice([
"James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
"Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
"Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
])
self.last_name = random.choice([
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
])
# Modify first letter of first name
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
self.first_name = new_first_letter + first_name[1:]
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
def _generate_password(self, length=12):
"""Generate Random Password"""
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length))
def _generate_name(self, length=6):
"""Generate Random Name"""
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rest_letters = ''.join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length-1))
return first_letter + rest_letters
def setup_email(self):
"""设置邮箱"""
"""Setup Email"""
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.browser_start')}...{Style.RESET_ALL}")
self.browser = self.browser_manager.init_browser()
self.controller = BrowserControl(self.browser, self.translator)
# 打开邮箱生成器页面(第一个标签页)
self.controller.navigate_to(self.mail_url)
self.email_tab = self.browser # 保存邮箱标签页
self.controller.email_tab = self.email_tab # 同时保存到controller
# Create a temporary email using new_tempemail, passing translator
from new_tempemail import NewTempEmail
self.temp_email = NewTempEmail(self.translator) # Pass translator
# 生成新邮箱
self.controller.generate_new_email()
# Create a temporary email
email_address = self.temp_email.create_email()
if not email_address:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_create_failed')}{Style.RESET_ALL}")
return False
# 选择随机域名
self.controller.select_email_domain()
# Save email address
self.email_address = email_address
self.email_tab = self.temp_email # Pass NewTempEmail instance
# 获取邮箱地址
self.email_address = self.controller.copy_and_get_email()
if self.email_address:
print(f"{EMOJI['MAIL']}{Fore.CYAN} {self.translator.get('register.get_email_address')}: {self.email_address}{Style.RESET_ALL}")
# 进入邮箱
if self.controller.view_mailbox():
return True
return False
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.setup_error', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
return False
def register_cursor(self):
"""注册 Cursor"""
signup_browser_manager = None
browser_tab = None
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
# 创建新的浏览器实例用于注册
from browser import BrowserManager
signup_browser_manager = BrowserManager(noheader=True)
self.signup_tab = signup_browser_manager.init_browser()
# Directly use new_signup.py to sign up
from new_signup import main as new_signup_main
# 访问注册页面
self.signup_tab.get(self.sign_up_url)
time.sleep(2)
# 填写注册表单
if self.signup_tab.ele("@name=first_name"):
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.filling_form')}...{Style.RESET_ALL}")
self.signup_tab.ele("@name=first_name").input(self.first_name)
time.sleep(random.uniform(1, 2))
self.signup_tab.ele("@name=last_name").input(self.last_name)
time.sleep(random.uniform(1, 2))
self.signup_tab.ele("@name=email").input(self.email_address)
time.sleep(random.uniform(1, 2))
self.signup_tab.ele("@type=submit").click()
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.basic_info_submitted')}...{Style.RESET_ALL}")
# 处理 Turnstile 验证
self._handle_turnstile()
# 设置密码
if self.signup_tab.ele("@name=password"):
print(f"{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.set_password')}...{Style.RESET_ALL}")
self.signup_tab.ele("@name=password").input(self.password)
time.sleep(random.uniform(1, 2))
self.signup_tab.ele("@type=submit").click()
# Execute the new registration process, passing translator
result, browser_tab = new_signup_main(
email=self.email_address,
password=self.password,
first_name=self.first_name,
last_name=self.last_name,
email_tab=self.email_tab,
controller=self.controller,
translator=self.translator
)
self._handle_turnstile()
# 等待并获取验证码
time.sleep(5) # 等待验证码邮件
self.browser.refresh()
if result:
# Use the returned browser instance to get account information
self.signup_tab = browser_tab # Save browser instance
success = self._get_account_info()
# Close browser after getting information
if browser_tab:
try:
browser_tab.quit()
except:
pass
return success
# 获取验证码设置60秒超时
verification_code = None
max_attempts = 10 # 增加到10次尝试
retry_interval = 5 # 每5秒重试一次
start_time = time.time()
timeout = 60 # 60秒超时
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.start_getting_verification_code')}...{Style.RESET_ALL}")
return False
for attempt in range(max_attempts):
# 检查是否超时
if time.time() - start_time > timeout:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.get_verification_code_timeout')}...{Style.RESET_ALL}")
break
verification_code = self.controller.get_verification_code()
if verification_code:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.get_verification_code_success')}: {verification_code}{Style.RESET_ALL}")
break
remaining_time = int(timeout - (time.time() - start_time))
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.try_get_verification_code', attempt=attempt + 1, remaining_time=remaining_time)}...{Style.RESET_ALL}")
# 刷新邮箱
self.browser.refresh()
time.sleep(retry_interval)
if verification_code:
# 在注册页面填写验证码
for i, digit in enumerate(verification_code):
self.signup_tab.ele(f"@data-index={i}").input(digit)
time.sleep(random.uniform(0.1, 0.3))
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.verification_code_filled')}...{Style.RESET_ALL}")
time.sleep(3)
self._handle_turnstile()
# 检查当前URL
current_url = self.signup_tab.url
if "authenticator.cursor.sh" in current_url:
print(f"{Fore.CYAN}{EMOJI['VERIFY']} {self.translator.get('register.detect_login_page')}...{Style.RESET_ALL}")
# 填写邮箱
email_input = self.signup_tab.ele('@name=email')
if email_input:
email_input.input(self.email_address)
time.sleep(random.uniform(1, 2))
# 点击提交
submit_button = self.signup_tab.ele('@type=submit')
if submit_button:
submit_button.click()
time.sleep(2)
# 处理 Turnstile 验证
self._handle_turnstile()
# 填写密码
password_input = self.signup_tab.ele('@name=password')
if password_input:
password_input.input(self.password)
time.sleep(random.uniform(1, 2))
# 点击提交
submit_button = self.signup_tab.ele('@type=submit')
if submit_button:
submit_button.click()
time.sleep(2)
# 处理 Turnstile 验证
self._handle_turnstile()
# 等待跳转到设置页面
max_wait = 30
start_time = time.time()
while time.time() - start_time < max_wait:
if "cursor.com/settings" in self.signup_tab.url:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.login_success_and_jump_to_settings_page')}...{Style.RESET_ALL}")
break
time.sleep(1)
# 获取账户信息
result = self._get_account_info()
# 关闭注册窗口
if signup_browser_manager:
signup_browser_manager.quit()
return result
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.get_verification_code_timeout')}...{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
return False
finally:
# 确保在任何情况下都关闭注册窗口
if signup_browser_manager:
signup_browser_manager.quit()
def _handle_turnstile(self):
"""处理 Turnstile 验证"""
print(f"{Fore.YELLOW}{EMOJI['VERIFY']} {self.translator.get('register.handle_turnstile')}...{Style.RESET_ALL}")
# 设置最大等待时间(秒)
max_wait_time = 10 # 增加等待时间
start_time = time.time()
while True:
try:
# 检查是否超时
if time.time() - start_time > max_wait_time:
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.no_turnstile')}...{Style.RESET_ALL}")
time.sleep(2)
break
# Ensure browser is closed in any case
if browser_tab:
try:
challengeCheck = (
self.signup_tab.ele("@id=cf-turnstile", timeout=1)
.child()
.shadow_root.ele("tag:iframe")
.ele("tag:body")
.sr("tag:input")
)
if challengeCheck:
challengeCheck.click()
time.sleep(3)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.turnstile_passed')}{Style.RESET_ALL}")
time.sleep(2)
break
browser_tab.quit()
except:
pass
try:
if (self.signup_tab.ele("@name=password", timeout=0.5) or
self.signup_tab.ele("@name=email", timeout=0.5) or
self.signup_tab.ele("@data-index=0", timeout=0.5)):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.turnstile_passed')}{Style.RESET_ALL}")
time.sleep(2)
break
except:
pass
time.sleep(1)
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.error', error=str(e))}{Style.RESET_ALL}")
time.sleep(2)
break
time.sleep(2)
def _get_account_info(self):
"""获取账户信息和 Token"""
"""Get Account Information and Token"""
try:
self.signup_tab.get(self.settings_url)
time.sleep(2)
@@ -313,6 +157,7 @@ class CursorRegistration:
if usage_ele:
total_usage = usage_ele.text.split("/")[-1].strip()
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
@@ -349,22 +194,22 @@ class CursorRegistration:
return False
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
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
f.write(f"\n{'='*50}\n")
f.write(f"Email: {self.email_address}\n")
@@ -381,7 +226,7 @@ class CursorRegistration:
return False
def start(self):
"""启动注册流程"""
"""Start Registration Process"""
try:
if self.setup_email():
if self.register_cursor():
@@ -389,11 +234,15 @@ class CursorRegistration:
return True
return False
finally:
if self.browser_manager:
self.browser_manager.quit()
# Close email tab
if hasattr(self, 'temp_email'):
try:
self.temp_email.close()
except:
pass
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
"""更新Cursor的认证信息的便捷函数"""
"""Update Cursor Auth Info"""
auth_manager = CursorAuth(translator=self.translator)
return auth_manager.update_auth(email, access_token, refresh_token)

View File

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

View File

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

270
cursor_register_manual.py Normal file
View File

@@ -0,0 +1,270 @@
import os
from colorama import Fore, Style, init
import time
import random
from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter
os.environ["PYTHONVERBOSE"] = "0"
os.environ["PYINSTALLER_VERBOSE"] = "0"
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
'START': '🚀',
'FORM': '📝',
'VERIFY': '🔄',
'PASSWORD': '🔑',
'CODE': '📱',
'DONE': '',
'ERROR': '',
'WAIT': '',
'SUCCESS': '',
'MAIL': '📧',
'KEY': '🔐',
'UPDATE': '🔄',
'INFO': ''
}
class CursorRegistration:
def __init__(self, translator=None):
self.translator = translator
# Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
self.controller = None
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
self.settings_url = "https://www.cursor.com/settings"
self.email_address = None
self.signup_tab = None
self.email_tab = None
# Generate account information
self.password = self._generate_password()
# Generate first name and last name separately
first_name = random.choice([
"James", "John", "Robert", "Michael", "William", "David", "Joseph", "Thomas",
"Emma", "Olivia", "Ava", "Isabella", "Sophia", "Mia", "Charlotte", "Amelia",
"Liam", "Noah", "Oliver", "Elijah", "Lucas", "Mason", "Logan", "Alexander"
])
self.last_name = random.choice([
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
"Anderson", "Wilson", "Taylor", "Thomas", "Moore", "Martin", "Jackson", "Lee",
"Thompson", "White", "Harris", "Clark", "Lewis", "Walker", "Hall", "Young"
])
# Modify first letter of first name
new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
self.first_name = new_first_letter + first_name[1:]
print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
def _generate_password(self, length=12):
"""Generate Random Password"""
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length))
def setup_email(self):
"""Setup Email"""
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}")
self.email_address = input().strip()
if '@' not in self.email_address:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}\n{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_verification_code(self):
"""Manually Get Verification Code"""
try:
print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}")
code = input().strip()
if not code.isdigit() or len(code) != 6:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}")
return None
return code
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}")
return None
def register_cursor(self):
"""Register Cursor"""
browser_tab = None
try:
print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
# Use new_signup.py directly for registration
from new_signup import main as new_signup_main
# Execute new registration process, passing translator
result, browser_tab = new_signup_main(
email=self.email_address,
password=self.password,
first_name=self.first_name,
last_name=self.last_name,
email_tab=None, # No email tab needed
controller=self, # Pass self instead of self.controller
translator=self.translator
)
if result:
# Use the returned browser instance to get account information
self.signup_tab = browser_tab # Save browser instance
success = self._get_account_info()
# Close browser after getting information
if browser_tab:
try:
browser_tab.quit()
except:
pass
return success
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
return False
finally:
# Ensure browser is closed in any case
if browser_tab:
try:
browser_tab.quit()
except:
pass
def _get_account_info(self):
"""Get Account Information and Token"""
try:
self.signup_tab.get(self.settings_url)
time.sleep(2)
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.signup_tab.ele(usage_selector)
total_usage = "未知"
if usage_ele:
total_usage = usage_ele.text.split("/")[-1].strip()
print(f"Total Usage: {total_usage}\n")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
max_attempts = 30
retry_interval = 2
attempts = 0
while attempts < max_attempts:
try:
cookies = self.signup_tab.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
token = cookie["value"].split("%3A%3A")[1]
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}")
self._save_account_info(token, total_usage)
return True
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
time.sleep(retry_interval)
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}")
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
time.sleep(retry_interval)
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}")
return False
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}")
# Reset machine ID
print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
resetter = MachineIDResetter(self.translator) # Create instance with translator
if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly
raise Exception("Failed to reset machine ID")
# Save account information to file
with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
f.write(f"\n{'='*50}\n")
f.write(f"Email: {self.email_address}\n")
f.write(f"Password: {self.password}\n")
f.write(f"Token: {token}\n")
f.write(f"Usage Limit: {total_usage}\n")
f.write(f"{'='*50}\n")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}")
return False
def start(self):
"""Start Registration Process"""
try:
if self.setup_email():
if self.register_cursor():
print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}")
return True
return False
finally:
# Close email tab
if hasattr(self, 'temp_email'):
try:
self.temp_email.close()
except:
pass
def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
"""Convenient function to update Cursor authentication information"""
auth_manager = CursorAuth(translator=self.translator)
return auth_manager.update_auth(email, access_token, refresh_token)
def main(translator=None):
"""Main function to be called from main.py"""
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
registration = CursorRegistration(translator)
registration.start()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...")
if __name__ == "__main__":
from main import translator as main_translator
main(main_translator)

149
disable_auto_update.py Normal file
View File

@@ -0,0 +1,149 @@
import os
import sys
import platform
import shutil
from colorama import Fore, Style, init
import subprocess
from config import get_config
# Initialize colorama
init()
# Define emoji constants
EMOJI = {
"PROCESS": "🔄",
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"FOLDER": "📁",
"FILE": "📄",
"STOP": "🛑",
"CHECK": "✔️"
}
class AutoUpdateDisabler:
def __init__(self, translator=None):
self.translator = translator
self.system = platform.system()
# 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"""
try:
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('update.killing_processes') if self.translator else '正在结束 Cursor 进程...'}{Style.RESET_ALL}")
if self.system == "Windows":
subprocess.run(['taskkill', '/F', '/IM', 'Cursor.exe', '/T'], capture_output=True)
else:
subprocess.run(['pkill', '-f', 'Cursor'], capture_output=True)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.processes_killed') if self.translator else 'Cursor 进程已结束'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.kill_process_failed', error=str(e)) if self.translator else f'结束进程失败: {e}'}{Style.RESET_ALL}")
return False
def _remove_updater_directory(self):
"""Delete updater directory"""
try:
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}")
print(f"{Fore.CYAN}{EMOJI['FOLDER']} {self.translator.get('update.removing_directory') if self.translator else '正在删除更新程序目录...'}{Style.RESET_ALL}")
if os.path.exists(updater_path):
if os.path.isdir(updater_path):
shutil.rmtree(updater_path)
else:
os.remove(updater_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.directory_removed') if self.translator else '更新程序目录已删除'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}")
return False
def _create_blocking_file(self):
"""Create blocking file"""
try:
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}")
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
# Create empty file
open(updater_path, 'w').close()
# Set read-only attribute
if self.system == "Windows":
os.system(f'attrib +r "{updater_path}"')
else:
os.chmod(updater_path, 0o444) # Set to read-only
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.create_block_file_failed', error=str(e)) if self.translator else f'创建阻止文件失败: {e}'}{Style.RESET_ALL}")
return False
def disable_auto_update(self):
"""Disable auto update"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('update.start_disable') if self.translator else '开始禁用自动更新...'}{Style.RESET_ALL}")
# 1. End processes
if not self._kill_cursor_processes():
return False
# 2. Delete directory
if not self._remove_updater_directory():
return False
# 3. Create blocking file
if not self._create_blocking_file():
return False
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.disable_failed', error=str(e)) if self.translator else f'禁用自动更新失败: {e}'}{Style.RESET_ALL}")
return False
def run(translator=None):
"""Convenient function for directly calling the disable function"""
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('update.title') if translator else 'Disable Cursor Auto Update'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
disabler = AutoUpdateDisabler(translator)
disabler.disable_auto_update()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('update.press_enter') if translator else 'Press Enter to Continue...'}")
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
images/paypal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
images/provi-code.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -2,20 +2,27 @@
"menu": {
"title": "Available Options",
"exit": "Exit Program",
"reset": "Reset Machine Manual",
"register": "Register Cursor",
"quit": "Quit Cursor",
"select_language": "Select Language",
"input_choice": "Enter your choice ({choices})",
"invalid_choice": "Invalid choice. Please try again",
"program_terminated": "Program terminated by user",
"error_occurred": "An error occurred: {error}",
"press_enter": "Press Enter to Exit"
"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",
"input_choice": "Please enter your choice ({choices})",
"invalid_choice": "Invalid selection. Please enter a number from {choices}",
"program_terminated": "Program was terminated by user",
"error_occurred": "An error occurred: {error}. Please try again",
"press_enter": "Press Enter to Exit",
"disable_auto_update": "Disable Cursor Auto-Update",
"lifetime_access_enabled": "LIFETIME ACCESS ENABLED",
"totally_reset": "Totally Reset Cursor"
},
"languages": {
"en": "English",
"zh_cn": "简体中文",
"zh_tw": "繁體中文"
"zh_tw": "繁體中文",
"vi": "Vietnamese"
},
"quit_cursor": {
"start": "Start Quitting Cursor",
@@ -45,14 +52,67 @@
"updating_pair": "Updating Key-Value Pair",
"sqlite_success": "SQLite Database Updated Successfully",
"sqlite_error": "SQLite Database Update Failed: {error}",
"press_enter": "Press Enter to Exit"
"press_enter": "Press Enter to Exit",
"unsupported_os": "Unsupported OS: {os}",
"linux_path_not_found": "Linux Path Not Found",
"updating_system_ids": "Updating System IDs",
"system_ids_updated": "System IDs Updated Successfully",
"system_ids_update_failed": "System IDs Update Failed: {error}",
"windows_guid_updated": "Windows GUID Updated Successfully",
"windows_permission_denied": "Windows Permission Denied",
"windows_guid_update_failed": "Windows GUID Update Failed",
"macos_uuid_updated": "macOS UUID Updated Successfully",
"plutil_command_failed": "plutil Command Failed",
"start_patching": "Starting Patching getMachineId",
"macos_uuid_update_failed": "macOS UUID Update Failed",
"current_version": "Current Cursor Version: {version}",
"patch_completed": "Patching getMachineId Completed",
"patch_failed": "Patching getMachineId Failed: {error}",
"version_check_passed": "Cursor Version Check Passed",
"file_modified": "File Modified",
"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",
"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",
"start": "Starting Registration Process",
"mailbox": "Successfully Entered Mailbox",
"start": "Starting registration process...",
"handling_turnstile": "Processing security verification...",
"retry_verification": "Retrying verification...",
"detect_turnstile": "Checking security verification...",
"verification_success": "Security verification successful",
"starting_browser": "Opening browser...",
"form_success": "Form submitted successfully",
"browser_started": "Browser opened successfully",
"waiting_for_second_verification": "Waiting for email verification...",
"waiting_for_verification_code": "Waiting for verification code...",
"password_success": "Password set successfully",
"password_error": "Could not set password: {error}. Please try again",
"waiting_for_page_load": "Loading page...",
"first_verification_passed": "Initial verification successful",
"mailbox": "Successfully accessed email inbox",
"register_start": "Start Register",
"form_submitted": "Form Submitted, Start Verification...",
"filling_form": "Fill Form",
"visiting_url": "Visiting URL",
"basic_info": "Basic Info Submitted",
"handle_turnstile": "Handle Turnstile",
"no_turnstile": "Not Detect Turnstile",
@@ -90,7 +150,24 @@
"save_account_info_failed": "Save Account Info Failed",
"get_email_address": "Get Email Address",
"update_cursor_auth_info": "Update Cursor Auth Info",
"register_process_error": "Register Process Error: {error}"
"register_process_error": "Register Process Error: {error}",
"setting_password": "Setting Password",
"manual_code_input": "Manual Code Input",
"manual_email_input": "Manual Email Input",
"password": "Password",
"first_name": "First Name",
"last_name": "Last Name",
"exit_signal": "Exit Signal",
"email_address": "Email Address",
"config_created": "Config Created",
"verification_failed": "Verification Failed",
"verification_error": "Verification Error: {error}",
"config_option_added": "Config Option Added: {option}",
"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",
@@ -107,10 +184,15 @@
"reset_machine_id": "Reset Machine ID",
"database_connection_closed": "Database Connection Closed",
"database_updated_successfully": "Database Updated Successfully",
"connected_to_database": "Connected to Database"
"connected_to_database": "Connected to Database",
"updating_pair": "Updating Key-Value Pair",
"db_not_found": "Database file not found at: {path}",
"db_permission_error": "Cannot access database file. Please check permissions",
"db_connection_error": "Failed to connect to database: {error}"
},
"control": {
"generate_email": "Generating New Email",
"blocked_domain": "Blocked Domain",
"select_domain": "Selecting Random Domain",
"copy_email": "Copying Email Address",
"enter_mailbox": "Entering Mailbox",
@@ -136,6 +218,160 @@
"get_cursor_session_token": "Get Cursor Session Token",
"get_cursor_session_token_success": "Get Cursor Session Token Success",
"get_cursor_session_token_failed": "Get Cursor Session Token Failed",
"save_token_failed": "Save Token Failed"
"save_token_failed": "Save Token Failed",
"database_updated_successfully": "Database Updated Successfully",
"database_connection_closed": "Database Connection Closed",
"no_valid_verification_code": "No Valid Verification Code"
},
"email": {
"starting_browser": "Starting Browser",
"visiting_site": "Visiting mail.tm",
"create_success": "Email Created Successfully",
"create_failed": "Failed to Create Email",
"create_error": "Email Creation Error: {error}",
"refreshing": "Refreshing Email",
"refresh_success": "Email Refreshed Successfully",
"refresh_error": "Email Refresh Error: {error}",
"refresh_button_not_found": "Refresh Button Not Found",
"verification_found": "Verification Found",
"verification_not_found": "Verification Not Found",
"verification_error": "Verification Error: {error}",
"verification_code_found": "Verification Code Found",
"verification_code_not_found": "Verification Code Not Found",
"verification_code_error": "Verification Code Error: {error}",
"address": "Email Address",
"all_domains_blocked": "All Domains Blocked, Switching Service",
"no_available_domains_after_filtering": "No Available Domains After Filtering",
"switching_service": "Switching to {service} Service",
"domains_list_error": "Failed to Get Domains List: {error}",
"failed_to_get_available_domains": "Failed to Get Available Domains",
"domains_excluded": "Domains Excluded: {domains}",
"failed_to_create_account": "Failed to Create Account",
"account_creation_error": "Account Creation Error: {error}",
"blocked_domains": "Blocked Domains: {domains}",
"blocked_domains_loaded": "Blocked Domains Loaded: {count}",
"blocked_domains_loaded_error": "Blocked Domains Loaded Error: {error}",
"blocked_domains_loaded_success": "Blocked Domains Loaded Successfully",
"blocked_domains_loaded_timeout": "Blocked Domains Loaded Timeout: {timeout}s",
"blocked_domains_loaded_timeout_error": "Blocked Domains Loaded Timeout Error: {error}",
"available_domains_loaded": "Available Domains Loaded: {count}",
"domains_filtered": "Domains Filtered: {count}",
"trying_to_create_email": "Trying to create email: {email}"
},
"update": {
"title": "Disable Cursor Auto Update",
"disable_success": "Auto Update Disabled Successfully",
"disable_failed": "Disable Auto Update Failed: {error}",
"press_enter": "Press Enter to Exit",
"start_disable": "Start Disabling Auto Update",
"killing_processes": "Killing Processes",
"processes_killed": "Processes Killed",
"removing_directory": "Removing Directory",
"directory_removed": "Directory Removed",
"creating_block_file": "Creating Block File",
"block_file_created": "Block File Created"
},
"updater": {
"checking": "Checking for updates...",
"new_version_available": "New version available! (Current: {current}, Latest: {latest})",
"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...",
"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'.",
"development_version": "Development Version {current} > {latest}"
},
"totally_reset": {
"title": "Totally Reset Cursor",
"checking_config": "Checking Config File",
"config_not_found": "Config File Not Found",
"no_permission": "Cannot Read or Write Config File, Please Check File Permissions",
"reading_config": "Reading Current Config",
"creating_backup": "Creating Config Backup",
"backup_exists": "Backup File Already Exists, Skipping Backup Step",
"generating_new_machine_id": "Generating New Machine ID",
"saving_new_config": "Saving New Config to JSON",
"success": "Cursor Reset Successfully",
"error": "Cursor Reset Failed: {error}",
"press_enter": "Press Enter to Exit",
"reset_machine_id": "Reset Machine ID",
"database_connection_closed": "Database Connection Closed",
"database_updated_successfully": "Database Updated Successfully",
"connected_to_database": "Connected to Database",
"updating_pair": "Updating Key-Value Pair",
"db_not_found": "Database file not found at: {path}",
"db_permission_error": "Cannot access database file. Please check permissions",
"db_connection_error": "Failed to connect to database: {error}",
"feature_title": "FEATURES",
"feature_1": "Complete removal of Cursor AI settings and configurations",
"feature_2": "Clears all cached data including AI history and prompts",
"feature_3": "Resets machine ID to bypass trial detection",
"feature_4": "Creates new randomized machine identifiers",
"feature_5": "Removes custom extensions and preferences",
"feature_6": "Resets trial information and activation data",
"feature_7": "Deep scan for hidden license and trial-related files",
"feature_8": "Safely preserves non-Cursor files and applications",
"feature_9": "Compatible with Windows, macOS, and Linux",
"disclaimer_title": "DISCLAIMER",
"disclaimer_1": "This tool will permanently delete all Cursor AI settings,",
"disclaimer_2": "configurations, and cached data. This action cannot be undone.",
"disclaimer_3": "Your code files will NOT be affected, and the tool is designed",
"disclaimer_4": "to only target Cursor AI editor files and trial detection mechanisms.",
"disclaimer_5": "Other applications on your system will not be affected.",
"disclaimer_6": "You will need to set up Cursor AI again after running this tool.",
"disclaimer_7": "Use at your own risk",
"confirm_title": "Are you sure you want to proceed?",
"confirm_1": "This action will delete all Cursor AI settings,",
"confirm_2": "configurations, and cached data. This action cannot be undone.",
"confirm_3": "Your code files will NOT be affected, and the tool is designed",
"confirm_4": "to only target Cursor AI editor files and trial detection mechanisms.",
"confirm_5": "Other applications on your system will not be affected.",
"confirm_6": "You will need to set up Cursor AI again after running this tool.",
"confirm_7": "Use at your own risk",
"invalid_choice": "Please enter 'Y' or 'n'",
"skipped_for_safety": "Skipped for safety (not Cursor-related): {path}",
"deleted": "Deleted: {path}",
"error_deleting": "Error deleting {path}: {error}",
"not_found": "File not found: {path}",
"resetting_machine_id": "Resetting machine identifiers to bypass trial detection...",
"created_machine_id": "Created new machine ID: {path}",
"error_creating_machine_id": "Error creating machine ID file {path}: {error}",
"error_searching": "Error searching for files in {path}: {error}",
"created_extended_trial_info": "Created new extended trial info: {path}",
"error_creating_trial_info": "Error creating trial info file {path}: {error}",
"resetting_cursor_ai_editor": "Resetting Cursor AI Editor... Please wait.",
"reset_cancelled": "Reset cancelled. Exiting without making any changes.",
"windows_machine_id_modification_skipped": "Windows machine ID modification skipped: {error}",
"linux_machine_id_modification_skipped": "Linux machine-id modification skipped: {error}",
"note_complete_machine_id_reset_may_require_running_as_administrator": "Note: Complete machine ID reset may require running as administrator",
"note_complete_system_machine_id_reset_may_require_sudo_privileges": "Note: Complete system machine-id reset may require sudo privileges",
"windows_registry_instructions": "📝 NOTE: For complete reset on Windows, you might also need to clean registry entries.",
"windows_registry_instructions_2": " Run 'regedit' and search for keys containing 'Cursor' or 'CursorAI' under HKEY_CURRENT_USER\\Software\\ and delete them.\n",
"reset_log_1": "Cursor AI has been fully reset and trial detection bypassed!",
"reset_log_2": "Please restart your system for changes to take effect.",
"reset_log_3": "You will need to reinstall Cursor AI and should now have a fresh trial period.",
"reset_log_4": "For best results, consider also:",
"reset_log_5": "Use a different email address when registering for a new trial",
"reset_log_6": "If available, use a VPN to change your IP address",
"reset_log_7": "Clear your browser cookies and cache before visiting Cursor AI's website",
"reset_log_8": "If issues persist, try installing Cursor AI in a different location",
"reset_log_9": "If you encounter any issues, go to Github Issue Tracker and create an issue at https://github.com/yeongpin/cursor-free-vip/issues",
"unexpected_error": "An unexpected error occurred: {error}",
"report_issue": "Please report this issue to Github Issue Tracker at https://github.com/yeongpin/cursor-free-vip/issues",
"keyboard_interrupt": "Process interrupted by user. Exiting...",
"return_to_main_menu": "Returning to main menu...",
"process_interrupted": "Process interrupted. Exiting...",
"press_enter_to_return_to_main_menu": "Press Enter to return to main menu...",
"removing_known": "Removing known trial/license files",
"performing_deep_scan": "Performing deep scan for additional trial/license files",
"found_additional_potential_license_trial_files": "Found {count} additional potential license/trial files",
"checking_for_electron_localstorage_files": "Checking for Electron localStorage files",
"no_additional_license_trial_files_found_in_deep_scan": "No additional license/trial files found in deep scan",
"removing_electron_localstorage_files": "Removing Electron localStorage files",
"electron_localstorage_files_removed": "Electron localStorage files removed",
"electron_localstorage_files_removal_error": "Error removing Electron localStorage files: {error}",
"removing_electron_localstorage_files_completed": "Electron localStorage files removal completed"
}
}

375
locales/vi.json Normal file
View File

@@ -0,0 +1,375 @@
{
"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",
"lifetime_access_enabled": "ĐÃ BẬT TRUY CẬP TRỌN ĐỜI",
"totally_reset": "Đặt lại hoàn toàn 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'.",
"development_version": "Phiên Bản Phát Triển {current} > {latest}"
},
"totally_reset": {
"title": "Đặt lại hoàn toàn Cursor",
"checking_config": "Đang kiểm tra tệp cấu hình",
"config_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_config": "Đ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_new_machine_id": "Đang tạo ID máy mới",
"saving_new_config": "Đang lưu cấu hình mới vào JSON",
"success": "Đặt lại Cursor thành công",
"error": "Đặt lại Cursor 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 với 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": "Kết nối cơ sở dữ liệu thất bại: {error}",
"feature_title": "TÍNH NĂNG",
"feature_1": "Xóa hoàn toàn các cài đặt và cấu hình của Cursor AI",
"feature_2": "Xóa tất cả dữ liệu bộ nhớ đệm bao gồm lịch sử và gợi ý AI",
"feature_3": "Đặt lại ID máy để vượt qua kiểm tra dùng thử",
"feature_4": "Tạo định danh máy ngẫu nhiên mới",
"feature_5": "Xóa tiện ích mở rộng và tùy chỉnh",
"feature_6": "Đặt lại thông tin dùng thử và dữ liệu kích hoạt",
"feature_7": "Quét sâu các tệp ẩn liên quan đến giấy phép và dùng thử",
"feature_8": "Bảo toàn các tệp và ứng dụng không liên quan đến Cursor",
"feature_9": "Tương thích với Windows, macOS và Linux",
"disclaimer_title": "LƯU Ý",
"disclaimer_1": "Công cụ này sẽ xóa vĩnh viễn tất cả cài đặt,",
"disclaimer_2": "cấu hình và dữ liệu bộ nhớ đệm của Cursor AI. Thao tác này không thể hoàn tác.",
"disclaimer_3": "Tệp mã nguồn của bạn sẽ KHÔNG bị ảnh hưởng, công cụ chỉ nhắm đến",
"disclaimer_4": "các tệp chỉnh sửa Cursor AI và cơ chế kiểm tra dùng thử.",
"disclaimer_5": "Các ứng dụng khác trên hệ thống của bạn sẽ không bị ảnh hưởng.",
"disclaimer_6": "Bạn sẽ cần thiết lập lại Cursor AI sau khi chạy công cụ này.",
"disclaimer_7": "Sử dụng dưới sự tự chịu trách nhiệm",
"confirm_title": "Bạn có chắc chắn muốn tiếp tục không?",
"confirm_1": "Hành động này sẽ xóa tất cả cài đặt,",
"confirm_2": "cấu hình và dữ liệu bộ nhớ đệm của Cursor AI. Hành động này không thể hoàn tác.",
"confirm_3": "Tệp mã nguồn của bạn sẽ KHÔNG bị ảnh hưởng, công cụ chỉ nhắm đến",
"confirm_4": "các tệp chỉnh sửa Cursor AI và cơ chế kiểm tra dùng thử.",
"confirm_5": "Các ứng dụng khác trên hệ thống của bạn sẽ không bị ảnh hưởng.",
"confirm_6": "Bạn sẽ cần thiết lập lại Cursor AI sau khi chạy công cụ này.",
"confirm_7": "Sử dụng dưới sự tự chịu trách nhiệm",
"invalid_choice": "Vui lòng nhập 'Y' hoặc 'n'",
"skipped_for_safety": "Bỏ qua vì an toàn (không liên quan đến Cursor): {path}",
"deleted": "Đã xóa: {path}",
"error_deleting": "Lỗi khi xóa {path}: {error}",
"not_found": "Không tìm thấy tệp: {path}",
"resetting_machine_id": "Đang đặt lại ID máy để vượt qua kiểm tra dùng thử...",
"created_machine_id": "Đã tạo ID máy mới: {path}",
"error_creating_machine_id": "Lỗi khi tạo tệp ID máy {path}: {error}",
"error_searching": "Lỗi khi tìm kiếm tệp trong {path}: {error}",
"created_extended_trial_info": "Đã tạo thông tin dùng thử mở rộng mới: {path}",
"error_creating_trial_info": "Lỗi khi tạo tệp thông tin dùng thử {path}: {error}",
"resetting_cursor_ai_editor": "Đang đặt lại Cursor AI Editor... Vui lòng chờ.",
"reset_cancelled": "Đã hủy đặt lại. Thoát mà không thực hiện thay đổi nào.",
"windows_machine_id_modification_skipped": "Bỏ qua sửa đổi ID máy Windows: {error}",
"linux_machine_id_modification_skipped": "Bỏ qua sửa đổi machine-id Linux: {error}",
"note_complete_machine_id_reset_may_require_running_as_administrator": "Lưu ý: Đặt lại ID máy hoàn toàn có thể yêu cầu chạy dưới quyền quản trị viên",
"note_complete_system_machine_id_reset_may_require_sudo_privileges": "Lưu ý: Đặt lại machine-id hệ thống hoàn toàn có thể yêu cầu quyền sudo",
"windows_registry_instructions": "📝 LƯU Ý: Để đặt lại hoàn toàn trên Windows, bạn có thể cần dọn dẹp các mục registry.",
"windows_registry_instructions_2": " Chạy 'regedit' và tìm kiếm khóa chứa 'Cursor' hoặc 'CursorAI' dưới HKEY_CURRENT_USER\\Software\\ và xóa chúng.\n",
"reset_log_1": "Cursor AI đã được đặt lại hoàn toàn và vượt qua kiểm tra dùng thử!",
"reset_log_2": "Vui lòng khởi động lại hệ thống để thay đổi có hiệu lực.",
"reset_log_3": "Bạn cần cài đặt lại Cursor AI và sẽ có kỳ dùng thử mới.",
"reset_log_4": "Để có kết quả tốt nhất, bạn có thể cân nhắc:",
"reset_log_5": "Sử dụng địa chỉ email khác khi đăng ký dùng thử mới",
"reset_log_6": "Nếu có thể, sử dụng VPN để thay đổi địa chỉ IP",
"reset_log_7": "Xóa cookie và bộ nhớ cache trình duyệt trước khi truy cập trang web Cursor AI",
"reset_log_8": "Nếu vẫn gặp sự cố, hãy thử cài Cursor AI vào vị trí khác",
"reset_log_9": "Nếu gặp bất kỳ vấn đề nào, hãy truy cập Github Issue Tracker và tạo issue tại https://github.com/yeongpin/cursor-free-vip/issues",
"unexpected_error": "Đã xảy ra lỗi không mong muốn: {error}",
"report_issue": "Vui lòng báo cáo sự cố này tại Github Issue Tracker: https://github.com/yeongpin/cursor-free-vip/issues",
"keyboard_interrupt": "Quá trình bị người dùng hủy. Đang thoát...",
"return_to_main_menu": "Trở về menu chính...",
"process_interrupted": "Quá trình bị gián đoạn. Đang thoát...",
"press_enter_to_return_to_main_menu": "Nhấn Enter để trở về menu chính...",
"removing_known": "Đang xóa các tệp thử nghiệm/giấy phép đã biết",
"performing_deep_scan": "Đang quét sâu để tìm thêm tệp thử nghiệm/giấy phép",
"found_additional_potential_license_trial_files": "Đã tìm thấy {count} tệp thử nghiệm/giấy phép tiềm năng bổ sung",
"checking_for_electron_localstorage_files": "Đang kiểm tra các tệp Electron localStorage",
"no_additional_license_trial_files_found_in_deep_scan": "Không tìm thấy thêm tệp thử nghiệm/giấy phép nào trong quá trình quét sâu",
"removing_electron_localstorage_files": "Đang xóa các tệp Electron localStorage",
"electron_localstorage_files_removed": "Đã xóa các tệp Electron localStorage",
"electron_localstorage_files_removal_error": "Lỗi khi xóa các tệp Electron localStorage: {error}",
"removing_electron_localstorage_files_completed": "Đã hoàn tất việc xóa các tệp Electron localStorage"
}
}

View File

@@ -3,19 +3,26 @@
"title": "可用选项",
"exit": "退出程序",
"reset": "重置机器标识",
"register": "注册 Cursor",
"quit": "退出 Cursor",
"select_language": "选择语言",
"input_choice": "输入选择 ({choices})",
"invalid_choice": "无效选择,请重试",
"program_terminated": "程序被用户终止",
"error_occurred": "发生错误: {error}",
"press_enter": "按回车键退出"
"register": "注册 Cursor 账号",
"register_google": "使用 Google 账号注册",
"register_github": "使用 GitHub 账号注册",
"register_manual": "使用自定义邮箱注册",
"quit": "关闭 Cursor 应用",
"select_language": "更改语言",
"input_choice": "请输入您的选择 ({choices})",
"invalid_choice": "选择无效,请输入 {choices} 范围内的数字",
"program_terminated": "程序已被用户终止",
"error_occurred": "发生错误:{error},请重试",
"press_enter": "按回车键退出",
"disable_auto_update": "禁用 Cursor 自动更新",
"lifetime_access_enabled": "永久订阅",
"totally_reset": "完全重置 Cursor"
},
"languages": {
"en": "English",
"zh_cn": "简体中文",
"zh_tw": "繁體中文"
"zh_tw": "繁體中文",
"vi": "Vietnamese"
},
"quit_cursor": {
"start": "开始退出 Cursor",
@@ -26,13 +33,86 @@
"timeout": "以下进程未能在规定时间内关闭: {pids}",
"error": "关闭 Cursor 进程时发生错误: {error}"
},
"reset": {
"title": "Cursor 机器标识重置工具",
"checking": "检查配置文件",
"not_found": "配置文件未找到",
"no_permission": "无法读取或写入配置文件,请检查文件权限",
"reading": "读取当前配置",
"creating_backup": "创建配置备份",
"backup_exists": "备份文件已存在,跳过备份步骤",
"generating": "生成新机器标识",
"saving_json": "保存新配置到JSON",
"success": "机器标识重置成功",
"new_id": "新机器标识",
"permission_error": "权限错误: {error}",
"run_as_admin": "请尝试以管理员身份运行此程序",
"process_error": "重置进程错误: {error}",
"updating_sqlite": "更新SQLite数据库",
"updating_pair": "更新键值对",
"sqlite_success": "SQLite数据库更新成功",
"sqlite_error": "SQLite数据库更新失败: {error}",
"press_enter": "按回车键退出",
"updating_system_ids": "更新系统ID",
"system_ids_updated": "系统ID更新成功",
"system_ids_update_failed": "系统ID更新失败: {error}",
"unsupported_os": "不支持的操作系统: {os}",
"linux_path_not_found": "Linux路径未找到",
"windows_guid_updated": "Windows GUID更新成功",
"windows_permission_denied": "Windows权限拒绝",
"windows_guid_update_failed": "Windows GUID更新失败",
"macos_uuid_updated": "macOS UUID更新成功",
"plutil_command_failed": "plutil命令失败",
"macos_uuid_update_failed": "macOS UUID更新失败",
"start_patching": "开始修补getMachineId",
"current_version": "当前Cursor版本: {version}",
"patch_completed": "getMachineId修补完成",
"patch_failed": "getMachineId修补失败: {error}",
"version_check_passed": "Cursor版本检查通过",
"file_modified": "文件已修改",
"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",
"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": "开始注册流程",
"mailbox": "成功进入邮箱",
"start": "正在启动注册流程...",
"browser_started": "浏览器已成功打开",
"password_success": "密码设置成功",
"password_error": "无法设置密码:{error},请重试",
"waiting_for_page_load": "页面加载中...",
"mailbox": "已成功访问邮箱",
"waiting_for_second_verification": "等待邮箱验证...",
"waiting_for_verification_code": "等待验证码...",
"first_verification_passed": "初始验证通过",
"register_start": "开始注册流程",
"form_submitted": "表单已提交,开始验证...",
"filling_form": "填写注册信息",
"visiting_url": "访问URL",
"basic_info": "基本信息提交完成",
"handling_turnstile": "正在处理安全验证...",
"retry_verification": "正在重试验证...",
"detect_turnstile": "正在检查安全验证...",
"verification_success": "安全验证通过",
"starting_browser": "正在打开浏览器...",
"form_success": "表单提交成功",
"handle_turnstile": "处理 Turnstile 验证",
"no_turnstile": "未检测到 Turnstile 验证",
"turnstile_passed": "验证通过",
@@ -68,7 +148,25 @@
"account_info_saved": "账户信息已保存",
"save_account_info_failed": "保存账户信息失败",
"get_email_address": "获取邮箱地址",
"register_process_error": "注册流程错误: {error}"
"register_process_error": "注册流程错误: {error}",
"update_cursor_auth_info": "更新Cursor认证信息",
"setting_password": "设置密码",
"manual_code_input": "手动输入验证码",
"manual_email_input": "手动输入邮箱",
"password": "密码",
"first_name": "名字",
"last_name": "姓氏",
"exit_signal": "退出信号",
"email_address": "邮箱地址",
"config_created": "配置已创建",
"verification_failed": "验证失败",
"verification_error": "验证错误: {error}",
"config_option_added": "配置项已添加: {option}",
"config_updated": "配置已更新",
"password_submitted": "密码已提交",
"total_usage": "总使用量: {usage}",
"setting_on_password": "设置密码",
"getting_code": "获取验证码将在60秒内尝试..."
},
"auth": {
"title": "Cursor 认证管理器",
@@ -81,13 +179,21 @@
"auth_update_failed": "认证信息更新失败: {error}",
"auth_file_created": "认证文件已创建",
"auth_file_create_failed": "认证文件创建失败: {error}",
"press_enter": "按回车键退出"
"press_enter": "按回车键退出",
"connected_to_database": "已连接到数据库",
"database_updated_successfully": "数据库更新成功",
"database_connection_closed": "数据库连接已关闭",
"updating_pair": "更新键值对",
"db_not_found": "未找到数据库文件:{path}",
"db_permission_error": "无法访问数据库文件,请检查权限",
"db_connection_error": "连接数据库失败:{error}"
},
"control": {
"generate_email": "生成新邮箱",
"select_domain": "选择随机域名",
"copy_email": "复制邮箱地址",
"enter_mailbox": "进入邮箱",
"blocked_domain": "被屏蔽的域名",
"refresh_mailbox": "刷新邮箱",
"check_verification": "检查验证码",
"verification_found": "找到验证码",
@@ -110,6 +216,158 @@
"get_cursor_session_token": "获取Cursor Session Token",
"get_cursor_session_token_success": "获取Cursor Session Token成功",
"get_cursor_session_token_failed": "获取Cursor Session Token失败",
"save_token_failed": "保存Token失败"
"save_token_failed": "保存Token失败",
"no_valid_verification_code": "没有有效的验证码"
},
"email": {
"starting_browser": "启动浏览器",
"visiting_site": "访问 mail.tm",
"create_success": "邮箱创建成功",
"create_failed": "邮箱创建失败",
"create_error": "邮箱创建错误: {error}",
"refreshing": "刷新邮箱",
"refresh_success": "邮箱刷新成功",
"refresh_error": "邮箱刷新错误: {error}",
"refresh_button_not_found": "未找到刷新按钮",
"verification_found": "找到验证码",
"verification_not_found": "未找到验证码",
"verification_error": "验证错误: {error}",
"verification_code_found": "找到验证码",
"verification_code_not_found": "未找到验证码",
"verification_code_error": "验证码错误: {error}",
"address": "邮箱地址",
"all_domains_blocked": "所有域名都被屏蔽了,切换服务",
"no_available_domains_after_filtering": "过滤后没有可用域名",
"switching_service": "切换到 {service} 服务",
"domains_list_error": "获取域名列表失败: {error}",
"failed_to_get_available_domains": "获取可用域名失败",
"blocked_domains_loaded": "加载了 {count} 个被屏蔽的域名",
"domains_excluded": "排除了 {domains} 个被屏蔽的域名",
"failed_to_create_account": "创建账户失败",
"account_creation_error": "账户创建错误: {error}",
"blocked_domains": "被屏蔽的域名: {domains}",
"blocked_domains_loaded_error": "加载被屏蔽的域名失败: {error}",
"blocked_domains_loaded_success": "加载被屏蔽的域名成功",
"blocked_domains_loaded_timeout": "加载被屏蔽的域名超时: {timeout}秒",
"blocked_domains_loaded_timeout_error": "加载被屏蔽的域名超时错误: {error}",
"available_domains_loaded": "获取到 {count} 个可用域名",
"domains_filtered": "过滤后剩餘 {count} 個可用域名",
"trying_to_create_email": "尝试创建邮箱: {email}"
},
"update": {
"title": "禁用 Cursor 自动更新",
"disable_success": "自动更新禁用成功",
"disable_failed": "禁用自动更新失败: {error}",
"press_enter": "按回车键退出",
"start_disable": "开始禁用自动更新",
"killing_processes": "杀死进程",
"processes_killed": "进程已杀死",
"removing_directory": "删除目录",
"directory_removed": "目录已删除",
"creating_block_file": "创建阻止文件",
"block_file_created": "阻止文件已创建"
},
"updater": {
"checking": "检查更新...",
"new_version_available": "有新版本可用! (当前版本: {current}, 最新版本: {latest})",
"updating": "正在更新到最新版本。程序将自动重启。",
"up_to_date": "您使用的是最新版本。",
"check_failed": "检查更新失败: {error}",
"continue_anyway": "继续使用当前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'.",
"development_version": "开发版本 {current} > {latest}"
},
"totally_reset": {
"title": "完全重置 Cursor",
"checking_config": "正在检查配置文件",
"config_not_found": "未找到配置文件",
"no_permission": "无法读取或写入配置文件,请检查文件权限",
"reading_config": "正在读取当前配置",
"creating_backup": "正在创建配置备份",
"backup_exists": "备份文件已存在,跳过备份步骤",
"generating_new_machine_id": "正在生成新的机器 ID",
"saving_new_config": "正在将新配置保存为 JSON",
"success": "Cursor 重置成功",
"error": "Cursor 重置失败:{error}",
"press_enter": "按回车键退出",
"reset_machine_id": "重置机器 ID",
"database_connection_closed": "数据库连接已关闭",
"database_updated_successfully": "数据库更新成功",
"connected_to_database": "已连接到数据库",
"updating_pair": "正在更新键值对",
"db_not_found": "未找到数据库文件,路径:{path}",
"db_permission_error": "无法访问数据库文件,请检查权限",
"db_connection_error": "连接数据库失败:{error}",
"feature_title": "功能介绍",
"feature_1": "完全移除 Cursor AI 设置和配置",
"feature_2": "清除所有缓存数据,包括 AI 历史记录和提示",
"feature_3": "重置机器 ID 以绕过试用检测",
"feature_4": "创建新的随机机器标识符",
"feature_5": "移除自定义扩展和偏好设置",
"feature_6": "重置试用信息和激活数据",
"feature_7": "深度扫描隐藏的授权和试用相关文件",
"feature_8": "安全保留非 Cursor 文件和应用程序",
"feature_9": "兼容 Windows、macOS 和 Linux",
"disclaimer_title": "免责声明",
"disclaimer_1": "该工具将永久删除所有 Cursor AI 设置、",
"disclaimer_2": "配置和缓存数据。此操作无法撤销。",
"disclaimer_3": "您的代码文件 **不会** 受到影响,工具设计为",
"disclaimer_4": "仅针对 Cursor AI 编辑器文件和试用检测机制。",
"disclaimer_5": "系统中的其他应用程序不会受到影响。",
"disclaimer_6": "运行此工具后,您需要重新设置 Cursor AI。",
"disclaimer_7": "请自行承担风险",
"confirm_title": "您确定要继续吗?",
"confirm_1": "该操作将删除所有 Cursor AI 设置、",
"confirm_2": "配置和缓存数据。此操作无法撤销。",
"confirm_3": "您的代码文件 **不会** 受到影响,工具设计为",
"confirm_4": "仅针对 Cursor AI 编辑器文件和试用检测机制。",
"confirm_5": "系统中的其他应用程序不会受到影响。",
"confirm_6": "运行此工具后,您需要重新设置 Cursor AI。",
"confirm_7": "请自行承担风险",
"invalid_choice": "请输入 'Y' 或 'n'",
"skipped_for_safety": "出于安全原因跳过(非 Cursor 相关):{path}",
"deleted": "已删除:{path}",
"error_deleting": "删除 {path} 时出错:{error}",
"not_found": "未找到文件:{path}",
"resetting_machine_id": "正在重置机器 ID 以绕过试用检测...",
"created_machine_id": "已创建新的机器 ID{path}",
"error_creating_machine_id": "创建机器 ID 文件 {path} 时出错:{error}",
"error_searching": "在 {path} 搜索文件时出错:{error}",
"created_extended_trial_info": "已创建新的扩展试用信息:{path}",
"error_creating_trial_info": "创建试用信息文件 {path} 时出错:{error}",
"resetting_cursor_ai_editor": "正在重置 Cursor AI 编辑器... 请稍候。",
"reset_cancelled": "重置已取消,未进行任何更改。",
"windows_machine_id_modification_skipped": "跳过 Windows 机器 ID 修改:{error}",
"linux_machine_id_modification_skipped": "跳过 Linux machine-id 修改:{error}",
"note_complete_machine_id_reset_may_require_running_as_administrator": "注意:完整的机器 ID 重置可能需要以管理员身份运行",
"note_complete_system_machine_id_reset_may_require_sudo_privileges": "注意:完整的系统 machine-id 重置可能需要 sudo 权限",
"windows_registry_instructions": "📝 注意:在 Windows 上进行完整重置,您可能还需要清理注册表项。",
"windows_registry_instructions_2": " 运行 'regedit' 并搜索 HKEY_CURRENT_USER\\Software\\ 下包含 'Cursor' 或 'CursorAI' 的键并删除它们。\n",
"reset_log_1": "Cursor AI 已完全重置并绕过试用检测!",
"reset_log_2": "请重启系统以使更改生效。",
"reset_log_3": "您需要重新安装 Cursor AI现在应该有一个新的试用期。",
"reset_log_4": "为了获得最佳效果,建议还可以:",
"reset_log_5": "注册新试用时使用不同的邮箱地址",
"reset_log_6": "如果可能,使用 VPN 更改 IP 地址",
"reset_log_7": "访问 Cursor AI 网站前清除浏览器的 Cookie 和缓存",
"reset_log_8": "如果仍有问题,尝试将 Cursor AI 安装到不同位置",
"reset_log_9": "如遇到任何问题,请到 Github Issue Tracker 提交问题https://github.com/yeongpin/cursor-free-vip/issues",
"unexpected_error": "发生意外错误:{error}",
"report_issue": "请在 Github Issue Tracker 报告此问题https://github.com/yeongpin/cursor-free-vip/issues",
"keyboard_interrupt": "用户中断进程,正在退出...",
"return_to_main_menu": "返回主菜单...",
"process_interrupted": "进程已中断,正在退出...",
"press_enter_to_return_to_main_menu": "按回车键返回主菜单...",
"removing_known": "正在移除已知的试用/授权文件",
"performing_deep_scan": "正在进行深度扫描以查找其他试用/授权文件",
"found_additional_potential_license_trial_files": "发现 {count} 个其他潜在的试用/授权文件",
"checking_for_electron_localstorage_files": "正在检查 Electron localStorage 文件",
"no_additional_license_trial_files_found_in_deep_scan": "深度扫描中未发现其他试用/授权文件",
"removing_electron_localstorage_files": "正在移除 Electron localStorage 文件",
"electron_localstorage_files_removed": "Electron localStorage 文件已移除",
"electron_localstorage_files_removal_error": "移除 Electron localStorage 文件时出错:{error}",
"removing_electron_localstorage_files_completed": "Electron localStorage 文件移除完成"
}
}

View File

@@ -1,21 +1,26 @@
{
"menu": {
"title": "可用選項",
"exit": "退出程",
"reset": "重置機器識",
"register": "註冊 Cursor",
"quit": "退出 Cursor",
"select_language": "選擇語言",
"input_choice": "輸入選擇 ({choices})",
"invalid_choice": "無效選擇,請重試",
"program_terminated": "程序被用戶終止",
"error_occurred": "發生錯誤: {error}",
"press_enter": "按回車鍵退出"
"exit": "退出程",
"reset": "重置機器識別碼",
"register": "註冊 Cursor 帳號",
"register_manual": "使用自訂郵箱註冊",
"quit": "關閉 Cursor 應用程式",
"select_language": "變更語言",
"input_choice": "請輸入您的選擇 ({choices})",
"invalid_choice": "選擇無效,請輸入 {choices} 範圍內的數字",
"program_terminated": "程式已被使用者終止",
"error_occurred": "發生錯誤:{error},請重試",
"press_enter": "按返回鍵退出",
"disable_auto_update": "禁用 Cursor 自動更新",
"lifetime_access_enabled": "終身訪問已啟用",
"totally_reset": "完全重置 Cursor"
},
"languages": {
"en": "English",
"zh_cn": "简体中文",
"zh_tw": "繁體中文"
"zh_tw": "繁體中文",
"vi": "Vietnamese"
},
"quit_cursor": {
"start": "開始退出 Cursor",
@@ -26,36 +31,91 @@
"timeout": "以下進程未能在規定時間內關閉: {pids}",
"error": "關閉 Cursor 進程時發生錯誤: {error}"
},
"reset": {
"title": "Cursor 機器標識重置工具",
"checking": "檢查配置文件",
"not_found": "配置文件未找到",
"no_permission": "無法讀取或寫入配置文件,請檢查文件權限",
"reading": "讀取當前配置",
"creating_backup": "創建配置備份",
"backup_exists": "備份文件已存在,跳過備份步驟",
"generating": "生成新機器標識",
"saving_json": "保存新配置到JSON",
"success": "機器標識重置成功",
"new_id": "新機器標識",
"permission_error": "權限錯誤: {error}",
"run_as_admin": "請嘗試以管理員身份運行此程序",
"process_error": "重置進程錯誤: {error}",
"updating_sqlite": "更新SQLite數據庫",
"updating_pair": "更新鍵值對",
"sqlite_success": "SQLite數據庫更新成功",
"sqlite_error": "SQLite數據庫更新失敗: {error}",
"press_enter": "按回車鍵退出",
"updating_system_ids": "更新系統ID",
"system_ids_updated": "系統ID更新成功",
"system_ids_update_failed": "系統ID更新失敗: {error}",
"unsupported_os": "不支持的操作系統: {os}",
"linux_path_not_found": "Linux路徑未找到",
"windows_guid_updated": "Windows GUID更新成功",
"windows_permission_denied": "Windows權限拒絕",
"windows_guid_update_failed": "Windows GUID更新失敗",
"macos_uuid_updated": "macOS UUID更新成功",
"plutil_command_failed": "plutil命令失敗",
"macos_uuid_update_failed": "macOS UUID更新失敗",
"start_patching": "開始修補getMachineId",
"current_version": "當前Cursor版本: {version}",
"patch_completed": "getMachineId修補完成",
"patch_failed": "getMachineId修補失敗: {error}",
"version_check_passed": "Cursor版本檢查通過",
"file_modified": "文件已修改",
"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",
"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": "開始註冊流程",
"mailbox": "成功進入郵箱",
"start": "正在啟動註冊流程...",
"handling_turnstile": "正在處理安全驗證...",
"retry_verification": "正在重試驗證...",
"detect_turnstile": "正在檢查安全驗證...",
"verification_success": "安全驗證通過",
"starting_browser": "正在開啟瀏覽器...",
"form_success": "表單提交成功",
"browser_started": "瀏覽器已成功開啟",
"waiting_for_second_verification": "等待郵箱驗證...",
"waiting_for_verification_code": "等待驗證碼...",
"password_success": "密碼設定成功",
"password_error": "無法設定密碼:{error},請重試",
"waiting_for_page_load": "頁面載入中...",
"first_verification_passed": "初始驗證通過",
"mailbox": "已成功存取郵箱",
"visiting_url": "訪問URL",
"register_start": "開始註冊流程",
"form_submitted": "表單已提交,開始驗證...",
"filling_form": "填寫註冊信息",
"basic_info": "基本信息提交完成",
"handle_turnstile": "處理 Turnstile 驗證",
"no_turnstile": "未檢測到 Turnstile 驗證",
"turnstile_passed": "驗證通過",
"verification_start": "開始獲取驗證碼",
"verification_timeout": "獲取驗證碼超時",
"verification_not_found": "未找到驗證碼",
"try_get_code": "第 {attempt} 次嘗試獲取驗證碼 | 剩餘時間: {time}秒",
"get_account": "獲取賬戶信息",
"get_token": "獲取 Cursor Session Token",
"token_success": "Token 獲取成功",
"token_attempt": "第 {attempt} 次嘗試未獲取到 Token{time}秒後重試",
"token_max_attempts": "已達到最大嘗試次數({max}),獲取 Token 失敗",
"token_failed": "獲取 Token 失敗: {error}",
"account_error": "獲取賬戶信息失敗: {error}",
"press_enter": "按回車鍵退出",
"browser_start": "正在啟動瀏覽器",
"open_mailbox": "正在打開郵箱頁面",
"email_error": "獲取郵箱地址失敗",
"setup_error": "郵箱設置出錯: {error}",
"start_getting_verification_code": "開始獲取驗證碼將在60秒內嘗試...",
"get_verification_code_timeout": "獲取驗證碼超時",
"get_verification_code_success": "成功獲取驗證碼",
"try_get_verification_code": "第 {attempt} 次嘗試未獲取到驗證碼,剩餘時間: {remaining_time}秒",
"verification_code_filled": "驗證碼填寫完成",
"login_success_and_jump_to_settings_page": "成功登錄並跳轉到設置頁面",
"detect_login_page": "檢測到登錄頁面,開始登錄...",
@@ -68,7 +128,25 @@
"account_info_saved": "賬戶信息已保存",
"save_account_info_failed": "保存賬戶信息失敗",
"get_email_address": "獲取郵箱地址",
"register_process_error": "註冊流程錯誤: {error}"
"register_process_error": "註冊流程錯誤: {error}",
"update_cursor_auth_info": "更新Cursor認證信息",
"setting_password": "設置密碼",
"manual_code_input": "手動輸入驗證碼",
"manual_email_input": "手動輸入郵箱地址",
"password": "密碼",
"first_name": "名字",
"last_name": "姓氏",
"exit_signal": "退出信號",
"email_address": "郵箱地址",
"config_created": "配置已創建",
"verification_failed": "驗證失敗",
"verification_error": "驗證錯誤: {error}",
"config_option_added": "配置項已添加: {option}",
"config_updated": "配置已更新",
"password_submitted": "密碼已提交",
"total_usage": "總使用量: {usage}",
"setting_on_password": "設置密碼",
"getting_code": "正在獲取驗證碼將在60秒內嘗試..."
},
"auth": {
"title": "Cursor 認證管理器",
@@ -81,7 +159,14 @@
"auth_update_failed": "認證信息更新失敗: {error}",
"auth_file_created": "認證文件已創建",
"auth_file_create_failed": "認證文件創建失敗: {error}",
"press_enter": "按回車鍵退出"
"press_enter": "按回車鍵退出",
"connected_to_database": "已連接到數據庫",
"database_updated_successfully": "數據庫更新成功",
"database_connection_closed": "數據庫連接已關閉",
"updating_pair": "更新鍵值對",
"db_not_found": "未找到數據庫文件:{path}",
"db_permission_error": "無法訪問數據庫文件,請檢查權限",
"db_connection_error": "連接數據庫失敗:{error}"
},
"control": {
"generate_email": "生成新郵箱",
@@ -110,6 +195,159 @@
"get_cursor_session_token": "獲取Cursor Session Token",
"get_cursor_session_token_success": "獲取Cursor Session Token成功",
"get_cursor_session_token_failed": "獲取Cursor Session Token失敗",
"save_token_failed": "保存Token失敗"
}
"save_token_failed": "保存Token失敗",
"blocked_domain": "被屏蔽的域名",
"no_valid_verification_code": "沒有有效的驗證碼"
},
"email": {
"starting_browser": "啟動瀏覽器",
"visiting_site": "訪問 mail.tm",
"create_success": "郵箱創建成功",
"create_failed": "郵箱創建失敗",
"create_error": "郵箱創建錯誤: {error}",
"refreshing": "刷新郵箱",
"refresh_success": "郵箱刷新成功",
"refresh_error": "郵箱刷新錯誤: {error}",
"refresh_button_not_found": "未找到刷新按鈕",
"verification_found": "找到驗證碼",
"verification_not_found": "未找到驗證碼",
"verification_error": "驗證錯誤: {error}",
"verification_code_found": "找到驗證碼",
"verification_code_not_found": "未找到驗證碼",
"verification_code_error": "驗證碼錯誤: {error}",
"address": "郵箱地址",
"all_domains_blocked": "所有域名都被屏蔽了,切換服務",
"no_available_domains_after_filtering": "過濾後沒有可用域名",
"switching_service": "切換到 {service} 服務",
"domains_list_error": "獲取域名列表失敗: {error}",
"failed_to_get_available_domains": "獲取可用域名失敗",
"domains_excluded": "排除的域名: {domains}",
"failed_to_create_account": "創建帳戶失敗",
"account_creation_error": "帳戶創建錯誤: {error}",
"blocked_domains": "被屏蔽的域名: {domains}",
"blocked_domains_loaded": "加載被屏蔽的域名: {domains}",
"blocked_domains_loaded_error": "加載被屏蔽的域名失敗: {error}",
"blocked_domains_loaded_success": "加載被屏蔽的域名成功",
"blocked_domains_loaded_timeout": "加載被屏蔽的域名超時: {timeout}秒",
"blocked_domains_loaded_timeout_error": "加載被屏蔽的域名超時錯誤: {error}",
"available_domains_loaded": "獲取到 {count} 個可用域名",
"domains_filtered": "過濾後剩餘 {count} 個可用域名",
"trying_to_create_email": "嘗試創建郵箱: {email}"
},
"update": {
"title": "禁用 Cursor 自动更新",
"disable_success": "自動更新禁用成功",
"disable_failed": "禁用自動更新失敗: {error}",
"press_enter": "按回車鍵退出",
"start_disable": "開始禁用自動更新",
"killing_processes": "殺死進程",
"processes_killed": "進程已殺死",
"removing_directory": "刪除目錄",
"directory_removed": "目錄已刪除",
"creating_block_file": "創建阻止文件",
"block_file_created": "阻止文件已創建"
},
"updater": {
"checking": "檢查更新...",
"new_version_available": "有新版本可用! (當前版本: {current}, 最新版本: {latest})",
"updating": "正在更新到最新版本。程序將自動重啟。",
"up_to_date": "您使用的是最新版本。",
"check_failed": "檢查更新失敗: {error}",
"continue_anyway": "繼續使用當前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.",
"development_version": "開發版本 {current} > {latest}"
},
"totally_reset": {
"title": "完全重置 Cursor",
"checking_config": "正在檢查配置檔案",
"config_not_found": "找不到配置檔案",
"no_permission": "無法讀取或寫入配置檔案,請檢查檔案權限",
"reading_config": "正在讀取當前配置",
"creating_backup": "正在建立配置備份",
"backup_exists": "備份檔案已存在,跳過備份步驟",
"generating_new_machine_id": "正在生成新的機器 ID",
"saving_new_config": "正在將新配置保存到 JSON",
"success": "Cursor 重置成功",
"error": "Cursor 重置失敗:{error}",
"press_enter": "按 Enter 鍵退出",
"reset_machine_id": "重置機器 ID",
"database_connection_closed": "資料庫連線已關閉",
"database_updated_successfully": "資料庫更新成功",
"connected_to_database": "已連接到資料庫",
"updating_pair": "正在更新鍵值對",
"db_not_found": "未找到資料庫檔案,路徑:{path}",
"db_permission_error": "無法訪問資料庫檔案,請檢查權限",
"db_connection_error": "連接資料庫失敗:{error}",
"feature_title": "功能特色",
"feature_1": "完全移除 Cursor AI 設定與配置",
"feature_2": "清除所有快取資料,包括 AI 歷史與提示",
"feature_3": "重置機器 ID 以繞過試用偵測",
"feature_4": "建立新的隨機機器標識",
"feature_5": "移除自訂擴充功能與偏好設定",
"feature_6": "重置試用資訊與啟動資料",
"feature_7": "深度掃描隱藏的授權與試用相關檔案",
"feature_8": "安全保留非 Cursor 相關檔案與應用程式",
"feature_9": "相容於 Windows、macOS 與 Linux",
"disclaimer_title": "免責聲明",
"disclaimer_1": "此工具將永久刪除所有 Cursor AI 設定、",
"disclaimer_2": "配置與快取資料。此操作無法還原。",
"disclaimer_3": "您的程式碼檔案將 **不會** 受到影響,且此工具僅針對",
"disclaimer_4": "Cursor AI 編輯器檔案與試用偵測機制。",
"disclaimer_5": "系統中的其他應用程式不會受到影響。",
"disclaimer_6": "執行此工具後,您需要重新設定 Cursor AI。",
"disclaimer_7": "請自行承擔風險",
"confirm_title": "您確定要繼續嗎?",
"confirm_1": "此操作將刪除所有 Cursor AI 設定、",
"confirm_2": "配置與快取資料。此操作無法還原。",
"confirm_3": "您的程式碼檔案將 **不會** 受到影響,且此工具僅針對",
"confirm_4": "Cursor AI 編輯器檔案與試用偵測機制。",
"confirm_5": "系統中的其他應用程式不會受到影響。",
"confirm_6": "執行此工具後,您需要重新設定 Cursor AI。",
"confirm_7": "請自行承擔風險",
"invalid_choice": "請輸入 'Y' 或 'n'",
"skipped_for_safety": "出於安全考量跳過(與 Cursor 無關):{path}",
"deleted": "已刪除:{path}",
"error_deleting": "刪除 {path} 時出錯:{error}",
"not_found": "未找到檔案:{path}",
"resetting_machine_id": "正在重置機器 ID 以繞過試用偵測...",
"created_machine_id": "已建立新的機器 ID{path}",
"error_creating_machine_id": "建立機器 ID 檔案 {path} 時出錯:{error}",
"error_searching": "在 {path} 搜尋檔案時出錯:{error}",
"created_extended_trial_info": "已建立新的擴展試用資訊:{path}",
"error_creating_trial_info": "建立試用資訊檔案 {path} 時出錯:{error}",
"resetting_cursor_ai_editor": "正在重置 Cursor AI 編輯器... 請稍候。",
"reset_cancelled": "重置已取消,未進行任何更改。",
"windows_machine_id_modification_skipped": "跳過 Windows 機器 ID 修改:{error}",
"linux_machine_id_modification_skipped": "跳過 Linux machine-id 修改:{error}",
"note_complete_machine_id_reset_may_require_running_as_administrator": "注意:完整的機器 ID 重置可能需要以管理員身份執行",
"note_complete_system_machine_id_reset_may_require_sudo_privileges": "注意:完整的系統 machine-id 重置可能需要 sudo 權限",
"windows_registry_instructions": "📝 注意:在 Windows 上進行完整重置,您可能還需要清理登錄檔項目。",
"windows_registry_instructions_2": " 執行 'regedit' 並搜尋 HKEY_CURRENT_USER\\Software\\ 下包含 'Cursor' 或 'CursorAI' 的鍵並刪除它們。\n",
"reset_log_1": "Cursor AI 已完全重置並繞過試用偵測!",
"reset_log_2": "請重新啟動系統以使更改生效。",
"reset_log_3": "您需要重新安裝 Cursor AI現在應該有全新的試用期。",
"reset_log_4": "為獲得最佳效果,建議還可以:",
"reset_log_5": "註冊新試用時使用不同的電子郵件地址",
"reset_log_6": "若有可能,使用 VPN 變更 IP 地址",
"reset_log_7": "在造訪 Cursor AI 網站前清除瀏覽器的 Cookie 與快取",
"reset_log_8": "若仍有問題,嘗試將 Cursor AI 安裝到不同位置",
"reset_log_9": "若遇到任何問題,請到 Github Issue Tracker 提交問題https://github.com/yeongpin/cursor-free-vip/issues",
"unexpected_error": "發生非預期錯誤:{error}",
"report_issue": "請在 Github Issue Tracker 回報此問題https://github.com/yeongpin/cursor-free-vip/issues",
"keyboard_interrupt": "使用者中斷流程,正在退出...",
"return_to_main_menu": "返回主選單...",
"process_interrupted": "流程已中斷,正在退出...",
"press_enter_to_return_to_main_menu": "按 Enter 鍵返回主選單...",
"removing_known": "正在移除已知的試用/授權檔案",
"performing_deep_scan": "正在進行深度掃描以查找其他試用/授權檔案",
"found_additional_potential_license_trial_files": "找到 {count} 個其他潛在試用/授權檔案",
"checking_for_electron_localstorage_files": "正在檢查 Electron localStorage 檔案",
"no_additional_license_trial_files_found_in_deep_scan": "深度掃描中未發現其他試用/授權檔案",
"removing_electron_localstorage_files": "正在移除 Electron localStorage 檔案",
"electron_localstorage_files_removed": "已移除 Electron localStorage 檔案",
"electron_localstorage_files_removal_error": "移除 Electron localStorage 檔案時出錯:{error}",
"removing_electron_localstorage_files_completed": "Electron localStorage 檔案移除完成"
}
}

17
logo.py
View File

@@ -2,11 +2,17 @@ from colorama import Fore, Style, init
from dotenv import load_dotenv
import os
# 加載環境變量獲取版本號
load_dotenv()
# Get the current script directory
current_dir = os.path.dirname(os.path.abspath(__file__))
# Build the full path to the .env file
env_path = os.path.join(current_dir, '.env')
# Load environment variables, specifying the .env file path
load_dotenv(env_path)
# Get the version number, using the default value if not found
version = os.getenv('VERSION', '1.0.0')
# 初始化 colorama
# Initialize colorama
init()
CURSOR_LOGO = f"""
@@ -21,14 +27,15 @@ CURSOR_LOGO = f"""
Pro Version Activator v{version}
{Fore.GREEN}
Author: Pin Studios | yeongpin
Github: https://github.com/yeongpin/cursor-free-vip
{Fore.RED}
Press 4 to change language | 按下 4 键切换语言
Press 5 to change language | 按下 5 键切换语言
{Style.RESET_ALL}
"""
def print_logo():
print(CURSOR_LOGO)
if __name__ == "__main__":
print_logo()

402
main.py
View File

@@ -3,13 +3,24 @@
import os
import sys
import json
from logo import print_logo
from logo import print_logo, version
from colorama import Fore, Style, init
import locale
import platform
import requests
import subprocess
from config import get_config
# 初始化colorama
# Only import windll on Windows systems
if platform.system() == 'Windows':
import ctypes
# 只在 Windows 上导入 windll
from ctypes import windll
# Initialize colorama
init()
# 定义emoji和颜色常量
# Define emoji and color constants
EMOJI = {
"FILE": "📄",
"BACKUP": "💾",
@@ -19,84 +30,364 @@ EMOJI = {
"RESET": "🔄",
"MENU": "📋",
"ARROW": "",
"LANG": "🌐"
"LANG": "🌐",
"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.current_language = 'zh_tw' # 默认语言
self.translations = {}
self.current_language = self.detect_system_language() # Use correct method name
self.fallback_language = 'en' # Fallback language if translation is missing
self.load_translations()
def load_translations(self):
"""加载所有可用的翻译"""
locales_dir = os.path.join(os.path.dirname(__file__), 'locales')
if hasattr(sys, '_MEIPASS'):
locales_dir = os.path.join(sys._MEIPASS, 'locales')
def detect_system_language(self):
"""Detect system language and return corresponding language code"""
try:
system = platform.system()
for file in os.listdir(locales_dir):
if file.endswith('.json'):
lang_code = file[:-5] # 移除 .json
with open(os.path.join(locales_dir, file), 'r', encoding='utf-8') as f:
self.translations[lang_code] = json.load(f)
if system == 'Windows':
return self._detect_windows_language()
else:
return self._detect_unix_language()
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} Failed to detect system language: {e}{Style.RESET_ALL}")
return 'en'
def _detect_windows_language(self):
"""Detect language on Windows systems"""
try:
# 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)
layout_id = user32.GetKeyboardLayout(threadid) & 0xFFFF
# Map language ID to our language codes
language_map = {
0x0409: 'en', # English
0x0404: 'zh_tw', # Traditional Chinese
0x0804: 'zh_cn', # Simplified Chinese
0x0422: 'vi', # Vietnamese
}
return language_map.get(layout_id, 'en')
except:
return self._detect_unix_language()
def _detect_unix_language(self):
"""Detect language on Unix-like systems (Linux, macOS)"""
try:
# Get the system locale
system_locale = locale.getdefaultlocale()[0]
if not system_locale:
return 'en'
system_locale = system_locale.lower()
# Map locale to our language codes
if system_locale.startswith('zh_tw') or system_locale.startswith('zh_hk'):
return 'zh_tw'
elif system_locale.startswith('zh_cn'):
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'
def load_translations(self):
"""Load all available translations"""
try:
locales_dir = os.path.join(os.path.dirname(__file__), 'locales')
if hasattr(sys, '_MEIPASS'):
locales_dir = os.path.join(sys._MEIPASS, 'locales')
if not os.path.exists(locales_dir):
print(f"{Fore.RED}{EMOJI['ERROR']} Locales directory not found{Style.RESET_ALL}")
return
for file in os.listdir(locales_dir):
if file.endswith('.json'):
lang_code = file[:-5] # Remove .json
try:
with open(os.path.join(locales_dir, file), 'r', encoding='utf-8') as f:
self.translations[lang_code] = json.load(f)
except (json.JSONDecodeError, UnicodeDecodeError) as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Error loading {file}: {e}{Style.RESET_ALL}")
continue
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to load translations: {e}{Style.RESET_ALL}")
def get(self, key, **kwargs):
"""获取翻译文本"""
keys = key.split('.')
value = self.translations.get(self.current_language, {})
for k in keys:
value = value.get(k, key)
return value.format(**kwargs) if kwargs else value
"""Get translated text with fallback support"""
try:
# Try current language
result = self._get_translation(self.current_language, key)
if result == key and self.current_language != self.fallback_language:
# Try fallback language if translation not found
result = self._get_translation(self.fallback_language, key)
return result.format(**kwargs) if kwargs else result
except Exception:
return key
def _get_translation(self, lang_code, key):
"""Get translation for a specific language"""
try:
keys = key.split('.')
value = self.translations.get(lang_code, {})
for k in keys:
if isinstance(value, dict):
value = value.get(k, key)
else:
return key
return value
except Exception:
return key
def set_language(self, lang_code):
"""设置当前语言"""
"""Set current language with validation"""
if lang_code in self.translations:
self.current_language = lang_code
return True
return False
# 创建翻译器实例
def get_available_languages(self):
"""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['ERROR']} {translator.get('menu.quit')}")
print(f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
print(f"{Fore.YELLOW} ┗━━ 🔥 {translator.get('menu.lifetime_access_enabled')} 🔥{Style.RESET_ALL}")
print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}")
print(f"{Fore.YELLOW} ┗━━ 🚀 {translator.get('menu.lifetime_access_enabled')} 🚀{Style.RESET_ALL}")
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
print(f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
def select_language():
"""语言选择菜单"""
"""Language selection menu"""
print(f"\n{Fore.CYAN}{EMOJI['LANG']} {translator.get('menu.select_language')}:{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
languages = translator.get('languages')
for i, (code, name) in enumerate(languages.items()):
print(f"{Fore.GREEN}{i}{Style.RESET_ALL}. {name}")
languages = translator.get_available_languages()
for i, lang in enumerate(languages):
lang_name = translator.get(f"languages.{lang}")
print(f"{Fore.GREEN}{i}{Style.RESET_ALL}. {lang_name}")
try:
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-' + str(len(languages)-1))}: {Style.RESET_ALL}")
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{len(languages)-1}')}: {Style.RESET_ALL}")
if choice.isdigit() and 0 <= int(choice) < len(languages):
lang_code = list(languages.keys())[int(choice)]
translator.set_language(lang_code)
translator.set_language(languages[int(choice)])
return True
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
return False
except (ValueError, IndexError):
pass
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
return False
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
return False
def check_latest_version():
"""Check if current version matches the latest release version"""
try:
print(f"\n{Fore.CYAN}{EMOJI['UPDATE']} {translator.get('updater.checking')}{Style.RESET_ALL}")
# Get latest version from GitHub API with timeout and proper headers
headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'CursorFreeVIP-Updater'
}
response = requests.get(
"https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest",
headers=headers,
timeout=10
)
# Check if response is successful
if response.status_code != 200:
raise Exception(f"GitHub API returned status code {response.status_code}")
response_data = response.json()
if "tag_name" not in response_data:
raise Exception("No version tag found in GitHub response")
latest_version = response_data["tag_name"].lstrip('v')
# Validate version format
if not latest_version:
raise Exception("Invalid version format received")
# Parse versions for proper comparison
def parse_version(version_str):
"""Parse version string into tuple for proper comparison"""
try:
return tuple(map(int, version_str.split('.')))
except ValueError:
# Fallback to string comparison if parsing fails
return version_str
current_version_tuple = parse_version(version)
latest_version_tuple = parse_version(latest_version)
# Compare versions properly
is_newer_version_available = False
if isinstance(current_version_tuple, tuple) and isinstance(latest_version_tuple, tuple):
is_newer_version_available = current_version_tuple < latest_version_tuple
else:
# Fallback to string comparison
is_newer_version_available = version != latest_version
if is_newer_version_available:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
# Ask user if they want to update
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':
update_command = 'irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex'
subprocess.run(['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', update_command], check=True)
else:
# For Linux/Mac, download and execute the install script
install_script_url = 'https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh'
# First verify the script exists
script_response = requests.get(install_script_url, timeout=5)
if script_response.status_code != 200:
raise Exception("Installation script not found")
# Save and execute the script
with open('install.sh', 'wb') as f:
f.write(script_response.content)
os.chmod('install.sh', 0o755) # Make executable
subprocess.run(['./install.sh'], check=True)
# Clean up
if os.path.exists('install.sh'):
os.remove('install.sh')
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.updating')}{Style.RESET_ALL}")
sys.exit(0)
except Exception as update_error:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.update_failed', error=str(update_error))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.manual_update_required')}{Style.RESET_ALL}")
return
else:
# If current version is newer or equal to latest version
if current_version_tuple > latest_version_tuple:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.development_version', current=version, latest=latest_version)}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('updater.up_to_date')}{Style.RESET_ALL}")
except requests.exceptions.RequestException as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.network_error', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}")
return
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('updater.check_failed', error=str(e))}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}")
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-4')}: {Style.RESET_ALL}")
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-9')}: {Style.RESET_ALL}")
if choice == "0":
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
@@ -105,19 +396,39 @@ def main():
elif choice == "1":
import reset_machine_manual
reset_machine_manual.run(translator)
break
print_menu()
elif choice == "2":
import cursor_register
cursor_register.main(translator)
break
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 == "6":
import quit_cursor
quit_cursor.quit_cursor(translator)
break
elif choice == "4":
print_menu()
elif choice == "7":
if select_language():
print_menu()
continue
elif choice == "8":
import disable_auto_update
disable_auto_update.run(translator)
print_menu()
elif choice == "9":
import totally_reset_cursor
totally_reset_cursor.run(translator)
print_menu()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
print_menu()
@@ -128,10 +439,7 @@ def main():
return
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.error_occurred', error=str(e))}{Style.RESET_ALL}")
break
print(f"\n{Fore.CYAN}{'' * 50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('menu.press_enter')}...{Style.RESET_ALL}")
print_menu()
if __name__ == "__main__":
main()
main()

646
new_signup.py Normal file
View File

@@ -0,0 +1,646 @@
from DrissionPage import ChromiumOptions, ChromiumPage
import time
import os
import signal
import random
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):
"""Clean all Chrome related processes"""
print("\nCleaning Chrome processes...")
try:
if os.name == 'nt':
os.system('taskkill /F /IM chrome.exe /T 2>nul')
os.system('taskkill /F /IM chromedriver.exe /T 2>nul')
else:
os.system('pkill -f chrome')
os.system('pkill -f chromedriver')
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.cleanup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"清理进程时出错: {e}")
def signal_handler(signum, frame):
"""Handle Ctrl+C signal"""
global _translator
if _translator:
print(f"{Fore.CYAN}{_translator.get('register.exit_signal')}{Style.RESET_ALL}")
else:
print("\n接收到退出信号,正在关闭...")
cleanup_chrome_processes(_translator)
os._exit(0)
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}")
# First visit blank page
page.get('about:blank')
time.sleep(get_random_wait_time(config, 'page_load_wait'))
# Visit target page
page.get(url)
time.sleep(get_random_wait_time(config, 'page_load_wait'))
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'))
if translator:
print(f"{Fore.GREEN}{translator.get('register.form_success')}{Style.RESET_ALL}")
else:
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"Error filling form: {e}")
return False
def get_default_chrome_path():
"""Get default Chrome path"""
if sys.platform == "win32":
paths = [
os.path.join(os.environ.get('PROGRAMFILES', ''), 'Google/Chrome/Application/chrome.exe'),
os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'Google/Chrome/Application/chrome.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google/Chrome/Application/chrome.exe')
]
elif sys.platform == "darwin":
paths = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
]
else: # Linux
paths = [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable"
]
for path in paths:
if os.path.exists(path):
return path
return ""
def get_user_documents_path():
"""Get user Documents folder path"""
if sys.platform == "win32":
return os.path.join(os.path.expanduser("~"), "Documents")
elif sys.platform == "darwin":
return os.path.join(os.path.expanduser("~"), "Documents")
else: # Linux
# Get actual user's home directory
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
return os.path.join("/home", sudo_user, "Documents")
return os.path.join(os.path.expanduser("~"), "Documents")
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 not config.has_section('Timing'):
return random.uniform(0.1, 0.8) # Default value
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:
# Get config
config = get_config(translator)
# Get Chrome path
chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path())
if not chrome_path or not os.path.exists(chrome_path):
if translator:
print(f"{Fore.YELLOW}⚠️ {translator.get('register.chrome_path_invalid') if translator else 'Chrome路径无效使用默认路径'}{Style.RESET_ALL}")
chrome_path = get_default_chrome_path()
# Set browser options
co = ChromiumOptions()
# Set Chrome path
co.set_browser_path(chrome_path)
# Use incognito mode
co.set_argument("--incognito")
# Set random port
co.set_argument("--no-sandbox")
# Set random port
co.auto_port()
# 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")
co.add_extension(extension_path)
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.extension_load_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"Error loading extension: {e}")
if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.starting_browser')}{Style.RESET_ALL}")
else:
print("Starting browser...")
page = ChromiumPage(co)
return config, page
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.browser_setup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"Error setting up browser: {e}")
raise
def handle_turnstile(page, config, translator=None):
"""Handle Turnstile verification"""
try:
if translator:
print(f"{Fore.CYAN}🔄 {translator.get('register.handling_turnstile')}{Style.RESET_ALL}")
else:
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')
# 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
while retry_count < max_retries:
retry_count += 1
if translator:
print(f"{Fore.CYAN}🔄 {translator.get('register.retry_verification', attempt=retry_count)}{Style.RESET_ALL}")
else:
print(f"Attempt {retry_count} of verification...")
try:
# 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()
.shadow_root.ele("tag:iframe")
.ele("tag:body")
.sr("tag:input")
)
if challenge_check:
if translator:
print(f"{Fore.CYAN}🔄 {translator.get('register.detect_turnstile')}{Style.RESET_ALL}")
else:
print("Detected verification box...")
# from config
time.sleep(random.uniform(min_time, max_time))
challenge_check.click()
time.sleep(turnstile_time) # from config
# check verification result
if check_verification_success(page, translator):
if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
else:
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"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("Verification successful!")
return True
time.sleep(random.uniform(min_time, max_time))
if translator:
print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}")
else:
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"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")]',
'xpath://div[contains(text(), "Please try again")]'
]
for error_xpath in error_messages:
if page.ele(error_xpath):
return False
return False
except:
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, config, translator=None):
"""
Fill password form
"""
try:
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else 'Setting password'}{Style.RESET_ALL}")
# 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)
# Click submit button
submit_button = page.ele("@type=submit")
if submit_button:
submit_button.click()
time.sleep(get_random_wait_time(config, 'submit_wait'))
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'Error setting password: {str(e)}'}{Style.RESET_ALL}")
return False
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}")
# 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(get_random_wait_time(config, 'verification_code_input'))
print(f"{translator.get('register.verification_success')}")
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
# 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(get_random_wait_time(config, 'verification_retry_wait'))
# 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(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"{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'email_check_initial_wait'))
# Use existing email_tab to refresh email
email_tab.refresh_inbox()
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(get_random_wait_time(config, 'verification_code_input'))
if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
# 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(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(get_random_wait_time(config, 'settings_page_load_wait'))
return True, browser_tab
else:
if translator:
print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}")
else:
print("最后一次验证失败")
return False, None
# Get verification code, set timeout
verification_code = None
max_attempts = 20
retry_interval = get_random_wait_time(config, 'retry_interval') # Use get_random_wait_time
start_time = time.time()
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}")
break
verification_code = controller.get_verification_code()
if verification_code:
if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
break
remaining_time = int(timeout - (time.time() - start_time))
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) # 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(get_random_wait_time(config, 'verification_code_input'))
if translator:
print(f"{Fore.GREEN}{translator.get('register.verification_success')}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'verification_success_wait'))
# 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(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(get_random_wait_time(config, 'settings_page_load_wait'))
# Return success directly, let cursor_register.py handle account information acquisition
return True, browser_tab
else:
if translator:
print(f"{Fore.RED}{translator.get('register.verification_failed')}{Style.RESET_ALL}")
return False, None
return False, None
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.verification_error', error=str(e))}{Style.RESET_ALL}")
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 # 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)
# Click Continue
continue_button = browser_tab.ele('xpath://button[contains(@class, "BrandedButton") and text()="Continue"]')
if continue_button:
continue_button.click()
time.sleep(2)
# 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)
# 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)
# Handle last Turnstile verification
if handle_turnstile(browser_tab, translator):
print(f"{Fore.GREEN}Login successful!{Style.RESET_ALL}")
time.sleep(3)
return True
print(f"{Fore.RED}Login failed{Style.RESET_ALL}")
return False
except Exception as e:
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 # Save to global variable
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
page = None
success = False
try:
config, page = setup_driver(translator)
if translator:
print(f"{Fore.CYAN}🚀 {translator.get('register.browser_started')}{Style.RESET_ALL}")
# Visit registration page
url = "https://authenticator.cursor.sh/sign-up"
# 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(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")
# 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}")
# 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}")
# 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}")
# 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, config, translator):
success = True
return True, page
else:
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 'Second verification failed'}{Style.RESET_ALL}")
else:
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 'First verification failed'}{Style.RESET_ALL}")
return False, None
except Exception as e:
print(f"发生错误: {e}")
return False, None
finally:
if page and not success: # Only clean up when failed
try:
page.quit()
except:
pass
cleanup_chrome_processes(translator)
if __name__ == "__main__":
main() # Run without parameters, use randomly generated information

368
new_tempemail.py Normal file
View File

@@ -0,0 +1,368 @@
from DrissionPage import ChromiumPage, ChromiumOptions
import time
import os
import sys
from colorama import Fore, Style, init
import requests
import random
import string
# Initialize colorama
init()
class NewTempEmail:
def __init__(self, translator=None):
self.translator = translator
# Randomly choose between mail.tm and mail.gw
self.services = [
{"name": "mail.tm", "api_url": "https://api.mail.tm"},
{"name": "mail.gw", "api_url": "https://api.mail.gw"}
]
self.selected_service = random.choice(self.services)
self.api_url = self.selected_service["api_url"]
self.token = None
self.email = None
self.password = None
self.blocked_domains = self.get_blocked_domains()
def get_blocked_domains(self):
"""Get blocked domains list"""
try:
block_url = "https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/block_domain.txt"
response = requests.get(block_url, timeout=5)
if response.status_code == 200:
# Split text and remove empty lines
domains = [line.strip() for line in response.text.split('\n') if line.strip()]
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.blocked_domains_loaded', count=len(domains))}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 已加载 {len(domains)} 个被屏蔽的域名{Style.RESET_ALL}")
return domains
return []
except Exception as e:
if self.translator:
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.blocked_domains_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}⚠️ 获取被屏蔽域名列表失败: {str(e)}{Style.RESET_ALL}")
return []
def exclude_blocked_domains(self, domains):
"""Exclude blocked domains"""
if not self.blocked_domains:
return domains
filtered_domains = []
for domain in domains:
if domain['domain'] not in self.blocked_domains:
filtered_domains.append(domain)
excluded_count = len(domains) - len(filtered_domains)
if excluded_count > 0:
if self.translator:
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.domains_excluded', domains=excluded_count)}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}⚠️ 已排除 {excluded_count} 个被屏蔽的域名{Style.RESET_ALL}")
return filtered_domains
def _generate_credentials(self):
"""generate random username and password"""
username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
password = ''.join(random.choices(string.ascii_letters + string.digits + string.punctuation, k=12))
return username, password
def create_email(self):
"""create temporary email"""
max_retries = 3 # Maximum number of retries
attempt = 0 # Current attempt count
while attempt < max_retries:
attempt += 1
try:
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.visiting_site').replace('mail.tm', self.selected_service['name'])}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 正在访问 {self.selected_service['name']}...{Style.RESET_ALL}")
# Get available domain list
try:
domains_response = requests.get(f"{self.api_url}/domains", timeout=10)
if domains_response.status_code != 200:
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.status_code)}{Style.RESET_ALL}")
print(f"{Fore.RED}{self.translator.get('email.domains_list_error', error=domains_response.text)}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_available_domains') if self.translator else 'Failed to get available domains'}")
domains = domains_response.json()["hydra:member"]
print(f"{Fore.CYAN} {self.translator.get('email.available_domains_loaded', count=len(domains))}{Style.RESET_ALL}")
if not domains:
raise Exception(f"{self.translator.get('email.no_available_domains') if self.translator else '没有可用域名'}")
except Exception as e:
print(f"{Fore.RED}❌ 获取域名列表时出错: {str(e)}{Style.RESET_ALL}")
raise
# Exclude blocked domains
try:
filtered_domains = self.exclude_blocked_domains(domains)
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.domains_filtered', count=len(filtered_domains))}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 过滤后剩余 {len(filtered_domains)} 个可用域名{Style.RESET_ALL}")
if not filtered_domains:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.all_domains_blocked')}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 所有域名都被屏蔽了,尝试切换服务{Style.RESET_ALL}")
# Switch to another service
for service in self.services:
if service["api_url"] != self.api_url:
self.selected_service = service
self.api_url = service["api_url"]
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.switching_service', service=service['name'])}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 切换到 {service['name']} 服务{Style.RESET_ALL}")
return self.create_email() # Recursively call
raise Exception(f"{self.translator.get('email.no_available_domains_after_filtering') if self.translator else '过滤后没有可用域名'}")
except Exception as e:
print(f"{Fore.RED}❌ 过滤域名时出错: {str(e)}{Style.RESET_ALL}")
raise
# Generate random username and password
try:
username, password = self._generate_credentials()
self.password = password
# Create email account
selected_domain = filtered_domains[0]['domain']
email = f"{username}@{selected_domain}"
if self.translator:
print(f"{Fore.CYAN} {self.translator.get('email.trying_to_create_email', email=email)}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN} 尝试创建邮箱: {email}{Style.RESET_ALL}")
account_data = {
"address": email,
"password": password
}
except Exception as e:
print(f"{Fore.RED}❌ 生成凭据时出错: {str(e)}{Style.RESET_ALL}")
raise
# Create account
try:
create_response = requests.post(f"{self.api_url}/accounts", json=account_data, timeout=15)
if create_response.status_code != 201:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=create_response.status_code)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建账户失败: 状态码 {create_response.status_code}{Style.RESET_ALL}")
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=create_response.text)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 响应内容: {create_response.text}{Style.RESET_ALL}")
# If it's a domain problem, try the next available domain
if len(filtered_domains) > 1 and ("domain" in create_response.text.lower() or "address" in create_response.text.lower()):
print(f"{Fore.YELLOW}⚠️ 尝试使用下一个可用域名...{Style.RESET_ALL}")
# Add current domain to blocked list
if selected_domain not in self.blocked_domains:
self.blocked_domains.append(selected_domain)
# Recursively call yourself
return self.create_email()
raise Exception(f"{self.translator.get('email.failed_to_create_account') if self.translator else '创建账户失败'}")
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_create_account', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建账户时出错: {str(e)}{Style.RESET_ALL}")
raise
# Get access token
try:
token_data = {
"address": email,
"password": password
}
token_response = requests.post(f"{self.api_url}/token", json=token_data, timeout=10)
if token_response.status_code != 200:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_get_access_token', error=token_response.status_code)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 获取令牌失败: 状态码 {token_response.status_code}{Style.RESET_ALL}")
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.failed_to_get_access_token', error=token_response.text)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 响应内容: {token_response.text}{Style.RESET_ALL}")
raise Exception(f"{self.translator.get('email.failed_to_get_access_token') if self.translator else '获取访问令牌失败'}")
self.token = token_response.json()["token"]
self.email = email
except Exception as e:
print(f"{Fore.RED}❌ 获取令牌时出错: {str(e)}{Style.RESET_ALL}")
raise
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.create_success')}: {email}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 创建邮箱成功: {email}{Style.RESET_ALL}")
return email
except Exception as e:
if attempt < max_retries:
print(f"{Fore.YELLOW}⚠️ 尝试重新创建邮箱... (尝试 {attempt}/{max_retries}){Style.RESET_ALL}")
else:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.create_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 创建邮箱出错: {str(e)}{Style.RESET_ALL}")
return None
def close(self):
"""close browser"""
if self.page:
self.page.quit()
def refresh_inbox(self):
"""refresh inbox"""
try:
if self.translator:
print(f"{Fore.CYAN}🔄 {self.translator.get('email.refreshing')}{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN}🔄 正在刷新邮箱...{Style.RESET_ALL}")
# Use API to get latest email
headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers)
if response.status_code == 200:
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.refresh_success')}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 邮箱刷新成功{Style.RESET_ALL}")
return True
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.refresh_failed')}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 刷新邮箱失败{Style.RESET_ALL}")
return False
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.refresh_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 刷新邮箱出错: {str(e)}{Style.RESET_ALL}")
return False
def check_for_cursor_email(self):
"""Check if there is a Cursor verification email"""
try:
# Use API to get email list
headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers)
if response.status_code == 200:
messages = response.json()["hydra:member"]
for message in messages:
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
# Get email content
message_id = message["id"]
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
if message_response.status_code == 200:
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.verification_found')}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 找到验证邮件{Style.RESET_ALL}")
return True
if self.translator:
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_not_found')}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}⚠️ 未找到验证邮件{Style.RESET_ALL}")
return False
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.verification_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 检查验证邮件出错: {str(e)}{Style.RESET_ALL}")
return False
def get_verification_code(self):
"""get verification code"""
try:
# Use API to get email list
headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{self.api_url}/messages", headers=headers)
if response.status_code == 200:
messages = response.json()["hydra:member"]
for message in messages:
if message["from"]["address"] == "no-reply@cursor.sh" and "Verify your email address" in message["subject"]:
# Get email content
message_id = message["id"]
message_response = requests.get(f"{self.api_url}/messages/{message_id}", headers=headers)
if message_response.status_code == 200:
# Extract verification code from email content
email_content = message_response.json()["text"]
# Find 6-digit verification code
import re
code_match = re.search(r'\b\d{6}\b', email_content)
if code_match:
code = code_match.group(0)
if self.translator:
print(f"{Fore.GREEN}{self.translator.get('email.verification_code_found')}: {code}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}✅ 获取验证码成功: {code}{Style.RESET_ALL}")
return code
if self.translator:
print(f"{Fore.YELLOW}⚠️ {self.translator.get('email.verification_code_not_found')}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}⚠️ 未找到有效的验证码{Style.RESET_ALL}")
return None
except Exception as e:
if self.translator:
print(f"{Fore.RED}{self.translator.get('email.verification_code_error')}: {str(e)}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ 获取验证码出错: {str(e)}{Style.RESET_ALL}")
return None
def main(translator=None):
temp_email = NewTempEmail(translator)
try:
email = temp_email.create_email()
if email:
if translator:
print(f"\n{Fore.CYAN}📧 {translator.get('email.address')}: {email}{Style.RESET_ALL}")
else:
print(f"\n{Fore.CYAN}📧 临时邮箱地址: {email}{Style.RESET_ALL}")
# Test refresh function
while True:
if translator:
choice = input(f"\n{translator.get('email.refresh_prompt')}: ").lower()
else:
choice = input("\n按 R 刷新邮箱,按 Q 退出: ").lower()
if choice == 'r':
temp_email.refresh_inbox()
elif choice == 'q':
break
finally:
temp_email.close()
if __name__ == "__main__":
main()

826
oauth_auth.py Normal file
View File

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

View File

@@ -4,10 +4,10 @@ from colorama import Fore, Style, init
import sys
import os
# 初始化colorama
# Initialize colorama
init()
# 定义emoji常量
# Define emoji constants
EMOJI = {
"PROCESS": "⚙️",
"SUCCESS": "",
@@ -19,15 +19,15 @@ EMOJI = {
class CursorQuitter:
def __init__(self, timeout=5, translator=None):
self.timeout = timeout
self.translator = translator # 使用传入的翻译器
self.translator = translator # Use the passed translator
def quit_cursor(self):
"""温和地关闭 Cursor 进程"""
"""Gently close Cursor processes"""
try:
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.start')}...{Style.RESET_ALL}")
cursor_processes = []
# 收集所有 Cursor 进程
# Collect all Cursor processes
for proc in psutil.process_iter(['pid', 'name']):
try:
if proc.info['name'].lower() in ['cursor.exe', 'cursor']:
@@ -39,7 +39,7 @@ class CursorQuitter:
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('quit_cursor.no_process')}{Style.RESET_ALL}")
return True
# 温和地请求进程终止
# Gently request processes to terminate
for proc in cursor_processes:
try:
if proc.is_running():
@@ -48,7 +48,7 @@ class CursorQuitter:
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# 等待进程自然终止
# Wait for processes to terminate naturally
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('quit_cursor.waiting')}...{Style.RESET_ALL}")
start_time = time.time()
while time.time() - start_time < self.timeout:
@@ -66,7 +66,7 @@ class CursorQuitter:
time.sleep(0.5)
# 如果超时后仍有进程在运行
# If processes are still running after timeout
if still_running:
process_list = ", ".join([str(p.pid) for p in still_running])
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.timeout', pids=process_list)}{Style.RESET_ALL}")
@@ -79,11 +79,11 @@ class CursorQuitter:
return False
def quit_cursor(translator=None, timeout=5):
"""便捷函数,用于直接调用退出功能"""
"""Convenient function for directly calling the quit function"""
quitter = CursorQuitter(timeout, translator)
return quitter.quit_cursor()
if __name__ == "__main__":
# 如果直接运行,使用默认翻译器
# If run directly, use the default translator
from main import translator as main_translator
quit_cursor(main_translator)

View File

@@ -1,22 +0,0 @@
{
"manifest_version": 3,
"name": "Recaptcha Blocker",
"version": "1.0",
"description": "Blocks reCAPTCHA from loading",
"content_scripts": [
{
"matches": ["*://yopmail.com/zh/wm*"],
"js": ["script.js"],
"run_at": "document_start",
"all_frames": true
}
],
"permissions": [
"webNavigation",
"activeTab"
],
"host_permissions": [
"*://yopmail.com/*",
"*://*.google.com/*"
]
}

View File

@@ -1,123 +0,0 @@
// Debug logging function
function log(message) {
console.log(`[reCAPTCHA Bypass] ${new Date().toISOString()}: ${message}`);
}
// Function to get element by selector
function qSelector(selector) {
return document.querySelector(selector);
}
// Constants
const MAX_ATTEMPTS = 1;
const SELECTORS = {
CHECK_BOX: ".recaptcha-checkbox-border",
AUDIO_BUTTON: "#recaptcha-audio-button",
PLAY_BUTTON: ".rc-audiochallenge-play-button .rc-button-default",
AUDIO_SOURCE: "#audio-source",
IMAGE_SELECT: "#rc-imageselect",
RESPONSE_FIELD: ".rc-audiochallenge-response-field",
AUDIO_ERROR_MESSAGE: ".rc-audiochallenge-error-message",
AUDIO_RESPONSE: "#audio-response",
RELOAD_BUTTON: "#recaptcha-reload-button",
RECAPTCHA_STATUS: "#recaptcha-accessible-status",
DOSCAPTCHA: ".rc-doscaptcha-body",
VERIFY_BUTTON: "#recaptcha-verify-button"
};
// Function to check if element is hidden
function isHidden(el) {
return (el.offsetParent === null);
}
// Function to handle the bypass process
async function bypassRecaptcha() {
try {
log('Starting bypass process...');
log('Waiting 3 seconds before starting...');
await new Promise(resolve => setTimeout(resolve, 3000));
let solved = false;
let checkBoxClicked = false;
let requestCount = 0;
const recaptchaInitialStatus = qSelector(SELECTORS.RECAPTCHA_STATUS) ?
qSelector(SELECTORS.RECAPTCHA_STATUS).innerText : "";
log('Initial reCAPTCHA status: ' + recaptchaInitialStatus);
// Main bypass logic
try {
if (!checkBoxClicked && qSelector(SELECTORS.CHECK_BOX) &&
!isHidden(qSelector(SELECTORS.CHECK_BOX))) {
log('Clicking checkbox...');
qSelector(SELECTORS.CHECK_BOX).click();
checkBoxClicked = true;
}
// Check if the captcha is solved
if (qSelector(SELECTORS.RECAPTCHA_STATUS) &&
(qSelector(SELECTORS.RECAPTCHA_STATUS).innerText != recaptchaInitialStatus)) {
solved = true;
log('SOLVED!');
}
if (requestCount > MAX_ATTEMPTS) {
log('Attempted Max Retries. Stopping the solver');
solved = true;
}
// Stop solving when Automated queries message is shown
if (qSelector(SELECTORS.DOSCAPTCHA) &&
qSelector(SELECTORS.DOSCAPTCHA).innerText.length > 0) {
log('Automated Queries Detected');
}
} catch (err) {
log(`Error in main bypass logic: ${err.message}`);
}
log('Bypass process completed');
// If not solved, retry after delay
if (!solved && requestCount < MAX_ATTEMPTS) {
requestCount++;
log(`Retrying... Attempt ${requestCount} of ${MAX_ATTEMPTS}`);
setTimeout(bypassRecaptcha, 2000);
}
} catch (e) {
log(`Bypass failed: ${e.message}`);
}
}
// Create a MutationObserver to watch for reCAPTCHA elements
log('Setting up MutationObserver...');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && // Element node
((node.tagName === 'SCRIPT' && node.src && node.src.includes('recaptcha')) ||
(node.tagName === 'IFRAME' && node.src && node.src.includes('recaptcha')))) {
log(`Detected new reCAPTCHA element: ${node.tagName} - ${node.src}`);
bypassRecaptcha();
}
});
});
});
// Start observing
observer.observe(document, {
childList: true,
subtree: true
});
log('MutationObserver started');
// Run on page load
if (document.readyState === 'loading') {
log('Document still loading, waiting for DOMContentLoaded');
document.addEventListener('DOMContentLoaded', bypassRecaptcha);
} else {
log('Document already loaded, starting bypass process');
bypassRecaptcha();
}

View File

@@ -5,12 +5,20 @@ import uuid
import hashlib
import shutil
import sqlite3
import platform
import re
import tempfile
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
# 初始化colorama
# Initialize colorama
init()
# 定义emoji常量
# Define emoji constants
EMOJI = {
"FILE": "📄",
"BACKUP": "💾",
@@ -20,62 +28,472 @@ EMOJI = {
"RESET": "🔄",
}
def get_cursor_paths(translator=None) -> Tuple[str, str]:
""" Get Cursor related paths"""
system = platform.system()
# 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 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:
"""
Get Cursor machineId file path based on operating system
Returns:
str: Path to machineId file
"""
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if os.path.exists(config_file):
config.read(config_file)
if sys.platform == "win32": # Windows
if not config.has_section('WindowsPaths'):
config.add_section('WindowsPaths')
config.set('WindowsPaths', 'machine_id_path',
os.path.join(os.getenv("APPDATA"), "Cursor", "machineId"))
return config.get('WindowsPaths', 'machine_id_path')
elif sys.platform == "linux": # Linux
if not config.has_section('LinuxPaths'):
config.add_section('LinuxPaths')
config.set('LinuxPaths', 'machine_id_path',
os.path.expanduser("~/.config/Cursor/machineId"))
return config.get('LinuxPaths', 'machine_id_path')
elif sys.platform == "darwin": # macOS
if not config.has_section('MacPaths'):
config.add_section('MacPaths')
config.set('MacPaths', 'machine_id_path',
os.path.expanduser("~/Library/Application Support/Cursor/machineId"))
return config.get('MacPaths', 'machine_id_path')
else:
raise OSError(f"Unsupported operating system: {sys.platform}")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
def get_workbench_cursor_path(translator=None) -> str:
"""Get Cursor workbench.desktop.main.js path"""
system = platform.system()
paths_map = {
"Darwin": { # macOS
"base": "/Applications/Cursor.app/Contents/Resources/app",
"main": "out/vs/workbench/workbench.desktop.main.js"
},
"Windows": {
"base": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"),
"main": "out/vs/workbench/workbench.desktop.main.js"
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"main": "out/vs/workbench/workbench.desktop.main.js"
}
}
if system not in paths_map:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
if system == "Linux":
for base in paths_map["Linux"]["bases"]:
main_path = os.path.join(base, paths_map["Linux"]["main"])
if os.path.exists(main_path):
return main_path
raise OSError(translator.get('reset.linux_path_not_found') if translator else "在 Linux 系统上未找到 Cursor 安装路径")
base_path = paths_map[system]["base"]
main_path = os.path.join(base_path, paths_map[system]["main"])
if not os.path.exists(main_path):
raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}")
return main_path
def version_check(version: str, min_version: str = "", max_version: str = "", translator=None) -> bool:
"""Version number check"""
version_pattern = r"^\d+\.\d+\.\d+$"
try:
if not re.match(version_pattern, version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.invalid_version_format', version=version)}{Style.RESET_ALL}")
return False
def parse_version(ver: str) -> Tuple[int, ...]:
return tuple(map(int, ver.split(".")))
current = parse_version(version)
if min_version and current < parse_version(min_version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_low', version=version, min_version=min_version)}{Style.RESET_ALL}")
return False
if max_version and current > parse_version(max_version):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_too_high', version=version, max_version=max_version)}{Style.RESET_ALL}")
return False
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_check_error', error=str(e))}{Style.RESET_ALL}")
return False
def check_cursor_version(translator) -> bool:
"""Check Cursor version"""
try:
pkg_path, _ = get_cursor_paths(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:
"""
Modify file content
"""
try:
# Save original file permissions
original_stat = os.stat(file_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
# Create temporary file
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file:
# Read original content
with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file:
content = main_file.read()
if sys.platform == "win32":
# Define replacement patterns
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "linux":
CButton_old_pattern = r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
elif sys.platform == "darwin":
CButton_old_pattern = r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)'
CButton_new_pattern = r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)'
CBadge_old_pattern = r'<div>Pro Trial'
CBadge_new_pattern = r'<div>Pro'
CToast_old_pattern = r'notifications-toasts'
CToast_new_pattern = r'notifications-toasts hidden'
# Replace content
content = content.replace(CButton_old_pattern, CButton_new_pattern)
content = content.replace(CBadge_old_pattern, CBadge_new_pattern)
content = content.replace(CToast_old_pattern, CToast_new_pattern)
# Write to temporary file
tmp_file.write(content)
tmp_path = tmp_file.name
# Backup original file
backup_path = file_path + ".backup"
if os.path.exists(backup_path):
os.remove(backup_path)
shutil.copy2(file_path, backup_path)
# Move temporary file to original position
if os.path.exists(file_path):
os.remove(file_path)
shutil.move(tmp_path, file_path)
# Restore original permissions
os.chmod(file_path, original_mode)
if os.name != "nt": # Not Windows
os.chown(file_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
try:
os.unlink(tmp_path)
except:
pass
return False
def modify_main_js(main_path: str, translator) -> bool:
"""Modify main.js file"""
try:
original_stat = os.stat(main_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
with open(main_path, "r", encoding="utf-8") as main_file:
content = main_file.read()
patterns = {
r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}",
r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}",
}
for pattern, replacement in patterns.items():
content = re.sub(pattern, replacement, content)
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(main_path, main_path + ".old")
shutil.move(tmp_path, main_path)
os.chmod(main_path, original_mode)
if os.name != "nt":
os.chown(main_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
os.unlink(tmp_path)
return False
def patch_cursor_get_machine_id(translator) -> bool:
"""Patch Cursor getMachineId function"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.start_patching')}...{Style.RESET_ALL}")
# Get paths
pkg_path, main_path = get_cursor_paths(translator)
# Check file permissions
for file_path in [pkg_path, main_path]:
if not os.path.isfile(file_path):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.file_not_found', path=file_path)}{Style.RESET_ALL}")
return False
if not os.access(file_path, os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.no_write_permission', path=file_path)}{Style.RESET_ALL}")
return False
# Get version number
try:
with open(pkg_path, "r", encoding="utf-8") as f:
version = json.load(f)["version"]
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.current_version', version=version)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.read_version_failed', error=str(e))}{Style.RESET_ALL}")
return False
# Check version
if not version_check(version, min_version="0.45.0", translator=translator):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.version_not_supported')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('reset.version_check_passed')}{Style.RESET_ALL}")
# Backup file
backup_path = main_path + ".bak"
if not os.path.exists(backup_path):
shutil.copy2(main_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
# Modify file
if not modify_main_js(main_path, translator):
return False
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.patch_completed')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.patch_failed', error=str(e))}{Style.RESET_ALL}")
return False
class MachineIDResetter:
def __init__(self, translator=None):
self.translator = translator
# 判断操作系统
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if not os.path.exists(config_file):
raise FileNotFoundError(f"Config file not found: {config_file}")
config.read(config_file, encoding='utf-8')
# Check operating system
if sys.platform == "win32": # Windows
appdata = os.getenv("APPDATA")
if appdata is None:
raise EnvironmentError("APPDATA Environment Variable Not Set")
self.db_path = os.path.join(
appdata, "Cursor", "User", "globalStorage", "storage.json"
)
self.sqlite_path = os.path.join(
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
)
if not config.has_section('WindowsPaths'):
config.add_section('WindowsPaths')
config.set('WindowsPaths', 'storage_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "storage.json"
))
config.set('WindowsPaths', 'sqlite_path', os.path.join(
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
))
self.db_path = config.get('WindowsPaths', 'storage_path')
self.sqlite_path = config.get('WindowsPaths', 'sqlite_path')
elif sys.platform == "darwin": # macOS
self.db_path = os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/storage.json"
))
self.sqlite_path = os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
))
if not config.has_section('MacPaths'):
config.add_section('MacPaths')
config.set('MacPaths', 'storage_path', os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/storage.json"
)))
config.set('MacPaths', 'sqlite_path', os.path.abspath(os.path.expanduser(
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('MacPaths', 'storage_path')
self.sqlite_path = config.get('MacPaths', 'sqlite_path')
elif sys.platform == "linux": # Linux
self.db_path = os.path.abspath(os.path.expanduser(
"~/.config/Cursor/User/globalStorage/storage.json"
))
self.sqlite_path = os.path.abspath(os.path.expanduser(
"~/.config/Cursor/User/globalStorage/state.vscdb"
))
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("~")
config.set('LinuxPaths', 'storage_path', os.path.abspath(os.path.join(
actual_home,
".config/Cursor/User/globalStorage/storage.json"
)))
config.set('LinuxPaths', 'sqlite_path', os.path.abspath(os.path.join(
actual_home,
".config/Cursor/User/globalStorage/state.vscdb"
)))
self.db_path = config.get('LinuxPaths', 'storage_path')
self.sqlite_path = config.get('LinuxPaths', 'sqlite_path')
else:
raise NotImplementedError(f"Not Supported OS: {sys.platform}")
# Save any changes to config file
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
def generate_new_ids(self):
"""生成新的机器ID"""
# 生成新的UUID
"""Generate new machine ID"""
# Generate new UUID
dev_device_id = str(uuid.uuid4())
# 生成新的machineId (64个字符的十六进制)
# Generate new machineId (64 characters of hexadecimal)
machine_id = hashlib.sha256(os.urandom(32)).hexdigest()
# 生成新的macMachineId (128个字符的十六进制)
# Generate new macMachineId (128 characters of hexadecimal)
mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest()
# 生成新的sqmId
# Generate new sqmId
sqm_id = "{" + str(uuid.uuid4()).upper() + "}"
self.update_machine_id_file(dev_device_id)
return {
"telemetry.devDeviceId": dev_device_id,
"telemetry.macMachineId": mac_machine_id,
"telemetry.machineId": machine_id,
"telemetry.sqmId": sqm_id,
"storage.serviceMachineId": dev_device_id, # 添加 storage.serviceMachineId
"storage.serviceMachineId": dev_device_id, # Add storage.serviceMachineId
}
def update_sqlite_db(self, new_ids):
"""更新 SQLite 数据库中的机器ID"""
"""Update machine ID in SQLite database"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_sqlite')}...{Style.RESET_ALL}")
@@ -98,7 +516,7 @@ class MachineIDResetter:
INSERT OR REPLACE INTO ItemTable (key, value)
VALUES (?, ?)
""", (key, value))
print(f"{EMOJI['INFO']} {Fore.CYAN}{self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}")
print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('reset.updating_pair')}: {key}{Style.RESET_ALL}")
conn.commit()
conn.close()
@@ -109,8 +527,61 @@ class MachineIDResetter:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.sqlite_error', error=str(e))}{Style.RESET_ALL}")
return False
def update_system_ids(self, new_ids):
"""Update system-level IDs"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.updating_system_ids')}...{Style.RESET_ALL}")
if sys.platform.startswith("win"):
self._update_windows_machine_guid()
elif sys.platform == "darwin":
self._update_macos_platform_uuid(new_ids)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.system_ids_updated')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}")
return False
def _update_windows_machine_guid(self):
"""Update Windows MachineGuid"""
try:
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Cryptography",
0,
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
)
new_guid = str(uuid.uuid4())
winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_guid_updated')}{Style.RESET_ALL}")
except PermissionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
raise
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
raise
def _update_macos_platform_uuid(self, new_ids):
"""Update macOS Platform UUID"""
try:
uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist"
if os.path.exists(uuid_file):
# Use sudo to execute plutil command
cmd = f'sudo plutil -replace "UUID" -string "{new_ids["telemetry.macMachineId"]}" "{uuid_file}"'
result = os.system(cmd)
if result == 0:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.macos_platform_uuid_updated')}{Style.RESET_ALL}")
else:
raise Exception(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.failed_to_execute_plutil_command')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_macos_platform_uuid_failed', error=str(e))}{Style.RESET_ALL}")
raise
def reset_machine_ids(self):
"""重置机器ID并备份原文件"""
"""Reset machine ID and backup original file"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.checking')}...{Style.RESET_ALL}")
@@ -136,14 +607,36 @@ class MachineIDResetter:
print(f"{Fore.CYAN}{EMOJI['RESET']} {self.translator.get('reset.generating')}...{Style.RESET_ALL}")
new_ids = self.generate_new_ids()
# Update configuration file
config.update(new_ids)
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('reset.saving_json')}...{Style.RESET_ALL}")
with open(self.db_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4)
# Update SQLite database
self.update_sqlite_db(new_ids)
# Update system IDs
self.update_system_ids(new_ids)
### Remove In v1.7.02
# Modify workbench.desktop.main.js
# workbench_path = get_workbench_cursor_path(self.translator)
# modify_workbench_js(workbench_path, self.translator)
### 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}")
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}")
for key, value in new_ids.items():
@@ -159,19 +652,58 @@ class MachineIDResetter:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.process_error', error=str(e))}{Style.RESET_ALL}")
return False
def update_machine_id_file(self, machine_id: str) -> bool:
"""
Update machineId file with new machine_id
Args:
machine_id (str): New machine ID to write
Returns:
bool: True if successful, False otherwise
"""
try:
# Get the machineId file path
machine_id_path = get_cursor_machine_id_path()
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(machine_id_path), exist_ok=True)
# Create backup if file exists
if os.path.exists(machine_id_path):
backup_path = machine_id_path + ".backup"
try:
shutil.copy2(machine_id_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('reset.backup_created', path=backup_path) if self.translator else f'Backup created at: {backup_path}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('reset.backup_creation_failed', error=str(e)) if self.translator else f'Could not create backup: {str(e)}'}{Style.RESET_ALL}")
# Write new machine ID to file
with open(machine_id_path, "w", encoding="utf-8") as f:
f.write(machine_id)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.update_success') if self.translator else 'Successfully updated machineId file'}{Style.RESET_ALL}")
return True
except Exception as e:
error_msg = f"Failed to update machineId file: {str(e)}"
if self.translator:
error_msg = self.translator.get('reset.update_failed', error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {error_msg}{Style.RESET_ALL}")
return False
def run(translator=None):
"""Main function to be called from main.py"""
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}")
resetter = MachineIDResetter(translator)
resetter = MachineIDResetter(translator) # Correctly pass translator
resetter.reset_machine_ids()
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('reset.press_enter')}...")
if __name__ == "__main__":
# 如果直接运行,使用默认翻译器
from main import translator as main_translator
run(main_translator)

View File

@@ -1,4 +1,4 @@
# 設置顏色主題
# set color theme
$Theme = @{
Primary = 'Cyan'
Success = 'Green'
@@ -17,7 +17,7 @@ $Logo = @"
"@
# 美化輸出函數
# Beautiful Output Function
function Write-Styled {
param (
[string]$Message,
@@ -40,7 +40,7 @@ function Write-Styled {
}
}
# 獲取版本號函數
# Get version number function
function Get-LatestVersion {
try {
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest"
@@ -54,28 +54,28 @@ function Get-LatestVersion {
}
}
# 顯示 Logo
# Show Logo
Write-Host $Logo -ForegroundColor $Theme.Primary
$releaseInfo = Get-LatestVersion
$version = $releaseInfo.Version
Write-Host "Version $version" -ForegroundColor $Theme.Info
Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info
# 設置 TLS 1.2
# Set TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# 主安裝函數
# Main installation function
function Install-CursorFreeVIP {
Write-Styled "Start downloading Cursor Free VIP" -Color $Theme.Primary -Prefix "Download"
try {
# 獲取最新版本
# Get latest version
Write-Styled "Checking latest version..." -Color $Theme.Primary -Prefix "Update"
$releaseInfo = Get-LatestVersion
$version = $releaseInfo.Version
Write-Styled "Found latest version: $version" -Color $Theme.Success -Prefix "Version"
# 查找對應的資源
# Find corresponding resources
$asset = $releaseInfo.Assets | Where-Object { $_.name -eq "CursorFreeVIP_${version}_windows.exe" }
if (!$asset) {
Write-Styled "File not found: CursorFreeVIP_${version}_windows.exe" -Color $Theme.Error -Prefix "Error"
@@ -86,22 +86,114 @@ function Install-CursorFreeVIP {
throw "Cannot find target file"
}
# 下載到Downloads文件夾
# Check if Downloads folder already exists for the corresponding version
$DownloadsPath = [Environment]::GetFolderPath("UserProfile") + "\Downloads"
$downloadPath = Join-Path $DownloadsPath "CursorFreeVIP.exe"
$downloadPath = Join-Path $DownloadsPath "CursorFreeVIP_${version}_windows.exe"
Write-Styled "Downloading to Downloads folder..." -Color $Theme.Primary -Prefix "Download"
if (Test-Path $downloadPath) {
Write-Styled "Found existing installation file" -Color $Theme.Success -Prefix "Found"
Write-Styled "Location: $downloadPath" -Color $Theme.Info -Prefix "Location"
# Check if running with administrator privileges
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Styled "Requesting administrator privileges..." -Color $Theme.Warning -Prefix "Admin"
# Create new process with administrator privileges
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $downloadPath
$startInfo.UseShellExecute = $true
$startInfo.Verb = "runas"
try {
[System.Diagnostics.Process]::Start($startInfo)
Write-Styled "Program started with admin privileges" -Color $Theme.Success -Prefix "Launch"
return
}
catch {
Write-Styled "Failed to start with admin privileges. Starting normally..." -Color $Theme.Warning -Prefix "Warning"
Start-Process $downloadPath
return
}
}
# If already running with administrator privileges, start directly
Start-Process $downloadPath
return
}
Write-Styled "No existing installation file found, starting download..." -Color $Theme.Primary -Prefix "Download"
# Create WebClient and add progress event
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "PowerShell Script")
$webClient.DownloadFile($asset.browser_download_url, $downloadPath)
# Define progress variables
$Global:downloadedBytes = 0
$Global:totalBytes = 0
$Global:lastProgress = 0
$Global:lastBytes = 0
$Global:lastTime = Get-Date
# Download progress event
$eventId = [guid]::NewGuid()
Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action {
$Global:downloadedBytes = $EventArgs.BytesReceived
$Global:totalBytes = $EventArgs.TotalBytesToReceive
$progress = [math]::Round(($Global:downloadedBytes / $Global:totalBytes) * 100, 1)
# Only update display when progress changes by more than 1%
if ($progress -gt $Global:lastProgress + 1) {
$Global:lastProgress = $progress
$downloadedMB = [math]::Round($Global:downloadedBytes / 1MB, 2)
$totalMB = [math]::Round($Global:totalBytes / 1MB, 2)
# Calculate download speed
$currentTime = Get-Date
$timeSpan = ($currentTime - $Global:lastTime).TotalSeconds
if ($timeSpan -gt 0) {
$bytesChange = $Global:downloadedBytes - $Global:lastBytes
$speed = $bytesChange / $timeSpan
# Choose appropriate unit based on speed
$speedDisplay = if ($speed -gt 1MB) {
"$([math]::Round($speed / 1MB, 2)) MB/s"
} elseif ($speed -gt 1KB) {
"$([math]::Round($speed / 1KB, 2)) KB/s"
} else {
"$([math]::Round($speed, 2)) B/s"
}
Write-Host "`rDownloading: $downloadedMB MB / $totalMB MB ($progress%) - $speedDisplay" -NoNewline -ForegroundColor Cyan
# Update last data
$Global:lastBytes = $Global:downloadedBytes
$Global:lastTime = $currentTime
}
}
} | Out-Null
# Download completed event
Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action {
Write-Host "`r" -NoNewline
Write-Styled "Download completed!" -Color $Theme.Success -Prefix "Complete"
Unregister-Event -SourceIdentifier $eventId
} | Out-Null
# Start download
$webClient.DownloadFileAsync([Uri]$asset.browser_download_url, $downloadPath)
# Wait for download to complete
while ($webClient.IsBusy) {
Start-Sleep -Milliseconds 100
}
Write-Styled "Download completed!" -Color $Theme.Success -Prefix "Complete"
Write-Styled "File location: $downloadPath" -Color $Theme.Info -Prefix "Location"
Write-Styled "Starting program..." -Color $Theme.Primary -Prefix "Launch"
# 運行程序
# Run program
Start-Process $downloadPath
}
catch {
Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error"
@@ -109,7 +201,7 @@ function Install-CursorFreeVIP {
}
}
# 執行安裝
# Execute installation
try {
Install-CursorFreeVIP
}
@@ -120,4 +212,4 @@ catch {
finally {
Write-Host "`nPress any key to exit..." -ForegroundColor $Theme.Info
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
}

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

@@ -1,6 +1,6 @@
#!/bin/bash
# 顏色定義
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
@@ -22,7 +22,7 @@ EOF
echo -e "${NC}"
}
# 获取下载文件夹路径
# Get download folder path
get_downloads_dir() {
if [[ "$(uname)" == "Darwin" ]]; then
echo "$HOME/Downloads"
@@ -36,59 +36,157 @@ get_downloads_dir() {
fi
}
# 獲取最新版本
# Get latest version
get_latest_version() {
echo -e "${CYAN} 正在檢查最新版本...${NC}"
local latest_release
latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest)
if [ $? -ne 0 ]; then
echo -e "${RED}❌ 無法獲取最新版本信息${NC}"
echo -e "${CYAN} Checking latest version...${NC}"
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')
echo -e "${GREEN}✅ 找到最新版本: ${VERSION}${NC}"
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}"
}
# 檢測系統類型
# Detect system type and architecture
detect_os() {
if [[ "$(uname)" == "Darwin" ]]; then
OS="mac"
# Detect macOS architecture
ARCH=$(uname -m)
if [[ "$ARCH" == "arm64" ]]; then
OS="mac_arm64"
echo -e "${CYAN} Detected macOS ARM64 architecture${NC}"
else
OS="mac_intel"
echo -e "${CYAN} Detected macOS Intel architecture${NC}"
fi
elif [[ "$(uname)" == "Linux" ]]; then
# 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
OS="linux"
# Assume Windows
OS="windows"
echo -e "${CYAN} Detected Windows system${NC}"
fi
}
# 下載並安裝
# Install and download
install_cursor_free_vip() {
local downloads_dir=$(get_downloads_dir)
local binary_name="CursorFreeVIP_${VERSION}_${OS}"
local binary_path="${downloads_dir}/cursor-free-vip"
local binary_path="${downloads_dir}/${binary_name}"
local download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
echo -e "${CYAN} 正在下載到 ${downloads_dir}...${NC}"
# Check if file already exists
if [ -f "${binary_path}" ]; then
echo -e "${GREEN}✅ Found existing installation file${NC}"
echo -e "${CYAN} Location: ${binary_path}${NC}"
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${YELLOW}⚠️ Requesting administrator privileges...${NC}"
if command -v sudo >/dev/null 2>&1; then
echo -e "${CYAN} Starting program with sudo...${NC}"
sudo chmod +x "${binary_path}"
sudo "${binary_path}"
else
echo -e "${YELLOW}⚠️ sudo not found, trying to run normally...${NC}"
chmod +x "${binary_path}"
"${binary_path}"
fi
else
# Already running as root
echo -e "${CYAN} Already running as root, starting program...${NC}"
chmod +x "${binary_path}"
"${binary_path}"
fi
return
fi
echo -e "${CYAN} No existing installation file found, starting download...${NC}"
echo -e "${CYAN} Downloading to ${downloads_dir}...${NC}"
echo -e "${CYAN} Download link: ${download_url}${NC}"
# Check if file exists
if curl --output /dev/null --silent --head --fail "$download_url"; then
echo -e "${GREEN}✅ File exists, starting download...${NC}"
else
echo -e "${RED}❌ Download link does not exist: ${download_url}${NC}"
echo -e "${YELLOW}⚠️ Trying without architecture...${NC}"
# Try without architecture
if [[ "$OS" == "mac_arm64" || "$OS" == "mac_intel" ]]; then
OS="mac"
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
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
fi
else
exit 1
fi
fi
# Download file
if ! curl -L -o "${binary_path}" "$download_url"; then
echo -e "${RED}下載失敗${NC}"
echo -e "${RED}Download failed${NC}"
exit 1
fi
echo -e "${CYAN} 正在設置執行權限...${NC}"
chmod +x "${binary_path}"
# Check downloaded file size
local file_size=$(stat -f%z "${binary_path}" 2>/dev/null || stat -c%s "${binary_path}" 2>/dev/null)
echo -e "${CYAN} Downloaded file size: ${file_size} bytes${NC}"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 安裝完成!${NC}"
echo -e "${CYAN} 程序已下載到: ${binary_path}${NC}"
echo -e "${CYAN} 正在啟動程序...${NC}"
# If file is too small, it might be an error message
if [ "$file_size" -lt 1000 ]; then
echo -e "${YELLOW} Warning: Downloaded file is too small, possibly not a valid executable file${NC}"
echo -e "${YELLOW} File content:${NC}"
cat "${binary_path}"
echo ""
echo -e "${RED}❌ Download failed, please check version and operating system${NC}"
exit 1
fi
echo -e "${CYAN} Setting executable permissions...${NC}"
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}"
# 直接运行程序
# Run program directly
"${binary_path}"
else
echo -e "${RED}安裝失敗${NC}"
echo -e "${RED}Installation failed${NC}"
exit 1
fi
}
# 主程序
# Main program
main() {
print_logo
get_latest_version
@@ -96,5 +194,5 @@ main() {
install_cursor_free_vip
}
# 運行主程序
main
# Run main program
main

509
totally_reset_cursor.py Normal file
View File

@@ -0,0 +1,509 @@
import os
import shutil
import platform
import time
import sys
import glob
import json
import uuid
import random
import string
import re
from datetime import datetime
import subprocess
from colorama import Fore, Style, init
from main import translator
from main import EMOJI
# Initialize colorama
init()
# Define emoji and color constants
EMOJI = {
"FILE": "📄",
"BACKUP": "💾",
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"RESET": "🔄",
"MENU": "📋",
"ARROW": "",
"LANG": "🌐",
"UPDATE": "🔄",
"ADMIN": "🔐",
"STOP": "🛑",
"DISCLAIMER": "⚠️",
"WARNING": "⚠️"
}
def display_banner():
"""Displays a stylized banner for the tool."""
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.title')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
def display_features():
"""Displays the features of the Cursor AI Reset Tool."""
print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('totally_reset.feature_title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_1')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_2')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_3')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_4')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_5')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_6')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_7')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_8')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.feature_9')} {Style.RESET_ALL}\n")
def display_disclaimer():
"""Displays a disclaimer for the user."""
print(f"\n{Fore.RED}{EMOJI['DISCLAIMER']} {translator.get('totally_reset.disclaimer_title')}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_1')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_2')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_3')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_4')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_5')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_6')} {Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.disclaimer_7')} {Style.RESET_ALL} \n")
def get_confirmation():
"""Gets confirmation from the user to proceed."""
while True:
choice = input(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.confirm_title')} (Y/n): ").strip().lower()
if choice == "y" or choice == "":
return True
elif choice == "n":
return False
else:
print(f"{EMOJI['ERROR']} {translator.get('totally_reset.invalid_choice')}")
def remove_dir(path):
"""Removes a directory if it exists and logs the action."""
# Safety check to ensure we're only deleting Cursor-related directories
if not is_cursor_related(path):
print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}")
return
if os.path.exists(path):
try:
shutil.rmtree(path, ignore_errors=True)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}")
def remove_file(path):
"""Removes a file if it exists and logs the action."""
# Safety check to ensure we're only deleting Cursor-related files
if not is_cursor_related(path):
print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('totally_reset.skipped_for_safety', path=path)} {Style.RESET_ALL}")
return
if os.path.isfile(path):
try:
os.remove(path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.deleted', path=path)} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_deleting', path=path, error=str(e))} {Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.not_found', path=path)} {Style.RESET_ALL}")
def is_cursor_related(path):
"""
Safety function to verify a path is related to Cursor before deletion.
Returns True if the path appears to be related to Cursor AI.
"""
# Skip .vscode check as it's shared with VS Code
if path.endswith(".vscode"):
return False
# Check if path contains cursor-related terms
cursor_terms = ["cursor", "cursorai", "cursor-electron"]
# Convert path to lowercase for case-insensitive matching
lower_path = path.lower()
# Return True if any cursor term is present in the path
for term in cursor_terms:
if term in lower_path:
return True
# Check specific known Cursor file patterns
cursor_patterns = [
r"\.cursor_.*$",
r"cursor-.*\.json$",
r"cursor_.*\.json$",
r"cursor-machine-id$",
r"trial_info\.json$",
r"license\.json$"
]
for pattern in cursor_patterns:
if re.search(pattern, lower_path):
return True
# If it's a specific file that we know is only for Cursor
if os.path.basename(lower_path) in [
"cursor_trial_data",
"cursor-state.json",
"cursor-machine-id",
"ai-settings.json",
"cursor.desktop"
]:
return True
return False
def find_cursor_license_files(base_path, pattern):
"""Finds files matching a pattern that might contain license information."""
try:
matches = []
for root, dirnames, filenames in os.walk(base_path):
for filename in filenames:
# Check if filename matches any pattern before adding to matches
if any(p.lower() in filename.lower() for p in pattern):
full_path = os.path.join(root, filename)
# Extra safety check to ensure it's cursor-related
if is_cursor_related(full_path):
matches.append(full_path)
return matches
except Exception as e:
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.error_searching', path=base_path, error=str(e))} {Style.RESET_ALL}")
return []
def generate_new_machine_id():
"""Generates a new random machine ID."""
return str(uuid.uuid4())
def create_fake_machine_id(path):
"""Creates a new machine ID file with random ID."""
if not is_cursor_related(path):
return
try:
new_id = generate_new_machine_id()
directory = os.path.dirname(path)
# Ensure directory exists
if not os.path.exists(directory):
os.makedirs(directory)
with open(path, 'w') as f:
f.write(new_id)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_machine_id', path=path)} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_machine_id', path=path, error=str(e))} {Style.RESET_ALL}")
def reset_machine_id(system, home):
"""Resets machine ID in all possible locations."""
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_machine_id')} {Style.RESET_ALL}")
# Common machine ID locations based on OS
if system == "Windows":
machine_id_paths = [
os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"),
os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"),
os.path.join(home, "AppData", "Roaming", "cursor-electron", "cursor-machine-id"),
os.path.join(home, "AppData", "Local", "cursor-electron", "cursor-machine-id"),
os.path.join(home, ".cursor-machine-id"),
]
elif system == "Darwin": # macOS
machine_id_paths = [
os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"),
os.path.join(home, "Library", "Application Support", "cursor-electron", "cursor-machine-id"),
os.path.join(home, ".cursor-machine-id"),
]
elif system == "Linux":
machine_id_paths = [
os.path.join(home, ".config", "Cursor", "cursor-machine-id"),
os.path.join(home, ".config", "cursor-electron", "cursor-machine-id"),
os.path.join(home, ".cursor-machine-id"),
]
# First remove existing machine IDs
for path in machine_id_paths:
remove_file(path)
# Then create new randomized IDs
for path in machine_id_paths:
create_fake_machine_id(path)
# Try to reset system machine ID if possible (with appropriate permissions)
if system == "Windows":
try:
# Windows: Create a temporary VBS script to reset machine GUID
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_machine_id_reset_may_require_running_as_administrator')} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.windows_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}")
elif system == "Linux":
try:
# Linux: Create a random machine-id in /etc/ (needs sudo)
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.note_complete_system_machine_id_reset_may_require_sudo_privileges')} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.linux_machine_id_modification_skipped', error=str(e))} {Style.RESET_ALL}")
def create_fake_trial_info(path, system, home):
"""Creates fake trial information to extend trial period."""
if not is_cursor_related(path):
return
try:
# Generate future expiry date (90 days from now)
future_date = (datetime.now().timestamp() + (90 * 24 * 60 * 60)) * 1000 # milliseconds
# Create fake trial info
fake_trial = {
"trialStartTimestamp": datetime.now().timestamp() * 1000,
"trialEndTimestamp": future_date,
"hasUsedTrial": False,
"machineId": generate_new_machine_id()
}
directory = os.path.dirname(path)
# Ensure directory exists
if not os.path.exists(directory):
os.makedirs(directory)
with open(path, 'w') as f:
json.dump(fake_trial, f)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.created_extended_trial_info', path=path)} {Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.error_creating_trial_info', path=path, error=str(e))} {Style.RESET_ALL}")
def reset_cursor():
"""Completely resets Cursor AI by removing all settings, caches, and extensions."""
system = platform.system()
home = os.path.expanduser("~")
display_banner()
display_features()
display_disclaimer()
if not get_confirmation():
print(f"\n{Fore.CYAN}{EMOJI['STOP']} {translator.get('totally_reset.reset_cancelled')} {Style.RESET_ALL}")
return
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.resetting_cursor_ai_editor')} {Style.RESET_ALL}")
# Define paths based on OS
if system == "Windows":
cursor_paths = [
os.path.join(home, "AppData", "Roaming", "Cursor"),
os.path.join(home, "AppData", "Local", "Cursor"),
os.path.join(home, "AppData", "Roaming", "cursor-electron"),
os.path.join(home, "AppData", "Local", "cursor-electron"),
os.path.join(home, "AppData", "Local", "CursorAI"),
os.path.join(home, "AppData", "Roaming", "CursorAI"),
# os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code
os.path.join(home, "AppData", "Local", "Temp", "Cursor"), # Temporary data
os.path.join(home, "AppData", "Local", "Temp", "cursor-updater"),
os.path.join(home, "AppData", "Local", "Programs", "cursor"),
]
# Additional locations for license/trial files on Windows
license_search_paths = [
os.path.join(home, "AppData", "Roaming"),
os.path.join(home, "AppData", "Local"),
os.path.join(home, "AppData", "LocalLow"),
]
# Registry instructions for Windows
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions')} {Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.windows_registry_instructions_2')} {Style.RESET_ALL}")
elif system == "Darwin": # macOS
cursor_paths = [
os.path.join(home, "Library", "Application Support", "Cursor"),
os.path.join(home, "Library", "Application Support", "cursor-electron"),
os.path.join(home, "Library", "Caches", "Cursor"),
os.path.join(home, "Library", "Caches", "cursor-electron"),
os.path.join(home, "Library", "Preferences", "Cursor"),
os.path.join(home, "Library", "Preferences", "cursor-electron"),
os.path.join(home, "Library", "Saved Application State", "com.cursor.Cursor.savedState"),
os.path.join(home, "Library", "HTTPStorages", "com.cursor.Cursor"),
os.path.join(home, "Library", "WebKit", "com.cursor.Cursor"),
# os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code
"/Applications/Cursor.app", # Main application location
]
# Additional locations for license/trial files on macOS
license_search_paths = [
os.path.join(home, "Library", "Application Support"),
os.path.join(home, "Library", "Preferences"),
os.path.join(home, "Library", "Caches"),
]
elif system == "Linux":
cursor_paths = [
os.path.join(home, ".config", "Cursor"),
os.path.join(home, ".config", "cursor-electron"),
os.path.join(home, ".cache", "Cursor"),
os.path.join(home, ".cache", "cursor-electron"),
os.path.join(home, ".local", "share", "Cursor"),
os.path.join(home, ".local", "share", "cursor-electron"),
# os.path.join(home, ".vscode"), # Removed to avoid affecting VS Code
os.path.join(home, ".local", "share", "applications", "cursor.desktop"),
os.path.join("/usr", "share", "applications", "cursor.desktop"),
os.path.join("/opt", "Cursor"),
]
# Additional locations for license/trial files on Linux
license_search_paths = [
os.path.join(home, ".config"),
os.path.join(home, ".local", "share"),
os.path.join(home, ".cache"),
]
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unsupported_os')} {Style.RESET_ALL}")
return
# Remove main Cursor directories
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_main_cursor_directories_and_files')} {Style.RESET_ALL}")
for path in cursor_paths:
remove_dir(path)
# Reset machine identifiers (this creates new ones)
reset_machine_id(system, home)
# Known trial/license file patterns
file_patterns = [
".cursor_trial_data",
"trial_info.json",
"license.json",
"cursor-license",
"cursor_license",
"cursor-auth",
"cursor_auth",
"cursor_subscription",
"cursor-subscription",
"cursor-state",
"cursorstate",
"cursorsettings",
"cursor-settings",
"ai-settings.json",
"cursor-machine-id",
"cursor_machine_id",
"cursor-storage"
]
# Direct known trial file paths
cursor_trial_files = [
os.path.join(home, ".cursor_trial_data"),
os.path.join(home, ".cursor_license"),
os.path.join(home, ".cursor-machine-id"),
os.path.join(home, ".cursor-state.json"),
]
# OS-specific known trial/license files
if system == "Windows":
cursor_trial_files.extend([
os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"),
os.path.join(home, "AppData", "Local", "Cursor", "license.json"),
os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"),
os.path.join(home, "AppData", "Roaming", "Cursor", "license.json"),
os.path.join(home, "AppData", "Roaming", "Cursor", "cursor-machine-id"),
os.path.join(home, "AppData", "Local", "Cursor", "cursor-machine-id"),
os.path.join(home, "AppData", "Local", "Cursor", "ai-settings.json"),
os.path.join(home, "AppData", "Roaming", "Cursor", "ai-settings.json"),
])
elif system == "Darwin": # macOS
cursor_trial_files.extend([
os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"),
os.path.join(home, "Library", "Application Support", "Cursor", "license.json"),
os.path.join(home, "Library", "Preferences", "Cursor", "trial_info.json"),
os.path.join(home, "Library", "Preferences", "Cursor", "license.json"),
os.path.join(home, "Library", "Application Support", "Cursor", "cursor-machine-id"),
os.path.join(home, "Library", "Application Support", "Cursor", "ai-settings.json"),
])
elif system == "Linux":
cursor_trial_files.extend([
os.path.join(home, ".config", "Cursor", "trial_info.json"),
os.path.join(home, ".config", "Cursor", "license.json"),
os.path.join(home, ".local", "share", "Cursor", "trial_info.json"),
os.path.join(home, ".local", "share", "Cursor", "license.json"),
os.path.join(home, ".config", "Cursor", "cursor-machine-id"),
os.path.join(home, ".config", "Cursor", "ai-settings.json"),
])
# Remove known trial/license files
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.removing_known')} {Style.RESET_ALL}")
for path in cursor_trial_files:
remove_file(path)
# Deep search for additional trial/license files
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.performing_deep_scan')} {Style.RESET_ALL}")
all_found_files = []
for base_path in license_search_paths:
if os.path.exists(base_path):
found_files = find_cursor_license_files(base_path, file_patterns)
all_found_files.extend(found_files)
if all_found_files:
print(f"\n🔎 {translator.get('totally_reset.found_additional_potential_license_trial_files', count=len(all_found_files))}\n")
for file_path in all_found_files:
remove_file(file_path)
else:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.no_additional_license_trial_files_found_in_deep_scan')} {Style.RESET_ALL}")
# Check for and remove localStorage files that might contain settings
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.checking_for_electron_localstorage_files')} {Style.RESET_ALL}")
if system == "Windows":
local_storage_paths = glob.glob(os.path.join(home, "AppData", "Roaming", "*cursor*", "Local Storage", "leveldb", "*"))
local_storage_paths += glob.glob(os.path.join(home, "AppData", "Local", "*cursor*", "Local Storage", "leveldb", "*"))
elif system == "Darwin":
local_storage_paths = glob.glob(os.path.join(home, "Library", "Application Support", "*cursor*", "Local Storage", "leveldb", "*"))
elif system == "Linux":
local_storage_paths = glob.glob(os.path.join(home, ".config", "*cursor*", "Local Storage", "leveldb", "*"))
for path in local_storage_paths:
if is_cursor_related(path):
remove_file(path)
# Create new trial files with extended expiration
print(f"\n{Fore.CYAN}{EMOJI['RESET']} {translator.get('totally_reset.creating_new_trial_information_with_extended_period')} {Style.RESET_ALL}")
if system == "Windows":
create_fake_trial_info(os.path.join(home, "AppData", "Local", "Cursor", "trial_info.json"), system, home)
create_fake_trial_info(os.path.join(home, "AppData", "Roaming", "Cursor", "trial_info.json"), system, home)
elif system == "Darwin":
create_fake_trial_info(os.path.join(home, "Library", "Application Support", "Cursor", "trial_info.json"), system, home)
elif system == "Linux":
create_fake_trial_info(os.path.join(home, ".config", "Cursor", "trial_info.json"), system, home)
print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('totally_reset.reset_log_1')}")
print(f" {translator.get('totally_reset.reset_log_2')}")
print(f" {translator.get('totally_reset.reset_log_3')}")
print(f"\n{Fore.GREEN}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_4')} {Style.RESET_ALL}")
print(f" {translator.get('totally_reset.reset_log_5')} {Style.RESET_ALL}")
print(f" {translator.get('totally_reset.reset_log_6')} {Style.RESET_ALL}")
print(f" {translator.get('totally_reset.reset_log_7')} {Style.RESET_ALL}")
print(f" {translator.get('totally_reset.reset_log_8')} {Style.RESET_ALL}")
print(f"\n{Fore.RED}{EMOJI['INFO']} {translator.get('totally_reset.reset_log_9')} {Style.RESET_ALL}")
if __name__ == "__main__":
try:
reset_cursor()
except KeyboardInterrupt:
print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.keyboard_interrupt')} {Style.RESET_ALL}")
sys.exit(1)
except Exception as e:
print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}")
print(f" {translator.get('totally_reset.report_issue')}")
sys.exit(1)
def run(translator=None):
"""Entry point for the totally reset cursor functionality when called from the main menu."""
try:
reset_cursor()
input(f"\n\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.return_to_main_menu')} {Style.RESET_ALL}")
except KeyboardInterrupt:
print(f"\n\n{Fore.RED}{EMOJI['STOP']} {translator.get('totally_reset.process_interrupted')} {Style.RESET_ALL}")
except Exception as e:
print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('totally_reset.unexpected_error', error=str(e))}{Style.RESET_ALL}")
print(f" {translator.get('totally_reset.report_issue')}")
input(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('totally_reset.press_enter_to_return_to_main_menu')} {Style.RESET_ALL}")

View File

@@ -1,69 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>uBlock — Your filters</title>
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="lib/codemirror/addon/hint/show-hint.css">
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
<link rel="stylesheet" href="css/themes/default.css">
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/fa-icons.css">
<link rel="stylesheet" href="css/dashboard-common.css">
<link rel="stylesheet" href="css/cloud-ui.css">
<link rel="stylesheet" href="css/1p-filters.css">
<link rel="stylesheet" href="css/codemirror.css">
</head>
<body>
<div class="body">
<div id="cloudWidget" class="hide" data-cloud-entry="myFiltersPane"></div>
<p>
<button id="userFiltersApply" class="preferred iconified" type="button" disabled><span class="fa-icon">check</span><span data-i18n="1pApplyChanges">_</span><span class="hover"></span></button>
<button id="userFiltersRevert" class="iconified" type="button" disabled><span class="fa-icon">undo</span><span data-i18n="genericRevert">_</span><span class="hover"></span></button>
&emsp;
<button id="importUserFiltersFromFile" class="iconified" type="button"><span class="fa-icon">download-alt</span><span data-i18n="1pImport">_</span><span class="hover"></span></button>
<button id="exportUserFiltersToFile" class="iconified" type="button"><span class="fa-icon">upload-alt</span><span data-i18n="1pExport">_</span><span class="hover"></span></button>
</p>
<p data-i18n="1pTrustWarning"></p>
<div class="li"><label><span id="enableMyFilters" class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pEnableMyFiltersLabel"></span></label></div>
<div id="trustMyFilters" class="li"><label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pTrustMyFiltersLabel"></span></label></div>
</div>
<div id="userFilters" class="codeMirrorContainer codeMirrorBreakAll cm-theme-override" spellcheck="false"></div>
<div class="hidden">
<input id="importFilePicker" type="file" accept="text/plain">
</div>
<script src="lib/codemirror/lib/codemirror.js"></script>
<script src="lib/codemirror/addon/comment/comment.js"></script>
<script src="lib/codemirror/addon/display/panel.js"></script>
<script src="lib/codemirror/addon/edit/closebrackets.js"></script>
<script src="lib/codemirror/addon/edit/matchbrackets.js"></script>
<script src="lib/codemirror/addon/fold/foldcode.js"></script>
<script src="lib/codemirror/addon/hint/show-hint.js"></script>
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
<script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="lib/diff/swatinem_diff.js"></script>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/codemirror/search.js" type="module"></script>
<script src="js/codemirror/search-thread.js"></script>
<script src="js/fa-icons.js" type="module"></script>
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script>
<script src="js/1p-filters.js" type="module"></script>
</body>
</html>

View File

@@ -1,117 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>uBlock — Filter lists</title>
<link rel="stylesheet" type="text/css" href="css/themes/default.css">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/fa-icons.css">
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
<link rel="stylesheet" type="text/css" href="css/3p-filters.css">
</head>
<body>
<div class="body">
<div id="cloudWidget" class="hide" data-cloud-entry="tpFiltersPane"></div>
<p id="actions">
<button id="buttonApply" class="preferred disabled iconified dontshrink" type="button" data-i18n-title="3pApplyChanges"><span class="fa-icon">check</span><span data-i18n="3pApplyChanges">_</span><span class="hover"></span></button>
<button id="buttonUpdate" class="preferred disabled iconified" type="button" data-i18n-title="3pUpdateNow"><span class="fa-icon">refresh</span><span data-i18n="3pUpdateNow">_</span><span class="hover"></span></button>
</p>
<div>
<div class="li">
<label><span class="input checkbox"><input type="checkbox" id="autoUpdate"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="3pAutoUpdatePrompt1"></span></label>
</div>
<div class="li">
<label><span class="input checkbox"><input type="checkbox" id="suspendUntilListsAreLoaded"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="3pSuspendUntilListsAreLoaded"></span></label>
</div>
<div class="li">
<label><span class="input checkbox"><input type="checkbox" id="parseCosmeticFilters"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span data-i18n="3pParseAllABPHideFiltersPrompt1"></span>&nbsp;<span class="fa-icon info" data-i18n-title="3pParseAllABPHideFiltersInfo">question-circle</span></span></label>
</div>
<div class="li">
<label><span class="input checkbox"><input type="checkbox" id="ignoreGenericCosmeticFilters"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span data-i18n="3pIgnoreGenericCosmeticFilters"></span>&nbsp;<span class="fa-icon info" data-i18n-title="3pIgnoreGenericCosmeticFiltersInfo">question-circle</span></span></label>
</div>
</div>
<div>
</div>
<div id="lists">
<div class="rootstats expandable" data-key="*">
<span class="fa-icon listExpander">angle-up</span><span id="listsOfBlockedHostsPrompt"></span>
</div>
<div class="searchfield"><input type="search" spellcheck="false" placeholder="" /><span class="fa-icon">search</span></div>
<div class="listEntries"></div>
<div class="li listEntry expandable" data-role="import">
<span class="detailbar">
<label><span class="fa-icon listExpander">angle-up</span><span class="listname" data-i18n="3pImport"></span></label>
<a class="fa-icon info towiki" href="https://github.com/gorhill/uBlock/wiki/Filter-lists-from-around-the-web" target="_blank">info-circle</a>
</span>
<textarea dir="ltr" spellcheck="false" placeholder="3pExternalListsHint"></textarea>
</div>
</div>
</div>
<div id="templates" style="display: none;">
<div class="listEntries"></div>
<div class="li listEntry" data-role="leaf">
<span class="detailbar">
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span class="listname forinput"></span>
</span></label>
<span class="iconbar"><!--
--><a class="fa-icon content" href="#" type="text/plain" target="_blank" data-i18n-title="3pViewContent">eye-open</a><!--
--><a class="fa-icon support" href="#" target="_blank">home</a><!--
--><span class="fa-icon remove">trash-o</span><!--
--><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
--><span class="fa-icon status unsecure" title="http">unlock-alt</span><!--
--><span class="fa-icon status obsolete" data-i18n-title="3pExternalListObsolete">exclamation-triangle</span><!--
--><span class="fa-icon status cache">clock-o</span><!--
--><span class="fa-icon status updating" data-i18n-title="3pUpdating">spinner</span><!--
--><span class="fa-icon status failed" data-i18n-title="3pNetworkError">unlink</span>
</span>
<span class="leafstats"></span>
</span>
</div>
<div class="li listEntry expandable" data-role="node">
<span class="detailbar">
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span class="listname forinput"></span></label>
<span class="nodestats"></span>
<span class="fa-icon listExpander">angle-up</span>
<span class="iconbar"><!--
--><a class="fa-icon support" href="#" target="_blank">home</a><!--
--><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
--><span class="fa-icon status obsolete" data-i18n-title="3pExternalListObsolete">exclamation-triangle</span><!--
--><span class="fa-icon status cache">clock-o</span><!--
--><span class="fa-icon status updating" data-i18n-title="3pUpdating">spinner</span><!--
--><span class="fa-icon status failed" data-i18n-title="3pNetworkError">unlink</span>
</span>
<span class="leafstats"></span>
</span>
</div>
<div class="li listEntry expandable" data-parent="root" data-role="node">
<span class="detailbar">
<label><span class="fa-icon listExpander">angle-up</span><span class="listname"></span></label>
<span class="nodestats"></span>
</span>
</div>
</div>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>
<script src="js/fa-icons.js" type="module"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/theme.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard-common.js" type="module"></script>
<script src="js/cloud-ui.js" type="module"></script>
<script src="js/3p-filters.js" type="module"></script>
</body>
</html>

View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/}
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{{project}} Copyright (C) {{year}} {{fullname}}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
{http://www.gnu.org/licenses/}.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
{http://www.gnu.org/philosophy/why-not-lgpl.html}.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More